diff --git a/3rdparty/fastcv/CMakeLists.txt b/3rdparty/fastcv/CMakeLists.txt new file mode 100644 index 0000000000..ab17375902 --- /dev/null +++ b/3rdparty/fastcv/CMakeLists.txt @@ -0,0 +1,32 @@ +if(HAVE_FASTCV) + set(FASTCV_HAL_VERSION 0.0.1 CACHE INTERNAL "") + set(FASTCV_HAL_LIBRARIES "fastcv_hal" CACHE INTERNAL "") + set(FASTCV_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" CACHE INTERNAL "") + set(FASTCV_HAL_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/include/fastcv_hal_core.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/fastcv_hal_imgproc.hpp" + CACHE INTERNAL "") + + file(GLOB FASTCV_HAL_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + + add_library(fastcv_hal STATIC ${FASTCV_HAL_FILES}) + + target_include_directories(fastcv_hal PRIVATE + ${CMAKE_SOURCE_DIR}/modules/core/include + ${CMAKE_SOURCE_DIR}/modules/imgproc/include + ${FASTCV_HAL_INCLUDE_DIRS} ${FastCV_INCLUDE_PATH}) + + target_link_libraries(fastcv_hal PUBLIC ${FASTCV_LIBRARY}) + + set_target_properties(fastcv_hal PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${3P_LIBRARY_OUTPUT_PATH}) + + if(NOT BUILD_SHARED_LIBS) + ocv_install_target(fastcv_hal EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev) + endif() + + if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(fastcv_hal PROPERTIES FOLDER "3rdparty") + endif() +else() + message(STATUS "FastCV is not available, disabling related HAL") +endif(HAVE_FASTCV) diff --git a/3rdparty/fastcv/fastcv.cmake b/3rdparty/fastcv/fastcv.cmake new file mode 100644 index 0000000000..b8172705ca --- /dev/null +++ b/3rdparty/fastcv/fastcv.cmake @@ -0,0 +1,43 @@ +function(download_fastcv root_dir) + + # Commit SHA in the opencv_3rdparty repo + set(FASTCV_COMMIT "dc5d58018f3af915a8d209386d2c58c0501c0f2c") + + # Define actual FastCV versions + if(ANDROID) + if(AARCH64) + message(STATUS "Download FastCV for Android aarch64") + set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2024_12_11.tgz") + set(FCV_PACKAGE_HASH "9dac41e86597305f846212dae31a4a88") + else() + message(STATUS "Download FastCV for Android armv7") + set(FCV_PACKAGE_NAME "fastcv_android_arm32_2024_12_11.tgz") + set(FCV_PACKAGE_HASH "fe2d30334180b17e3031eee92aac43b6") + endif() + elseif(UNIX AND NOT APPLE AND NOT IOS AND NOT XROS) + if(AARCH64) + set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2024_12_11.tgz") + set(FCV_PACKAGE_HASH "7b33ad833e6f15ab6d4ec64fa3c17acd") + else() + message("FastCV: fastcv lib for 32-bit Linux is not supported for now!") + endif() + endif(ANDROID) + + # Download Package + set(OPENCV_FASTCV_URL "https://raw.githubusercontent.com/opencv/opencv_3rdparty/${FASTCV_COMMIT}/fastcv/") + + ocv_download( FILENAME ${FCV_PACKAGE_NAME} + HASH ${FCV_PACKAGE_HASH} + URL ${OPENCV_FASTCV_URL} + DESTINATION_DIR ${root_dir} + ID FASTCV + STATUS res + UNPACK + RELATIVE_URL) + if(res) + set(HAVE_FASTCV TRUE CACHE BOOL "FastCV status") + else() + message(WARNING "FastCV: package download failed!") + endif() + +endfunction() diff --git a/3rdparty/fastcv/include/fastcv_hal_core.hpp b/3rdparty/fastcv/include/fastcv_hal_core.hpp new file mode 100644 index 0000000000..03c17dc6b5 --- /dev/null +++ b/3rdparty/fastcv/include/fastcv_hal_core.hpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef OPENCV_FASTCV_HAL_CORE_HPP_INCLUDED +#define OPENCV_FASTCV_HAL_CORE_HPP_INCLUDED + +#include + +#undef cv_hal_lut +#define cv_hal_lut fastcv_hal_lut +#undef cv_hal_normHammingDiff8u +#define cv_hal_normHammingDiff8u fastcv_hal_normHammingDiff8u +#undef cv_hal_mul8u16u +#define cv_hal_mul8u16u fastcv_hal_mul8u16u +#undef cv_hal_sub8u32f +#define cv_hal_sub8u32f fastcv_hal_sub8u32f +#undef cv_hal_transpose2d +#define cv_hal_transpose2d fastcv_hal_transpose2d +#undef cv_hal_meanStdDev +#define cv_hal_meanStdDev fastcv_hal_meanStdDev +#undef cv_hal_flip +#define cv_hal_flip fastcv_hal_flip +#undef cv_hal_rotate90 +#define cv_hal_rotate90 fastcv_hal_rotate +#undef cv_hal_addWeighted8u +#define cv_hal_addWeighted8u fastcv_hal_addWeighted8u +#undef cv_hal_mul8u +#define cv_hal_mul8u fastcv_hal_mul8u +#undef cv_hal_mul16s +#define cv_hal_mul16s fastcv_hal_mul16s +#undef cv_hal_mul32f +#define cv_hal_mul32f fastcv_hal_mul32f + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief look-up table transform of an array. +/// @param src_data Source image data +/// @param src_step Source image step +/// @param src_type Source image type +/// @param lut_data Pointer to lookup table +/// @param lut_channel_size Size of each channel in bytes +/// @param lut_channels Number of channels in lookup table +/// @param dst_data Destination data +/// @param dst_step Destination step +/// @param width Width of images +/// @param height Height of images +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_lut( + const uchar* src_data, + size_t src_step, + size_t src_type, + const uchar* lut_data, + size_t lut_channel_size, + size_t lut_channels, + uchar* dst_data, + size_t dst_step, + int width, + int height); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Hamming distance between two vectors +/// @param a pointer to first vector data +/// @param b pointer to second vector data +/// @param n length of vectors +/// @param cellSize how many bits of the vectors will be added and treated as a single bit, can be 1 (standard Hamming distance), 2 or 4 +/// @param result pointer to result output +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_normHammingDiff8u(const uchar* a, const uchar* b, int n, int cellSize, int* result); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul8u16u( + const uchar * src1_data, + size_t src1_step, + const uchar * src2_data, + size_t src2_step, + ushort * dst_data, + size_t dst_step, + int width, + int height, + double scale); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_sub8u32f( + const uchar *src1_data, + size_t src1_step, + const uchar *src2_data, + size_t src2_step, + float *dst_data, + size_t dst_step, + int width, + int height); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_transpose2d( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int src_width, + int src_height, + int element_size); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_meanStdDev( + const uchar * src_data, + size_t src_step, + int width, + int height, + int src_type, + double * mean_val, + double * stddev_val, + uchar * mask, + size_t mask_step); + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Flips a 2D array around vertical, horizontal, or both axes +/// @param src_type source and destination image type +/// @param src_data source image data +/// @param src_step source image step +/// @param src_width source and destination image width +/// @param src_height source and destination image height +/// @param dst_data destination image data +/// @param dst_step destination image step +/// @param flip_mode 0 flips around x-axis, 1 around y-axis, -1 both +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_flip( + int src_type, + const uchar* src_data, + size_t src_step, + int src_width, + int src_height, + uchar* dst_data, + size_t dst_step, + int flip_mode); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Rotates a 2D array in multiples of 90 degrees. +/// @param src_type source and destination image type +/// @param src_data source image data +/// @param src_step source image step +/// @param src_width source image width +/// @If angle has value [180] it is also destination image width +/// If angle has values [90, 270] it is also destination image height +/// @param src_height source and destination image height (destination image width for angles [90, 270]) +/// If angle has value [180] it is also destination image height +/// If angle has values [90, 270] it is also destination image width +/// @param dst_data destination image data +/// @param dst_step destination image step +/// @param angle clockwise angle for rotation in degrees from set [90, 180, 270] +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_rotate( + int src_type, + const uchar* src_data, + size_t src_step, + int src_width, + int src_height, + uchar* dst_data, + size_t dst_step, + int angle); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief weighted sum of two arrays using formula: dst[i] = a * src1[i] + b * src2[i] +/// @param src1_data first source image data +/// @param src1_step first source image step +/// @param src2_data second source image data +/// @param src2_step second source image step +/// @param dst_data destination image data +/// @param dst_step destination image step +/// @param width width of the images +/// @param height height of the images +/// @param scalars numbers a, b, and c +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_addWeighted8u( + const uchar* src1_data, + size_t src1_step, + const uchar* src2_data, + size_t src2_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + const double scalars[3]); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul8u( + const uchar *src1_data, + size_t src1_step, + const uchar *src2_data, + size_t src2_step, + uchar *dst_data, + size_t dst_step, + int width, + int height, + double scale); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul16s( + const short *src1_data, + size_t src1_step, + const short *src2_data, + size_t src2_step, + short *dst_data, + size_t dst_step, + int width, + int height, + double scale); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul32f( + const float *src1_data, + size_t src1_step, + const float *src2_data, + size_t src2_step, + float *dst_data, + size_t dst_step, + int width, + int height, + double scale); + +#endif diff --git a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp new file mode 100644 index 0000000000..c9360f95ed --- /dev/null +++ b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef OPENCV_FASTCV_HAL_IMGPROC_HPP_INCLUDED +#define OPENCV_FASTCV_HAL_IMGPROC_HPP_INCLUDED + +#include + +#undef cv_hal_medianBlur +#define cv_hal_medianBlur fastcv_hal_medianBlur +#undef cv_hal_sobel +#define cv_hal_sobel fastcv_hal_sobel +#undef cv_hal_boxFilter +#define cv_hal_boxFilter fastcv_hal_boxFilter +#undef cv_hal_adaptiveThreshold +#define cv_hal_adaptiveThreshold fastcv_hal_adaptiveThreshold +#undef cv_hal_gaussianBlurBinomial +#define cv_hal_gaussianBlurBinomial fastcv_hal_gaussianBlurBinomial +#undef cv_hal_warpPerspective +#define cv_hal_warpPerspective fastcv_hal_warpPerspective +#undef cv_hal_pyrdown +#define cv_hal_pyrdown fastcv_hal_pyrdown +#undef cv_hal_cvtBGRtoHSV +#define cv_hal_cvtBGRtoHSV fastcv_hal_cvtBGRtoHSV +#undef cv_hal_cvtBGRtoYUVApprox +#define cv_hal_cvtBGRtoYUVApprox fastcv_hal_cvtBGRtoYUVApprox +#undef cv_hal_canny +#define cv_hal_canny fastcv_hal_canny +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Calculate medianBlur filter +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param depth Depths of source and destination image +/// @param cn Number of channels +/// @param ksize Size of kernel +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_medianBlur( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + int ksize); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Computes Sobel derivatives +/// +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param src_depth Depth of source image +/// @param dst_depth Depths of destination image +/// @param cn Number of channels +/// @param margin_left Left margins for source image +/// @param margin_top Top margins for source image +/// @param margin_right Right margins for source image +/// @param margin_bottom Bottom margins for source image +/// @param dx orders of the derivative x +/// @param dy orders of the derivative y +/// @param ksize Size of kernel +/// @param scale Scale factor for the computed derivative values +/// @param delta Delta value that is added to the results prior to storing them in dst +/// @param border_type Border type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_sobel( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + int dx, + int dy, + int ksize, + double scale, + double delta, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int fastcv_hal_boxFilter( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + size_t ksize_width, + size_t ksize_height, + int anchor_x, + int anchor_y, + bool normalize, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_adaptiveThreshold( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + double maxValue, + int adaptiveMethod, + int thresholdType, + int blockSize, + double C); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Blurs an image using a Gaussian filter. +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param depth Depth of source and destination image +/// @param cn Number of channels +/// @param margin_left Left margins for source image +/// @param margin_top Top margins for source image +/// @param margin_right Right margins for source image +/// @param margin_bottom Bottom margins for source image +/// @param ksize Kernel size +/// @param border_type Border type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_gaussianBlurBinomial( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + size_t margin_left, + size_t margin_top, + size_t margin_right, + size_t margin_bottom, + size_t ksize, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Applies a perspective transformation to an image. +/// +/// @param src_type Source and destination image type +/// @param src_data Source image data +/// @param src_step Source image step +/// @param src_width Source image width +/// @param src_height Source image height +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param dst_width Destination image width +/// @param dst_height Destination image height +/// @param M 3x3 matrix with transform coefficients +/// @param interpolation Interpolation mode (CV_HAL_INTER_NEAREST, ...) +/// @param border_type Border processing mode (CV_HAL_BORDER_REFLECT, ...) +/// @param border_value Values to use for CV_HAL_BORDER_CONSTANT mode +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_warpPerspective( + int src_type, + const uchar* src_data, + size_t src_step, + int src_width, + int src_height, + uchar* dst_data, + size_t dst_step, + int dst_width, + int dst_height, + const double M[9], + int interpolation, + int border_type, + const double border_value[4]); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_pyrdown( + const uchar* src_data, + size_t src_step, + int src_width, + int src_height, + uchar* dst_data, + size_t dst_step, + int dst_width, + int dst_height, + int depth, + int cn, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_cvtBGRtoHSV( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isFullRange, + bool isHSV); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_cvtBGRtoYUVApprox( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isCbCr); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Canny edge detector +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param cn Number of channels +/// @param lowThreshold low hresholds value +/// @param highThreshold high thresholds value +/// @param ksize Kernel size for Sobel operator. +/// @param L2gradient Flag, indicating use of L2 or L1 norma. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_canny( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int cn, + double lowThreshold, + double highThreshold, + int ksize, + bool L2gradient); + +#endif diff --git a/3rdparty/fastcv/include/fastcv_hal_utils.hpp b/3rdparty/fastcv/include/fastcv_hal_utils.hpp new file mode 100644 index 0000000000..8492fbef58 --- /dev/null +++ b/3rdparty/fastcv/include/fastcv_hal_utils.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef OPENCV_FASTCV_HAL_UTILS_HPP_INCLUDED +#define OPENCV_FASTCV_HAL_UTILS_HPP_INCLUDED + +#include "fastcv.h" +#include + +#define INITIALIZATION_CHECK \ +{ \ + if (!FastCvContext::getContext().isInitialized) \ + { \ + return CV_HAL_ERROR_UNKNOWN; \ + } \ +} + +#define CV_HAL_RETURN(status, func) \ +{ \ + if( status == FASTCV_SUCCESS ) \ + { \ + CV_LOG_DEBUG(NULL, "FastCV HAL for "<<#func<<" run successfully!"); \ + return CV_HAL_ERROR_OK; \ + } \ + else if(status == FASTCV_EBADPARAM || status == FASTCV_EUNALIGNPARAM || \ + status == FASTCV_EUNSUPPORTED || status == FASTCV_EHWQDSP || \ + status == FASTCV_EHWGPU) \ + { \ + CV_LOG_DEBUG(NULL, "FastCV status:"< +#include + + +class ParallelTableLookup : public cv::ParallelLoopBody +{ +public: + + ParallelTableLookup(const uchar* src_data_, int width_, size_t src_step_, const uchar* lut_data_, uchar* dst_data_, size_t dst_step_) : + cv::ParallelLoopBody(), src_data(src_data_), width(width_), src_step(src_step_), lut_data(lut_data_), dst_data(dst_data_), dst_step(dst_step_) + { + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + fcvStatus status = FASTCV_SUCCESS; + for (int y = range.start; y < range.end; y++) { + status = fcvTableLookupu8((uint8_t*)src_data + y * src_step, width, 1, src_step, (uint8_t*)lut_data, (uint8_t*)dst_data + y * dst_step, dst_step); + if(status != FASTCV_SUCCESS) + CV_LOG_ERROR(NULL,"FastCV error:"<= 128.0f) || + (scalars[1] < -128.0f) || (scalars[1] >= 128.0f) || + (scalars[2] < -(1<<23))|| (scalars[2] >= 1<<23)) + CV_HAL_RETURN_NOT_IMPLEMENTED( + cv::format("Alpha:%f,Beta:%f,Gamma:%f is not supported because it's too large or too small\n", + scalars[0],scalars[1],scalars[2])); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + + if (height == 1) + { + src1_step = width*sizeof(uchar); + src2_step = width*sizeof(uchar); + dst_step = width*sizeof(uchar); + + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const uint8_t *src1 = src1_data + range.start; + const uint8_t *src2 = src2_data + range.start; + uint8_t *dst = dst_data + range.start; + fcvAddWeightedu8_v2(src1, rangeWidth, height, src1_step, src2, src2_step, + scalars[0], scalars[1], scalars[2], dst, dst_step); + }); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const uint8_t *src1 = src1_data + range.start * src1_step; + const uint8_t *src2 = src2_data + range.start * src2_step; + uint8_t *dst = dst_data + range.start * dst_step; + fcvAddWeightedu8_v2(src1, width, rangeHeight, src1_step, src2, src2_step, + scalars[0], scalars[1], scalars[2], dst, dst_step); + }); + } + + CV_HAL_RETURN(status, hal_addWeighted8u_v2); +} + +int fastcv_hal_mul8u( + const uchar *src1_data, + size_t src1_step, + const uchar *src2_data, + size_t src2_step, + uchar *dst_data, + size_t dst_step, + int width, + int height, + double scale) +{ + int8_t sF; + + if(FCV_CMP_EQ(scale,1.0)) { sF = 0; } + else if(scale > 1.0) + { + if(FCV_CMP_EQ(scale,2.0)) { sF = -1; } + else if(FCV_CMP_EQ(scale,4.0)) { sF = -2; } + else if(FCV_CMP_EQ(scale,8.0)) { sF = -3; } + else if(FCV_CMP_EQ(scale,16.0)) { sF = -4; } + else if(FCV_CMP_EQ(scale,32.0)) { sF = -5; } + else if(FCV_CMP_EQ(scale,64.0)) { sF = -6; } + else if(FCV_CMP_EQ(scale,128.0)) { sF = -7; } + else if(FCV_CMP_EQ(scale,256.0)) { sF = -8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else if(scale > 0 && scale < 1.0) + { + if(FCV_CMP_EQ(scale,1/2.0)) { sF = 1; } + else if(FCV_CMP_EQ(scale,1/4.0)) { sF = 2; } + else if(FCV_CMP_EQ(scale,1/8.0)) { sF = 3; } + else if(FCV_CMP_EQ(scale,1/16.0)) { sF = 4; } + else if(FCV_CMP_EQ(scale,1/32.0)) { sF = 5; } + else if(FCV_CMP_EQ(scale,1/64.0)) { sF = 6; } + else if(FCV_CMP_EQ(scale,1/128.0)) { sF = 7; } + else if(FCV_CMP_EQ(scale,1/256.0)) { sF = 8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + if(height == 1) + { + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const uchar* yS1 = src1_data + static_cast(range.start); + const uchar* yS2 = src2_data + static_cast(range.start); + uchar* yD = dst_data + static_cast(range.start); + fcvElementMultiplyu8(yS1, rangeWidth, 1, 0, yS2, 0, sF, + FASTCV_CONVERT_POLICY_SATURATE, yD, 0); + }, nStripes); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const uchar* yS1 = src1_data + static_cast(range.start)*src1_step; + const uchar* yS2 = src2_data + static_cast(range.start)*src2_step; + uchar* yD = dst_data + static_cast(range.start)*dst_step; + fcvElementMultiplyu8(yS1, width, rangeHeight, src1_step, yS2, src2_step, + sF, FASTCV_CONVERT_POLICY_SATURATE, yD, dst_step); + }, nStripes); + } + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_mul8u); +} + +int fastcv_hal_mul16s( + const short *src1_data, + size_t src1_step, + const short *src2_data, + size_t src2_step, + short *dst_data, + size_t dst_step, + int width, + int height, + double scale) +{ + int8_t sF; + + if(FCV_CMP_EQ(scale,1.0)) { sF = 0; } + else if(scale > 1.0) + { + if(FCV_CMP_EQ(scale,2.0)) { sF = -1; } + else if(FCV_CMP_EQ(scale,4.0)) { sF = -2; } + else if(FCV_CMP_EQ(scale,8.0)) { sF = -3; } + else if(FCV_CMP_EQ(scale,16.0)) { sF = -4; } + else if(FCV_CMP_EQ(scale,32.0)) { sF = -5; } + else if(FCV_CMP_EQ(scale,64.0)) { sF = -6; } + else if(FCV_CMP_EQ(scale,128.0)) { sF = -7; } + else if(FCV_CMP_EQ(scale,256.0)) { sF = -8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else if(scale > 0 && scale < 1.0) + { + if(FCV_CMP_EQ(scale,1/2.0)) { sF = 1; } + else if(FCV_CMP_EQ(scale,1/4.0)) { sF = 2; } + else if(FCV_CMP_EQ(scale,1/8.0)) { sF = 3; } + else if(FCV_CMP_EQ(scale,1/16.0)) { sF = 4; } + else if(FCV_CMP_EQ(scale,1/32.0)) { sF = 5; } + else if(FCV_CMP_EQ(scale,1/64.0)) { sF = 6; } + else if(FCV_CMP_EQ(scale,1/128.0)) { sF = 7; } + else if(FCV_CMP_EQ(scale,1/256.0)) { sF = 8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + if(height == 1) + { + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const short* yS1 = src1_data + static_cast(range.start); + const short* yS2 = src2_data + static_cast(range.start); + short* yD = dst_data + static_cast(range.start); + fcvElementMultiplys16(yS1, rangeWidth, 1, 0, yS2, 0, sF, + FASTCV_CONVERT_POLICY_SATURATE, yD, 0); + }, nStripes); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const short* yS1 = src1_data + static_cast(range.start) * (src1_step/sizeof(short)); + const short* yS2 = src2_data + static_cast(range.start) * (src2_step/sizeof(short)); + short* yD = dst_data + static_cast(range.start) * (dst_step/sizeof(short)); + fcvElementMultiplys16(yS1, width, rangeHeight, src1_step, yS2, src2_step, + sF, FASTCV_CONVERT_POLICY_SATURATE, yD, dst_step); + }, nStripes); + } + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_mul16s); +} + +int fastcv_hal_mul32f( + const float *src1_data, + size_t src1_step, + const float *src2_data, + size_t src2_step, + float *dst_data, + size_t dst_step, + int width, + int height, + double scale) +{ + if(!FCV_CMP_EQ(scale,1.0)) + CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + if(height == 1) + { + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const float* yS1 = src1_data + static_cast(range.start); + const float* yS2 = src2_data + static_cast(range.start); + float* yD = dst_data + static_cast(range.start); + fcvElementMultiplyf32(yS1, rangeWidth, 1, 0, yS2, 0, yD, 0); + }, nStripes); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const float* yS1 = src1_data + static_cast(range.start) * (src1_step/sizeof(float)); + const float* yS2 = src2_data + static_cast(range.start) * (src2_step/sizeof(float)); + float* yD = dst_data + static_cast(range.start) * (dst_step/sizeof(float)); + fcvElementMultiplyf32(yS1, width, rangeHeight, src1_step, + yS2, src2_step, yD, dst_step); + }, nStripes); + } + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_mul32f); +} \ No newline at end of file diff --git a/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp new file mode 100644 index 0000000000..d1e972b672 --- /dev/null +++ b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp @@ -0,0 +1,1050 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#include "fastcv_hal_imgproc.hpp" +#include "fastcv_hal_utils.hpp" +#include +#include + + +int fastcv_hal_medianBlur( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + int ksize) +{ + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input image width and height should greater than kernel size + if ((height <= ksize) || (width <= ksize)) + CV_HAL_RETURN_NOT_IMPLEMENTED("Input image size should be larger than kernel size"); + + // The input channel should be 1 + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + int fcvFuncType = FCV_MAKETYPE(ksize,depth); + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + { + status = fcvFilterMedian3x3u8_v3(src_data, width, height, src_step, dst_data, dst_step, + fcvBorderType::FASTCV_BORDER_REPLICATE, 0); + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, depth:%s is not supported", ksize, cv::depthToString(depth))); + } + + CV_HAL_RETURN(status, hal_medianBlur); +} + +class FcvSobelLoop_Invoker : public cv::ParallelLoopBody +{ + public: + + FcvSobelLoop_Invoker(const cv::Mat& _src, cv::Mat& _dst, int _dx, int _dy, int _ksize, fcvBorderType _fcvBorder, + int _fcvBorderValue) : cv::ParallelLoopBody(), src(_src), dst(_dst), dx(_dx), dy(_dy), ksize(_ksize), + fcvBorder(_fcvBorder), fcvBorderValue(_fcvBorderValue) + { + width = src.cols; + height = src.rows; + halfKernelSize = ksize/2; + fcvFuncType = FCV_MAKETYPE(ksize,src.depth()); + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + int topLines = 0; + int rangeHeight = range.end-range.start; + int paddedHeight = rangeHeight; + + // Need additional lines to be border. + if(range.start > 0) + { + topLines += halfKernelSize; + paddedHeight += halfKernelSize; + } + + if(range.end < height) + { + paddedHeight += halfKernelSize; + } + + cv::Mat srcPadded = src(cv::Rect(0, range.start-topLines, width, paddedHeight)); + cv::Mat dstPadded = cv::Mat(paddedHeight, width, dst.depth()); + + int16_t *dxBuffer = nullptr, *dyBuffer = nullptr; + + if ((dx == 1) && (dy == 0)) + { + dxBuffer = (int16_t*)dstPadded.data; + } + else if ((dx == 0) && (dy == 1)) + { + dyBuffer = (int16_t*)dstPadded.data; + } + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + { + fcvFilterSobel3x3u8s16(srcPadded.data, width, paddedHeight, srcPadded.step, dxBuffer, dyBuffer, dstPadded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(5,CV_8U): + { + fcvFilterSobel5x5u8s16(srcPadded.data, width, paddedHeight, srcPadded.step, dxBuffer, dyBuffer, dstPadded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(7,CV_8U): + { + fcvFilterSobel7x7u8s16(srcPadded.data, width, paddedHeight, srcPadded.step, dxBuffer, dyBuffer, dstPadded.step, + fcvBorder, 0); + break; + } + default: + CV_Error(cv::Error::StsBadArg, cv::format("Ksize:%d, src_depth:%s is not supported", + ksize, cv::depthToString(src.depth()))); + break; + } + + // Only copy center part back to output image and ignore the padded lines + cv::Mat temp1 = dstPadded(cv::Rect(0, topLines, width, rangeHeight)); + cv::Mat temp2 = dst(cv::Rect(0, range.start, width, rangeHeight)); + temp1.copyTo(temp2); + } + + private: + const cv::Mat& src; + cv::Mat& dst; + int width; + int height; + int dx; + int dy; + int ksize; + int halfKernelSize; + int fcvFuncType; + fcvBorderType fcvBorder; + int fcvBorderValue; + + FcvSobelLoop_Invoker(const FcvSobelLoop_Invoker &); // = delete; + const FcvSobelLoop_Invoker& operator= (const FcvSobelLoop_Invoker &); // = delete; +}; + +int fastcv_hal_sobel( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + int dx, + int dy, + int ksize, + double scale, + double delta, + int border_type) +{ + if (!(FCV_CMP_EQ(scale, 1.0f) && FCV_CMP_EQ(delta, 0.0f))) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Scale:%f, delta:%f is not supported", scale, delta)); + + // Only support one direction derivatives and the order is 1.(dx=1 && dy=0)||(dx=0 && dy=1) + if ((dx + dy == 0) || (dx + dy > 1)) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Dx:%d Dy:%d is not supported",dx, dy)); + + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input image width and height should greater than kernel size + if ((height <= ksize) || (width <= ksize)) + CV_HAL_RETURN_NOT_IMPLEMENTED("Input image size should be larger than kernel size"); + + // The input channel should be 1 + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + // 1. When ksize <= 0, OpenCV will use Scharr Derivatives + // 2. When ksize == 1, OpenCV will use 3×1 or 1×3 kernel(no Gaussian smoothing is done) + // FastCV doesn't support above two situation + if (ksize <= 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Scharr derivatives or non square kernel are not supported"); + + // Only support the result type is CV_16S + if (dst_depth != CV_16S) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Dst depth:%s is not supported", cv::depthToString(dst_depth))); + + // Only support following ksize and src_depth as input + if ((FCV_MAKETYPE(ksize,src_depth) != FCV_MAKETYPE(3, CV_8U)) && + (FCV_MAKETYPE(ksize,src_depth) != FCV_MAKETYPE(5, CV_8U)) && + (FCV_MAKETYPE(ksize,src_depth) != FCV_MAKETYPE(7, CV_8U))) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, src_depth:%s is not supported", ksize, cv::depthToString(src_depth))); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + fcvBorderType fcvBorder = FASTCV_BORDER_CONSTANT; + + switch (border_type) + { + // For constant border, there are no border value, OpenCV default value is 0 + case cv::BorderTypes::BORDER_CONSTANT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_CONSTANT; + break; + } + case cv::BorderTypes::BORDER_REPLICATE: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REPLICATE; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Border type:%s is not supported", borderToString(border_type))); + } + + cv::Mat src = cv::Mat(height, width, CV_MAKE_TYPE(src_depth, 1), (void*)src_data, src_step); + cv::Mat dst = cv::Mat(height, width, CV_MAKE_TYPE(dst_depth, 1), (void*)dst_data, dst_step); + + if (margin_left||margin_top||margin_top||margin_bottom) + { + // Need additional lines to be border. + int paddedHeight = height, paddedWidth = width, startX = 0, startY = 0; + + if(margin_left != 0) + { + src_data -= ksize/2; + paddedWidth += ksize/2; + startX = ksize/2; + } + + if(margin_top != 0) + { + src_data -= (ksize/2) * src_step; + paddedHeight += ksize/2; + startY = ksize/2; + } + + if(margin_right != 0) + { + paddedWidth += ksize/2; + } + + if(margin_bottom != 0) + { + paddedHeight += ksize/2; + } + + cv::Mat padded(paddedHeight, paddedWidth, src_depth); + int16_t *dxBuffer = nullptr, *dyBuffer = nullptr; + + if ((dx == 1) && (dy == 0)) + { + dxBuffer = (int16_t*)padded.data; + dyBuffer = NULL; + } + else if ((dx == 0) && (dy == 1)) + { + dxBuffer = NULL; + dyBuffer = (int16_t*)padded.data; + } + + int fcvFuncType = FCV_MAKETYPE(ksize, src_depth); + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + { + status = fcvFilterSobel3x3u8s16(src_data, paddedWidth, paddedHeight, src_step, dxBuffer, dyBuffer, padded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(5,CV_8U): + { + status = fcvFilterSobel5x5u8s16(src_data, paddedWidth, paddedHeight, src_step, dxBuffer, dyBuffer, padded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(7,CV_8U): + { + status = fcvFilterSobel7x7u8s16(src_data, paddedWidth, paddedHeight, src_step, dxBuffer, dyBuffer, padded.step, + fcvBorder, 0); + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, src_depth:%s is not supported", + ksize, cv::depthToString(src_depth))); + break; + } + + cv::Mat temp1 = padded(cv::Rect(startX, startY, width, height)); + temp1.copyTo(dst); + } + else + { + int nThreads = cv::getNumThreads(); + int nStripes = nThreads > 1 ? 3*nThreads : 1; + + cv::parallel_for_(cv::Range(0, height), FcvSobelLoop_Invoker(src, dst, dx, dy, ksize, fcvBorder, 0), nStripes); + } + + CV_HAL_RETURN(status, hal_sobel); +} + +int fastcv_hal_boxFilter( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + size_t ksize_width, + size_t ksize_height, + int anchor_x, + int anchor_y, + bool normalize, + int border_type) +{ + if((width*height) < (320*240)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("input size not supported"); + } + else if(src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("in-place processing not supported"); + } + else if(src_depth != CV_8U || cn != 1) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + else if(dst_depth != src_depth) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("same src and dst type supported"); + } + else if(ksize_width != ksize_height || + (ksize_width != 3 && ksize_width != 5)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("kernel size not supported"); + } + else if(anchor_x != -1 || anchor_y != -1 || + margin_left != 0 || margin_top != 0 || + margin_right != 0 || margin_bottom != 0) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("ROI not supported"); + } + + INITIALIZATION_CHECK; + + fcvBorderType bdr; + uint8_t bdrVal = 0; + switch(border_type) + { + case cv::BORDER_REPLICATE: + bdr = FASTCV_BORDER_REPLICATE; + break; + case cv::BORDER_REFLECT: + bdr = FASTCV_BORDER_REFLECT; + break; + case cv::BORDER_REFLECT101: // cv::BORDER_REFLECT_101, BORDER_DEFAULT + bdr = FASTCV_BORDER_REFLECT_V2; + break; + default: + CV_HAL_RETURN_NOT_IMPLEMENTED("border type not supported"); + } + + fcvStatus status = FASTCV_SUCCESS; + if(ksize_width == 3) + { + status = fcvBoxFilter3x3u8_v3(src_data, width, height, src_step, + dst_data, dst_step, normalize, bdr, bdrVal); + } + else if(ksize_width == 5) + { + status = fcvBoxFilter5x5u8_v2(src_data, width, height, src_step, + dst_data, dst_step, normalize, bdr, bdrVal); + } + + CV_HAL_RETURN(status,hal_boxFilter); +} + +int fastcv_hal_adaptiveThreshold( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + double maxValue, + int adaptiveMethod, + int thresholdType, + int blockSize, + double C) +{ + if((width*height) < (320*240)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("input size not supported"); + } + else if (src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("In place processing not supported"); + } + + int value = (thresholdType == cv::THRESH_BINARY) ? cvCeil(C) : cvFloor(C); + + if ((maxValue < 1) || (maxValue > 255)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("max value 1-255 supported"); + } + + INITIALIZATION_CHECK; + + uchar maxVal = cv::saturate_cast(maxValue); + + fcvThreshType threshType = (thresholdType == cv::THRESH_BINARY) ? FCV_THRESH_BINARY : FCV_THRESH_BINARY_INV; + + fcvStatus status = FASTCV_SUCCESS; + if(adaptiveMethod == cv::ADAPTIVE_THRESH_GAUSSIAN_C) + { + if(blockSize == 3) + status = fcvAdaptiveThresholdGaussian3x3u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else if(blockSize == 5) + status = fcvAdaptiveThresholdGaussian5x5u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED("block size not supported"); + } + } + else if(adaptiveMethod == cv::ADAPTIVE_THRESH_MEAN_C) + { + if(blockSize == 3) + status = fcvAdaptiveThresholdMean3x3u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else if(blockSize == 5) + status = fcvAdaptiveThresholdMean5x5u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED("block size not supported"); + } + } + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED("adaptive method not supported"); + } + + CV_HAL_RETURN(status,hal_adaptiveThreshold); +} + +class FcvGaussianBlurLoop_Invoker : public cv::ParallelLoopBody +{ + public: + + FcvGaussianBlurLoop_Invoker(const cv::Mat& _src, cv::Mat& _dst, int _ksize, fcvBorderType _fcvBorder, int _fcvBorderValue) : + cv::ParallelLoopBody(), src(_src),dst(_dst), ksize(_ksize), fcvBorder(_fcvBorder), fcvBorderValue(_fcvBorderValue) + { + width = src.cols; + height = src.rows; + halfKernelSize = ksize / 2; + fcvFuncType = FCV_MAKETYPE(ksize, src.depth()); + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + int topLines = 0; + int rangeHeight = range.end-range.start; + int paddedHeight = rangeHeight; + + // Need additional lines to be border. + if(range.start != 0) + { + topLines += halfKernelSize; + paddedHeight += halfKernelSize; + } + + if(range.end != height) + { + paddedHeight += halfKernelSize; + } + + const cv::Mat srcPadded = src(cv::Rect(0, range.start - topLines, width, paddedHeight)); + cv::Mat dstPadded = cv::Mat(paddedHeight, width, CV_8U); + + if (fcvFuncType == FCV_MAKETYPE(3,CV_8U)) + fcvFilterGaussian3x3u8_v4(srcPadded.data, width, paddedHeight, srcPadded.step, dstPadded.data, dstPadded.step, + fcvBorder, 0); + else if (fcvFuncType == FCV_MAKETYPE(5,CV_8U)) + fcvFilterGaussian5x5u8_v3(srcPadded.data, width, paddedHeight, srcPadded.step, dstPadded.data, dstPadded.step, + fcvBorder, 0); + + // Only copy center part back to output image and ignore the padded lines + cv::Mat temp1 = dstPadded(cv::Rect(0, topLines, width, rangeHeight)); + cv::Mat temp2 = dst(cv::Rect(0, range.start, width, rangeHeight)); + temp1.copyTo(temp2); + } + + private: + const cv::Mat& src; + cv::Mat& dst; + int width; + int height; + const int ksize; + int halfKernelSize; + int fcvFuncType; + fcvBorderType fcvBorder; + int fcvBorderValue; + + FcvGaussianBlurLoop_Invoker(const FcvGaussianBlurLoop_Invoker &); // = delete; + const FcvGaussianBlurLoop_Invoker& operator= (const FcvGaussianBlurLoop_Invoker &); // = delete; +}; + +int fastcv_hal_gaussianBlurBinomial( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + size_t margin_left, + size_t margin_top, + size_t margin_right, + size_t margin_bottom, + size_t ksize, + int border_type) +{ + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input image width and height should greater than kernel size + if (((size_t)height <= ksize) || ((size_t)width <= ksize)) + CV_HAL_RETURN_NOT_IMPLEMENTED("Input image size should be larger than kernel size"); + + // The input channel should be 1 + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + // Do not support for ROI case + if((margin_left!=0) || (margin_top != 0) || (margin_right != 0) || (margin_bottom !=0)) + CV_HAL_RETURN_NOT_IMPLEMENTED("ROI is not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + fcvBorderType fcvBorder = fcvBorderType::FASTCV_BORDER_UNDEFINED; + int fcvFuncType = FCV_MAKETYPE(ksize,depth); + + switch (border_type) + { + case cv::BorderTypes::BORDER_REPLICATE: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REPLICATE; + break; + } + // For constant border, there are no border value, OpenCV default value is 0 + case cv::BorderTypes::BORDER_CONSTANT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_CONSTANT; + break; + } + case cv::BorderTypes::BORDER_REFLECT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REFLECT; + break; + } + case cv::BorderTypes::BORDER_REFLECT_101: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REFLECT_V2; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Border type:%s is not supported", borderToString(border_type))); + } + + int nThreads = cv::getNumThreads(); + int nStripes = (nThreads > 1) ? ((height > 60) ? 3 * nThreads : 1) : 1; + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + case FCV_MAKETYPE(5,CV_8U): + { + cv::Mat src = cv::Mat(height, width, CV_8UC1, (void*)src_data, src_step); + cv::Mat dst = cv::Mat(height, width, CV_8UC1, (void*)dst_data, dst_step); + cv::parallel_for_(cv::Range(0, height), FcvGaussianBlurLoop_Invoker(src, dst, ksize, fcvBorder, 0), nStripes); + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, depth:%s is not supported", (int)ksize, cv::depthToString(depth))); + } + + CV_HAL_RETURN(status, hal_gaussianBlurBinomial); +} + +class FcvWarpPerspectiveLoop_Invoker : public cv::ParallelLoopBody +{ + public: + + FcvWarpPerspectiveLoop_Invoker(const uchar* _src_data, int _src_width, int _src_height, size_t _src_step, uchar* _dst_data, + int _dst_width, int _dst_height, size_t _dst_step, int _type, const double* _M, + fcvInterpolationType _fcvInterpolation, fcvBorderType _fcvBorder, int _fcvBorderValue) : + cv::ParallelLoopBody(), src_data(_src_data), src_width(_src_width), src_height(_src_height), src_step(_src_step), + dst_data(_dst_data), dst_width(_dst_width), dst_height(_dst_height), dst_step(_dst_step), type(_type), + M(_M), fcvInterpolation(_fcvInterpolation),fcvBorder(_fcvBorder), + fcvBorderValue(_fcvBorderValue) {} + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + uchar* dst = dst_data + range.start*dst_step; + int rangeHeight = range.end - range.start; + + float rangeMatrix[9]; + rangeMatrix[0] = (float)(M[0]); + rangeMatrix[1] = (float)(M[1]); + rangeMatrix[2] = (float)(M[2]+range.start*M[1]); + rangeMatrix[3] = (float)(M[3]); + rangeMatrix[4] = (float)(M[4]); + rangeMatrix[5] = (float)(M[5]+range.start*M[4]); + rangeMatrix[6] = (float)(M[6]); + rangeMatrix[7] = (float)(M[7]); + rangeMatrix[8] = (float)(M[8]+range.start*M[7]); + fcvWarpPerspectiveu8_v5(src_data, src_width, src_height, src_step, CV_MAT_CN(type), dst, dst_width, rangeHeight, + dst_step, rangeMatrix, fcvInterpolation, fcvBorder, fcvBorderValue); + } + + private: + const uchar* src_data; + const int src_width; + const int src_height; + const size_t src_step; + uchar* dst_data; + const int dst_width; + const int dst_height; + const size_t dst_step; + const int type; + const double* M; + fcvInterpolationType fcvInterpolation; + fcvBorderType fcvBorder; + int fcvBorderValue; + + FcvWarpPerspectiveLoop_Invoker(const FcvWarpPerspectiveLoop_Invoker &); // = delete; + const FcvWarpPerspectiveLoop_Invoker& operator= (const FcvWarpPerspectiveLoop_Invoker &); // = delete; +}; + +int fastcv_hal_warpPerspective( + int src_type, + const uchar* src_data, + size_t src_step, + int src_width, + int src_height, + uchar* dst_data, + size_t dst_step, + int dst_width, + int dst_height, + const double M[9], + int interpolation, + int border_type, + const double border_value[4]) +{ + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input channel should be 1 + if (CV_MAT_CN(src_type) != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + fcvBorderType fcvBorder; + uint8_t fcvBorderValue = 0; + fcvInterpolationType fcvInterpolation; + + switch (border_type) + { + case cv::BorderTypes::BORDER_CONSTANT: + { + if ((border_value[0] == border_value[1]) && + (border_value[0] == border_value[2]) && + (border_value[0] == border_value[3])) + { + fcvBorder = fcvBorderType::FASTCV_BORDER_CONSTANT; + fcvBorderValue = static_cast(border_value[0]); + break; + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED("Different border value is not supported"); + } + case cv::BorderTypes::BORDER_REPLICATE: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REPLICATE; + break; + } + case cv::BorderTypes::BORDER_TRANSPARENT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_UNDEFINED; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Border type:%s is not supported", borderToString(border_type))); + } + + switch(interpolation) + { + case cv::InterpolationFlags::INTER_NEAREST: + { + fcvInterpolation = FASTCV_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Interpolation type:%s is not supported", + interpolationToString(interpolation))); + } + + int nThreads = cv::getNumThreads(); + int nStripes = nThreads > 1 ? 3*nThreads : 1; + + if(CV_MAT_DEPTH(src_type) == CV_8U) + { + cv::parallel_for_(cv::Range(0, dst_height), + FcvWarpPerspectiveLoop_Invoker(src_data, src_width, src_height, src_step, dst_data, dst_width, dst_height, + dst_step, src_type, M, fcvInterpolation, fcvBorder, fcvBorderValue), nStripes); + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Src type:%s is not supported", cv::typeToString(src_type).c_str())); + + CV_HAL_RETURN(status, hal_warpPerspective); +} + +class FcvPyrLoop_Invoker : public cv::ParallelLoopBody +{ +public: + + FcvPyrLoop_Invoker(cv::Mat src_, int width_, int height_, cv::Mat dst_, int bdr_, int knl_, int stripeHeight_, int nStripes_) : + cv::ParallelLoopBody(), src(src_), width(width_), height(height_), dst(dst_), bdr(bdr_), knl(knl_), stripeHeight(stripeHeight_), nStripes(nStripes_) + { + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + int height_ = stripeHeight * (range.end - range.start); + int width_ = width; + cv::Mat src_; + int n = knl/2; + + if(range.end == nStripes) + height_ += (height - range.end * stripeHeight); + + src_ = cv::Mat(height_ + 2*n, width_ + 2*n, CV_8U); + + if(range.start == 0 && range.end == nStripes) + cv::copyMakeBorder(src(cv::Rect(0, 0, width, height)), src_, n, n, n, n, bdr); + else if(range.start == 0) + cv::copyMakeBorder(src(cv::Rect(0, 0, width_, height_ + n)), src_, n, 0, n, n, bdr); + else if(range.end == nStripes) + cv::copyMakeBorder(src(cv::Rect(0, range.start * stripeHeight - n, width_, height_ + n)), src_, 0, n, n, n, bdr); + else + cv::copyMakeBorder(src(cv::Rect(0, range.start * stripeHeight - n, width_, height_ + 2*n)), src_, 0, 0, n, n, bdr); + + int dstHeight_, dstWidth_, origDstHeight_, origDstWidth_; + dstHeight_ = (height_ + 2*n + 1)/2; + dstWidth_ = (width_ + 2*n + 1)/2; + origDstHeight_ = (height_ + 1)/2; + origDstWidth_ = (width_ + 1)/2; + + cv::Mat dst_padded = cv::Mat(dstHeight_, dstWidth_, CV_8U); + + fcvPyramidLevel_v2 framePyr[2]; + framePyr[0].ptr = NULL; + framePyr[1].ptr = dst_padded.data; + framePyr[1].stride = dstWidth_; + + fcvPyramidCreateu8_v4(src_.data, width_ + 2*n, height_ + 2*n, + width_ + 2*n, 2, FASTCV_PYRAMID_SCALE_HALF, + framePyr, FASTCV_BORDER_UNDEFINED, 0); + + int start_val = stripeHeight * range.start; + cv::Mat dst_temp1 = dst_padded(cv::Rect(n/2, n/2, origDstWidth_, origDstHeight_)); + cv::Mat dst_temp2 = dst(cv::Rect(0, start_val/2, origDstWidth_, origDstHeight_)); + dst_temp1.copyTo(dst_temp2); + } + +private: + cv::Mat src; + const int width; + const int height; + cv::Mat dst; + const int bdr; + const int knl; + const int stripeHeight; + const int nStripes; + + FcvPyrLoop_Invoker(const FcvPyrLoop_Invoker &); // = delete; + const FcvPyrLoop_Invoker& operator= (const FcvPyrLoop_Invoker &); // = delete; +}; + +int fastcv_hal_pyrdown( + const uchar* src_data, + size_t src_step, + int src_width, + int src_height, + uchar* dst_data, + size_t dst_step, + int dst_width, + int dst_height, + int depth, + int cn, + int border_type) +{ + if(depth != CV_8U || cn!= 1) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + + int dstW = (src_width & 1) == 1 ? ((src_width + 1) >> 1) : ((src_width) >> 1); + int dstH = (src_height & 1) == 1 ? ((src_height + 1) >> 1) : ((src_height) >> 1); + + if((dstW > dst_width) || (dstH > dst_height)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("dst size needs to be atleast half of the src size"); + } + + INITIALIZATION_CHECK; + + fcvBorderType bdr; + uint8_t bVal = 0; + int nThreads = cv::getNumThreads(); + if(nThreads <= 1) + { + switch(border_type) + { + case cv::BORDER_REPLICATE: + bdr = FASTCV_BORDER_REPLICATE; + break; + case cv::BORDER_REFLECT: + bdr = FASTCV_BORDER_REFLECT; + break; + case cv::BORDER_REFLECT101: // cv::BORDER_REFLECT_101, BORDER_DEFAULT + bdr = FASTCV_BORDER_REFLECT_V2; + break; + default: + CV_HAL_RETURN_NOT_IMPLEMENTED("border type not supported"); + } + + fcvPyramidLevel_v2 frame1Pyr[2]; + frame1Pyr[0].ptr = NULL; + frame1Pyr[1].ptr = dst_data; + frame1Pyr[1].stride = dst_step; + + fcvStatus status = fcvPyramidCreateu8_v4(src_data, src_width, src_height, + src_step, 2, FASTCV_PYRAMID_SCALE_HALF, + frame1Pyr, bdr, bVal); + + CV_HAL_RETURN(status,hal_pyrdown); + } + else + { + cv::Mat src = cv::Mat(src_height, src_width, CV_8UC1, (void*)src_data, src_step); + cv::Mat dst = cv::Mat(dst_height, dst_width, CV_8UC1, (void*)dst_data, dst_step); + + int nStripes, stripeHeight = nThreads * 10; + + if(src.rows/stripeHeight == 0) + nStripes = 1; + else + nStripes = (src.rows/stripeHeight); + + cv::parallel_for_(cv::Range(0, nStripes), + FcvPyrLoop_Invoker(src, src_width, src_height, dst, border_type, 5, stripeHeight, nStripes), nStripes); + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_pyrdown); + } +} + +int fastcv_hal_cvtBGRtoHSV( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isFullRange, + bool isHSV) +{ + if(width * height > 640 * 480) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("input size not supported"); + } + if(scn != 3 || depth != CV_8U) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + else if(!isHSV || !isFullRange) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("Full range HSV supported"); + } + else if(!swapBlue) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("current color code not supported, expected swapped blue channel"); + } + else if (src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("In-place not supported"); + } + else if((src_step < (size_t)width*3) || + (dst_step < (size_t)width*3)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("unexpected stride values"); + } + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int sHeight = range.end - range.start; + const uchar* yS = src_data + static_cast(range.start) * src_step; + uchar* yD = dst_data + static_cast(range.start) * dst_step; + fcvColorRGB888ToHSV888u8(yS, width, sHeight, src_step, yD, dst_step); + }, nStripes); + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_BGRtoHSV); +} + +int fastcv_hal_cvtBGRtoYUVApprox( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isCbCr) +{ + if(scn != 3 || depth != CV_8U) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + else if(!isCbCr) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("CbCr supported"); + } + else if(!swapBlue) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("expected swapped blue channel"); + } + else if (src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("In-place not supported"); + } + else if((src_step < (size_t)width*3) || + (dst_step < (size_t)width*3)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("unexpected stride values"); + } + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int sHeight = range.end - range.start; + const uchar* yS = src_data + static_cast(range.start) * src_step; + uchar* yD = dst_data + static_cast(range.start) * dst_step; + fcvColorRGB888toYCrCbu8_v3(yS, width, sHeight, src_step, yD, dst_step); + }, nStripes); + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_BGRtoYUVApprox); +} + +int fastcv_hal_canny( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int cn, + double lowThreshold, + double highThreshold, + int ksize, + bool L2gradient) +{ + int numThreads = cv::getNumThreads(); + + if(numThreads!=1) + CV_HAL_RETURN_NOT_IMPLEMENTED("API performs optimally in single-threaded mode"); + + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channel input is not supported"); + + if (lowThreshold > highThreshold) + CV_HAL_RETURN_NOT_IMPLEMENTED("lowThreshold is greater then highThreshold"); + + const double epsilon = 1e-9; + + if (std::abs(lowThreshold - std::round(lowThreshold)) > epsilon || std::abs(highThreshold - std::round(highThreshold)) > epsilon) + CV_HAL_RETURN_NOT_IMPLEMENTED("threshold with decimal values not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status; + fcvNormType norm; + + if (L2gradient == 1) + norm = fcvNormType::FASTCV_NORM_L2; + else + norm = fcvNormType::FASTCV_NORM_L1; + + if ((ksize == 3) && (width > 2) && (height > 2) && (src_step >= (size_t)width) && (dst_step >= (size_t)width)) + { + int16_t* gx = (int16_t*)fcvMemAlloc(width * height * sizeof(int16_t), 16); + int16_t* gy = (int16_t*)fcvMemAlloc(width * height * sizeof(int16_t), 16); + uint32_t gstride = 2 * width; + status = fcvFilterCannyu8(src_data, width, height, src_step, ksize, static_cast(std::round(lowThreshold)), static_cast(std::round(highThreshold)), norm, dst_data, dst_step, gx, gy, gstride); + fcvMemFree(gx); + fcvMemFree(gy); + } + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d is not supported", ksize)); + } + CV_HAL_RETURN(status, hal_canny); +} \ No newline at end of file diff --git a/3rdparty/fastcv/src/fastcv_hal_utils.cpp b/3rdparty/fastcv/src/fastcv_hal_utils.cpp new file mode 100644 index 0000000000..23fc6e2439 --- /dev/null +++ b/3rdparty/fastcv/src/fastcv_hal_utils.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#include "fastcv_hal_utils.hpp" + +const char* getFastCVErrorString(int status) +{ + switch(status) + { + case FASTCV_SUCCESS: return "Successful"; + case FASTCV_EFAIL: return "General failure"; + case FASTCV_EUNALIGNPARAM: return "Unaligned pointer parameter"; + case FASTCV_EBADPARAM: return "Bad parameters"; + case FASTCV_EINVALSTATE: return "Called at invalid state"; + case FASTCV_ENORES: return "Insufficient resources, memory, thread, etc"; + case FASTCV_EUNSUPPORTED: return "Unsupported feature"; + case FASTCV_EHWQDSP: return "Hardware QDSP failed to respond"; + case FASTCV_EHWGPU: return "Hardware GPU failed to respond"; + default: return "Unknown FastCV Error"; + } +} + +const char* borderToString(int border) +{ + switch (border) + { + case 0: return "BORDER_CONSTANT"; + case 1: return "BORDER_REPLICATE"; + case 2: return "BORDER_REFLECT"; + case 3: return "BORDER_WRAP"; + case 4: return "BORDER_REFLECT_101"; + case 5: return "BORDER_TRANSPARENT"; + default: return "Unknown border type"; + } +} + +const char* interpolationToString(int interpolation) +{ + switch (interpolation) + { + case 0: return "INTER_NEAREST"; + case 1: return "INTER_LINEAR"; + case 2: return "INTER_CUBIC"; + case 3: return "INTER_AREA"; + case 4: return "INTER_LANCZOS4"; + case 5: return "INTER_LINEAR_EXACT"; + case 6: return "INTER_NEAREST_EXACT"; + case 7: return "INTER_MAX"; + case 8: return "WARP_FILL_OUTLIERS"; + case 16: return "WARP_INVERSE_MAP"; + case 32: return "WARP_RELATIVE_MAP"; + default: return "Unknown interpolation type"; + } +} \ No newline at end of file diff --git a/3rdparty/hal_rvv/hal_rvv.hpp b/3rdparty/hal_rvv/hal_rvv.hpp index 4765a15518..086f248a24 100644 --- a/3rdparty/hal_rvv/hal_rvv.hpp +++ b/3rdparty/hal_rvv/hal_rvv.hpp @@ -21,6 +21,7 @@ #if defined(__riscv_v) && __riscv_v == 1000000 #include "hal_rvv_1p0/merge.hpp" // core +#include "hal_rvv_1p0/mean.hpp" // core #endif #endif diff --git a/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp b/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp new file mode 100644 index 0000000000..4a9ffec500 --- /dev/null +++ b/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp @@ -0,0 +1,228 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#ifndef OPENCV_HAL_RVV_MEANSTDDEV_HPP_INCLUDED +#define OPENCV_HAL_RVV_MEANSTDDEV_HPP_INCLUDED + +#include + +namespace cv { namespace cv_hal_rvv { + +#undef cv_hal_meanStdDev +#define cv_hal_meanStdDev cv::cv_hal_rvv::meanStdDev + +inline int meanStdDev_8UC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step); +inline int meanStdDev_8UC4(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step); +inline int meanStdDev_32FC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step); + +inline int meanStdDev(const uchar* src_data, size_t src_step, int width, int height, + int src_type, double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + switch (src_type) + { + case CV_8UC1: + return meanStdDev_8UC1(src_data, src_step, width, height, mean_val, stddev_val, mask, mask_step); + case CV_8UC4: + return meanStdDev_8UC4(src_data, src_step, width, height, mean_val, stddev_val, mask, mask_step); + case CV_32FC1: + return meanStdDev_32FC1(src_data, src_step, width, height, mean_val, stddev_val, mask, mask_step); + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } +} + +inline int meanStdDev_8UC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + int nz = 0; + int vlmax = __riscv_vsetvlmax_e64m8(); + vuint64m8_t vec_sum = __riscv_vmv_v_x_u64m8(0, vlmax); + vuint64m8_t vec_sqsum = __riscv_vmv_v_x_u64m8(0, vlmax); + if (mask) { + for (int i = 0; i < height; ++i) { + const uchar* src_row = src_data + i * src_step; + const uchar* mask_row = mask + i * mask_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e8m1(width - j); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vmask_u8 = __riscv_vle8_v_u8m1(mask_row+j, vl); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + auto vmask = __riscv_vmseq_vx_u8m1_b8(vmask_u8, 1, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tumu(vmask, vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tumu(vmask, vec_sqsum, vec_pixel, vec_pixel, vl); + nz += __riscv_vcpop_m_b8(vmask, vl); + } + } + } else { + for (int i = 0; i < height; i++) { + const uchar* src_row = src_data + i * src_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e8m1(width - j); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tu(vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tu(vec_sqsum, vec_pixel, vec_pixel, vl); + } + } + nz = height * width; + } + if (nz == 0) { + if (mean_val) *mean_val = 0.0; + if (stddev_val) *stddev_val = 0.0; + return CV_HAL_ERROR_OK; + } + auto zero = __riscv_vmv_s_x_u64m1(0, vlmax); + auto vec_red = __riscv_vmv_v_x_u64m1(0, vlmax); + auto vec_reddev = __riscv_vmv_v_x_u64m1(0, vlmax); + vec_red = __riscv_vredsum(vec_sum, zero, vlmax); + vec_reddev = __riscv_vredsum(vec_sqsum, zero, vlmax); + double sum = __riscv_vmv_x(vec_red); + double mean = sum / nz; + if (mean_val) { + *mean_val = mean; + } + if (stddev_val) { + double sqsum = __riscv_vmv_x(vec_reddev); + double variance = std::max((sqsum / nz) - (mean * mean), 0.0); + double stddev = std::sqrt(variance); + *stddev_val = stddev; + } + return CV_HAL_ERROR_OK; +} + +inline int meanStdDev_8UC4(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + int nz = 0; + int vlmax = __riscv_vsetvlmax_e64m8(); + vuint64m8_t vec_sum = __riscv_vmv_v_x_u64m8(0, vlmax); + vuint64m8_t vec_sqsum = __riscv_vmv_v_x_u64m8(0, vlmax); + if (mask) { + for (int i = 0; i < height; ++i) { + const uchar* src_row = src_data + i * src_step; + const uchar* mask_row = mask + i * mask_step; + int j = 0, jm = 0, vl, vlm; + for ( ; j < width*4; j += vl, jm += vlm) { + vl = __riscv_vsetvl_e8m1(width*4 - j); + vlm = __riscv_vsetvl_e8mf4(width - jm); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vmask_u8mf4 = __riscv_vle8_v_u8mf4(mask_row + jm, vlm); + auto vmask_u32 = __riscv_vzext_vf4(vmask_u8mf4, vlm); + // 0 -> 0000; 1 -> 1111 + vmask_u32 = __riscv_vmul(vmask_u32, 0b00000001000000010000000100000001, vlm); + auto vmask_u8 = __riscv_vreinterpret_u8m1(vmask_u32); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + auto vmask = __riscv_vmseq_vx_u8m1_b8(vmask_u8, 1, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tumu(vmask, vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tumu(vmask, vec_sqsum, vec_pixel, vec_pixel, vl); + nz += __riscv_vcpop_m_b8(vmask, vl); + } + } + nz /= 4; + } else { + for (int i = 0; i < height; i++) { + const uchar* src_row = src_data + i * src_step; + int j = 0, vl; + for ( ; j < width*4; j += vl) { + vl = __riscv_vsetvl_e8m1(width*4 - j); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tu(vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tu(vec_sqsum, vec_pixel, vec_pixel, vl); + } + } + nz = height * width; + } + if (nz == 0) { + if (mean_val) *mean_val = 0.0; + if (stddev_val) *stddev_val = 0.0; + return CV_HAL_ERROR_OK; + } + uint64_t s[256], sq[256], sum[4] = {0}, sqsum[4] = {0}; + __riscv_vse64(s, vec_sum, vlmax); + __riscv_vse64(sq, vec_sqsum, vlmax); + for (int i = 0; i < vlmax; ++i) + { + sum[i % 4] += s[i]; + sqsum[i % 4] += sq[i]; + } + if (mean_val) { + mean_val[0] = (double)sum[0] / nz; + mean_val[1] = (double)sum[1] / nz; + mean_val[2] = (double)sum[2] / nz; + mean_val[3] = (double)sum[3] / nz; + } + if (stddev_val) { + stddev_val[0] = std::sqrt(std::max(((double)sqsum[0] / nz) - (mean_val[0] * mean_val[0]), 0.0)); + stddev_val[1] = std::sqrt(std::max(((double)sqsum[1] / nz) - (mean_val[1] * mean_val[1]), 0.0)); + stddev_val[2] = std::sqrt(std::max(((double)sqsum[2] / nz) - (mean_val[2] * mean_val[2]), 0.0)); + stddev_val[3] = std::sqrt(std::max(((double)sqsum[3] / nz) - (mean_val[3] * mean_val[3]), 0.0)); + } + return CV_HAL_ERROR_OK; +} + +inline int meanStdDev_32FC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + int nz = 0; + int vlmax = __riscv_vsetvlmax_e64m4(); + vfloat64m4_t vec_sum = __riscv_vfmv_v_f_f64m4(0, vlmax); + vfloat64m4_t vec_sqsum = __riscv_vfmv_v_f_f64m4(0, vlmax); + src_step /= sizeof(float); + if (mask) { + for (int i = 0; i < height; ++i) { + const float* src_row0 = reinterpret_cast(src_data) + i * src_step; + const uchar* mask_row = mask + i * mask_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e32m2(width - j); + auto vec_pixel = __riscv_vle32_v_f32m2(src_row0 + j, vl); + auto vmask_u8 = __riscv_vle8_v_u8mf2(mask_row + j, vl); + auto vmask_u32 = __riscv_vzext_vf4(vmask_u8, vl); + auto vmask = __riscv_vmseq_vx_u32m2_b16(vmask_u32, 1, vl); + vec_sum = __riscv_vfwadd_wv_f64m4_tumu(vmask, vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vfwmacc_vv_f64m4_tumu(vmask, vec_sqsum, vec_pixel, vec_pixel, vl); + nz += __riscv_vcpop_m_b16(vmask, vl); + } + } + } else { + for (int i = 0; i < height; i++) { + const float* src_row0 = reinterpret_cast(src_data) + i * src_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e32m2(width - j); + auto vec_pixel = __riscv_vle32_v_f32m2(src_row0 + j, vl); + vec_sum = __riscv_vfwadd_wv_f64m4_tu(vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vfwmacc_vv_f64m4_tu(vec_sqsum, vec_pixel, vec_pixel, vl); + } + } + nz = height * width; + } + if (nz == 0) { + if (mean_val) *mean_val = 0.0; + if (stddev_val) *stddev_val = 0.0; + return CV_HAL_ERROR_OK; + } + auto zero = __riscv_vfmv_v_f_f64m1(0, vlmax); + auto vec_red = __riscv_vfmv_v_f_f64m1(0, vlmax); + auto vec_reddev = __riscv_vfmv_v_f_f64m1(0, vlmax); + vec_red = __riscv_vfredusum(vec_sum, zero, vlmax); + vec_reddev = __riscv_vfredusum(vec_sqsum, zero, vlmax); + double sum = __riscv_vfmv_f(vec_red); + double mean = sum / nz; + if (mean_val) { + *mean_val = mean; + } + if (stddev_val) { + double sqsum = __riscv_vfmv_f(vec_reddev); + double variance = std::max((sqsum / nz) - (mean * mean), 0.0); + double stddev = std::sqrt(variance); + *stddev_val = stddev; + } + return CV_HAL_ERROR_OK; +} + +}} + +#endif diff --git a/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp b/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp index 97c71ad0f3..353885b709 100644 --- a/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp +++ b/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp @@ -20,9 +20,9 @@ namespace cv { namespace cv_hal_rvv { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { +inline int merge8u(const uchar** src, uchar* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; - int i = 0, j; + int i = 0; int vl = __riscv_vsetvlmax_e8m1(); if( k == 1 ) { @@ -30,7 +30,7 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { for( ; i <= len - vl; i += vl) { auto a = __riscv_vle8_v_u8m1(src0 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*2, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -45,8 +45,8 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { { auto a = __riscv_vle8_v_u8m1(src0 + i, vl); auto b = __riscv_vle8_v_u8m1(src1 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*2, a, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*2, b, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*cn, b, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -65,9 +65,9 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { auto a = __riscv_vle8_v_u8m1(src0 + i, vl); auto b = __riscv_vle8_v_u8m1(src1 + i, vl); auto c = __riscv_vle8_v_u8m1(src2 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*3, a, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*3, b, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*3, c, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*cn, b, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*cn, c, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -88,10 +88,10 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { auto b = __riscv_vle8_v_u8m1(src1 + i, vl); auto c = __riscv_vle8_v_u8m1(src2 + i, vl); auto d = __riscv_vle8_v_u8m1(src3 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*4, a, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*4, b, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*4, c, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 3, sizeof(uchar)*4, d, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*cn, b, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*cn, c, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 3, sizeof(uchar)*cn, d, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -110,10 +110,27 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { for( ; k < cn; k += 4 ) { const uchar *src0 = src[k], *src1 = src[k+1], *src2 = src[k+2], *src3 = src[k+3]; - for( i = 0, j = k; i < len; i++, j += cn ) + i = 0; + for( ; i <= len - vl; i += vl) { - dst[j] = src0[i]; dst[j+1] = src1[i]; - dst[j+2] = src2[i]; dst[j+3] = src3[i]; + auto a = __riscv_vle8_v_u8m1(src0 + i, vl); + auto b = __riscv_vle8_v_u8m1(src1 + i, vl); + auto c = __riscv_vle8_v_u8m1(src2 + i, vl); + auto d = __riscv_vle8_v_u8m1(src3 + i, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn + 1, sizeof(uchar)*cn, b, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn + 2, sizeof(uchar)*cn, c, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn + 3, sizeof(uchar)*cn, d, vl); + } + #if defined(__clang__) + #pragma clang loop vectorize(disable) + #endif + for( ; i < len; i++ ) + { + dst[k+i*cn] = src0[i]; + dst[k+i*cn+1] = src1[i]; + dst[k+i*cn+2] = src2[i]; + dst[k+i*cn+3] = src3[i]; } } return CV_HAL_ERROR_OK; @@ -122,9 +139,9 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { +inline int merge16u(const ushort** src, ushort* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; - int i = 0, j; + int i = 0; int vl = __riscv_vsetvlmax_e16m1(); if( k == 1 ) { @@ -132,7 +149,7 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { for( ; i <= len - vl; i += vl) { auto a = __riscv_vle16_v_u16m1(src0 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*2, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -147,8 +164,8 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { { auto a = __riscv_vle16_v_u16m1(src0 + i, vl); auto b = __riscv_vle16_v_u16m1(src1 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*2, a, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*2, b, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*cn, b, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -167,9 +184,9 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { auto a = __riscv_vle16_v_u16m1(src0 + i, vl); auto b = __riscv_vle16_v_u16m1(src1 + i, vl); auto c = __riscv_vle16_v_u16m1(src2 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*3, a, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*3, b, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*3, c, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*cn, b, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*cn, c, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -190,10 +207,10 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { auto b = __riscv_vle16_v_u16m1(src1 + i, vl); auto c = __riscv_vle16_v_u16m1(src2 + i, vl); auto d = __riscv_vle16_v_u16m1(src3 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*4, a, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*4, b, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*4, c, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 3, sizeof(ushort)*4, d, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*cn, b, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*cn, c, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 3, sizeof(ushort)*cn, d, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -212,10 +229,24 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { for( ; k < cn; k += 4 ) { const uint16_t *src0 = src[k], *src1 = src[k+1], *src2 = src[k+2], *src3 = src[k+3]; - for( i = 0, j = k; i < len; i++, j += cn ) + i = 0; + for( ; i <= len - vl; i += vl) { - dst[j] = src0[i]; dst[j+1] = src1[i]; - dst[j+2] = src2[i]; dst[j+3] = src3[i]; + auto a = __riscv_vle16_v_u16m1(src0 + i, vl); + auto b = __riscv_vle16_v_u16m1(src1 + i, vl); + auto c = __riscv_vle16_v_u16m1(src2 + i, vl); + auto d = __riscv_vle16_v_u16m1(src3 + i, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn + 1, sizeof(ushort)*cn, b, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn + 2, sizeof(ushort)*cn, c, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn + 3, sizeof(ushort)*cn, d, vl); + } + for( ; i < len; i++ ) + { + dst[k+i*cn] = src0[i]; + dst[k+i*cn+1] = src1[i]; + dst[k+i*cn+2] = src2[i]; + dst[k+i*cn+3] = src3[i]; } } return CV_HAL_ERROR_OK; @@ -224,7 +255,7 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge32s(const int** src, int* dst, int len, int cn ) { +inline int merge32s(const int** src, int* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; int i, j; if( k == 1 ) @@ -294,7 +325,7 @@ static int merge32s(const int** src, int* dst, int len, int cn ) { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge64s(const int64** src, int64* dst, int len, int cn ) { +inline int merge64s(const int64** src, int64* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; int i, j; if( k == 1 ) diff --git a/3rdparty/kleidicv/CMakeLists.txt b/3rdparty/kleidicv/CMakeLists.txt index a236483c27..9273deb3c9 100644 --- a/3rdparty/kleidicv/CMakeLists.txt +++ b/3rdparty/kleidicv/CMakeLists.txt @@ -3,5 +3,9 @@ project(kleidicv_hal) if(HAVE_KLEIDICV) option(KLEIDICV_ENABLE_SME2 "" OFF) # not compatible with some CLang versions in NDK include("${KLEIDICV_SOURCE_PATH}/adapters/opencv/CMakeLists.txt") + # HACK to suppress adapters/opencv/kleidicv_hal.cpp:343:12: warning: unused function 'from_opencv' [-Wunused-function] + target_compile_options( kleidicv_hal PRIVATE + $ + "-Wno-old-style-cast" "-Wno-unused-function" + ) endif() - diff --git a/3rdparty/kleidicv/kleidicv.cmake b/3rdparty/kleidicv/kleidicv.cmake index 57d3843ef0..2ea071dade 100644 --- a/3rdparty/kleidicv/kleidicv.cmake +++ b/3rdparty/kleidicv/kleidicv.cmake @@ -1,8 +1,8 @@ function(download_kleidicv root_var) set(${root_var} "" PARENT_SCOPE) - ocv_update(KLEIDICV_SRC_COMMIT "0.2.0") - ocv_update(KLEIDICV_SRC_HASH "dabe522e8f55ac342d07a787391dab80") + ocv_update(KLEIDICV_SRC_COMMIT "0.3.0") + ocv_update(KLEIDICV_SRC_HASH "51a77b0185c2bac2a968a2163869b1ed") set(THE_ROOT "${OpenCV_BINARY_DIR}/3rdparty/kleidicv") ocv_download(FILENAME "kleidicv-${KLEIDICV_SRC_COMMIT}.tar.gz" diff --git a/CMakeLists.txt b/CMakeLists.txt index 43147c2e8e..c7651086ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,8 @@ OCV_OPTION(WITH_NDSRVP "Use Andes RVP extension" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF RISCV) OCV_OPTION(WITH_HAL_RVV "Use HAL RVV optimizations" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF RISCV) +OCV_OPTION(WITH_FASTCV "Use Qualcomm FastCV acceleration library for ARM platform" OFF + VISIBLE_IF ((ARM OR AARCH64) AND (ANDROID OR (UNIX AND NOT APPLE AND NOT IOS AND NOT XROS)))) OCV_OPTION(WITH_CPUFEATURES "Use cpufeatures Android library" ON VISIBLE_IF ANDROID VERIFY HAVE_CPUFEATURES) @@ -242,6 +244,9 @@ OCV_OPTION(WITH_OPENJPEG "Include JPEG2K support (OpenJPEG)" ON OCV_OPTION(WITH_JPEG "Include JPEG support" ON VISIBLE_IF TRUE VERIFY HAVE_JPEG) +OCV_OPTION(WITH_JPEGXL "Include JPEG XL support" OFF + VISIBLE_IF TRUE + VERIFY HAVE_JPEGXL) OCV_OPTION(WITH_WEBP "Include WebP support" ON VISIBLE_IF NOT WINRT VERIFY HAVE_WEBP) @@ -365,6 +370,9 @@ OCV_OPTION(WITH_ITT "Include Intel ITT support" ON OCV_OPTION(WITH_PROTOBUF "Enable libprotobuf" ON VISIBLE_IF TRUE VERIFY HAVE_PROTOBUF) +OCV_OPTION(WITH_IMGCODEC_GIF "Include GIF support" OFF + VISIBLE_IF TRUE + VERIFY HAVE_IMGCODEC_GIF) OCV_OPTION(WITH_IMGCODEC_HDR "Include HDR support" ON VISIBLE_IF TRUE VERIFY HAVE_IMGCODEC_HDR) @@ -697,6 +705,13 @@ elseif(WIN32) endif() endif() +if(DEFINED OPENCV_ALGO_HINT_DEFAULT) + if(NOT OPENCV_ALGO_HINT_DEFAULT STREQUAL "ALGO_HINT_ACCURATE" AND + NOT OPENCV_ALGO_HINT_DEFAULT STREQUAL "ALGO_HINT_APPROX") + message(FATAL_ERROR "OPENCV_ALGO_HINT_DEFAULT should be one of ALGO_HINT_ACCURATE or ALGO_HINT_APPROX.") + endif() +endif() + include(cmake/OpenCVPCHSupport.cmake) include(cmake/OpenCVModule.cmake) @@ -861,6 +876,13 @@ if(NOT DEFINED OpenCV_HAL) set(OpenCV_HAL "OpenCV_HAL") endif() +if(HAVE_FASTCV) + ocv_debug_message(STATUS "Enable FastCV acceleration") + if(NOT ";${OpenCV_HAL};" MATCHES ";fastcv;") + set(OpenCV_HAL "fastcv;${OpenCV_HAL}") + endif() +endif() + if(HAVE_KLEIDICV) ocv_debug_message(STATUS "Enable KleidiCV acceleration") if(NOT ";${OpenCV_HAL};" MATCHES ";kleidicv;") @@ -898,6 +920,14 @@ foreach(hal ${OpenCV_HAL}) else() message(STATUS "Carotene: NEON is not available, disabling carotene...") endif() + elseif(hal STREQUAL "fastcv") + if((ARM OR AARCH64) AND (ANDROID OR (UNIX AND NOT APPLE AND NOT IOS AND NOT XROS))) + add_subdirectory(3rdparty/fastcv) + ocv_hal_register(FASTCV_HAL_LIBRARIES FASTCV_HAL_HEADERS FASTCV_HAL_INCLUDE_DIRS) + list(APPEND OpenCV_USED_HAL "fastcv (ver ${FASTCV_HAL_VERSION})") + else() + message(STATUS "FastCV: fastcv is not available, disabling fastcv...") + endif() elseif(hal STREQUAL "kleidicv") add_subdirectory(3rdparty/kleidicv) ocv_hal_register(KLEIDICV_HAL_LIBRARIES KLEIDICV_HAL_HEADERS KLEIDICV_HAL_INCLUDE_DIRS) @@ -1439,6 +1469,10 @@ if(WITH_TIFF OR HAVE_TIFF) status(" TIFF:" TIFF_FOUND THEN "${TIFF_LIBRARY} (ver ${TIFF_VERSION} / ${TIFF_VERSION_STRING})" ELSE "build (ver ${TIFF_VERSION} - ${TIFF_VERSION_STRING})") endif() +if(WITH_JPEGXL OR HAVE_JPEGXL) + status(" JPEG XL:" JPEGXL_FOUND THEN "${JPEGXL_LIBRARY} (ver ${JPEGXL_VERSION})" ELSE "NO") +endif() + if(HAVE_OPENJPEG) status(" JPEG 2000:" OpenJPEG_FOUND THEN "OpenJPEG (ver ${OPENJPEG_VERSION})" @@ -1466,6 +1500,10 @@ if(WITH_GDCM OR HAVE_GDCM) status(" GDCM:" HAVE_GDCM THEN "YES (${GDCM_VERSION})" ELSE "NO") endif() +if(WITH_IMGCODEC_GIF OR DEFINED HAVE_IMGCODEC_GIF) + status(" GIF:" HAVE_IMGCODEC_GIF THEN "YES" ELSE "NO") +endif() + if(WITH_IMGCODEC_HDR OR DEFINED HAVE_IMGCODEC_HDR) status(" HDR:" HAVE_IMGCODEC_HDR THEN "YES" ELSE "NO") endif() @@ -1712,6 +1750,10 @@ if(WITH_EIGEN OR HAVE_EIGEN) status(" Eigen:" HAVE_EIGEN THEN "YES (ver ${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION})" ELSE NO) endif() +if(WITH_FASTCV OR HAVE_FASTCV) + status(" FastCV:" HAVE_FASTCV THEN "YES (${FASTCV_LIBRARY})" ELSE "NO") +endif() + status(" Custom HAL:" OpenCV_USED_HAL THEN "YES (${OpenCV_USED_HAL})" ELSE "NO") foreach(s ${CUSTOM_STATUS}) diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index c77f06b3b6..cb830f7a5d 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -191,11 +191,6 @@ if(CV_GCC OR CV_CLANG OR CV_ICX) endif() add_extra_compiler_option(-fdiagnostics-show-option) - # The -Wno-long-long is required in 64bit systems when including system headers. - if(X86_64) - add_extra_compiler_option(-Wno-long-long) - endif() - # We need pthread's, unless we have explicitly disabled multi-thread execution. if(NOT OPENCV_DISABLE_THREAD_SUPPORT AND ( @@ -409,6 +404,14 @@ if(NOT OPENCV_SKIP_LINK_NO_UNDEFINED) endif() endif() +# For 16k pages support with NDK prior 27 +# Details: https://developer.android.com/guide/practices/page-sizes?hl=en +if(ANDROID AND ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES AND (ANDROID_ABI STREQUAL arm64-v8a OR ANDROID_ABI STREQUAL x86_64)) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,max-page-size=16384") +endif() + # combine all "extra" options if(NOT OPENCV_SKIP_EXTRA_COMPILER_FLAGS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OPENCV_EXTRA_FLAGS} ${OPENCV_EXTRA_C_FLAGS}") diff --git a/cmake/OpenCVFindJPEGXL.cmake b/cmake/OpenCVFindJPEGXL.cmake new file mode 100644 index 0000000000..6eb5abb9c4 --- /dev/null +++ b/cmake/OpenCVFindJPEGXL.cmake @@ -0,0 +1,148 @@ +# The script is taken from https://webkit.googlesource.com/WebKit/+/master/Source/cmake/FindJPEGXL.cmake + +# Copyright (C) 2021 Sony Interactive Entertainment Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +#[=======================================================================[.rst: +FindJPEGXL +--------- +Find JPEGXL headers and libraries. +Imported Targets +^^^^^^^^^^^^^^^^ +``JPEGXL::jxl`` + The JPEGXL library, if found. +Result Variables +^^^^^^^^^^^^^^^^ +This will define the following variables in your project: +``JPEGXL_FOUND`` + true if (the requested version of) JPEGXL is available. +``JPEGXL_VERSION`` + the version of JPEGXL. +``JPEGXL_LIBRARIES`` + the libraries to link against to use JPEGXL. +``JPEGXL_INCLUDE_DIRS`` + where to find the JPEGXL headers. +``JPEGXL_COMPILE_OPTIONS`` + this should be passed to target_compile_options(), if the + target is not used for linking +#]=======================================================================] + +if(NOT OPENCV_SKIP_JPEGXL_FIND_PACKAGE) +find_package(PkgConfig QUIET) +if (PkgConfig_FOUND) + pkg_check_modules(PC_JPEGXL QUIET jxl) + set(JPEGXL_COMPILE_OPTIONS ${PC_JPEGXL_CFLAGS_OTHER}) + set(JPEGXL_VERSION ${PC_JPEGXL_VERSION}) +endif () +find_path(JPEGXL_INCLUDE_DIR + NAMES jxl/decode.h + HINTS ${PC_JPEGXL_INCLUDEDIR} ${PC_JPEGXL_INCLUDE_DIRS} ${JPEGXL_INCLUDE_DIR} +) +find_library(JPEGXL_LIBRARY + NAMES ${JPEGXL_NAMES} jxl + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +find_library(JPEGXL_CMS_LIBRARY + NAMES ${JPEGXL_NAMES} jxl_cms + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_CMS_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_CMS_LIBRARY}) +endif () +find_library(JPEGXL_CMS2_LIBRARY + NAMES ${JPEGXL_NAMES} lcms2 + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_CMS2_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_CMS2_LIBRARY}) +endif () +find_library(JPEGXL_THREADS_LIBRARY + NAMES ${JPEGXL_NAMES} jxl_threads + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_THREADS_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_THREADS_LIBRARY}) +endif () +find_library(JPEGXL_BROTLICOMMON_LIBRARY + NAMES ${JPEGXL_NAMES} brotlicommon + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +find_library(JPEGXL_BROTLIDEC_LIBRARY + NAMES ${JPEGXL_NAMES} brotlidec + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_BROTLIDEC_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLIDEC_LIBRARY}) +endif () +find_library(JPEGXL_BROTLIENC_LIBRARY + NAMES ${JPEGXL_NAMES} brotlienc + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_BROTLIENC_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLIENC_LIBRARY}) +endif () +if (JPEGXL_LIBRARY AND JPEGXL_BROTLICOMMON_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLICOMMON_LIBRARY}) +endif () +find_library(JPEGXL_HWY_LIBRARY + NAMES ${JPEGXL_NAMES} hwy + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_HWY_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_HWY_LIBRARY}) +endif () +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(JPEGXL + FOUND_VAR JPEGXL_FOUND + REQUIRED_VARS JPEGXL_LIBRARY JPEGXL_INCLUDE_DIR + VERSION_VAR JPEGXL_VERSION +) +if (NOT EXISTS "${JPEGXL_INCLUDE_DIR}/jxl/version.h") + # the library version older 0.6 is not supported (no version.h file there) + set(JPEGXL_FOUND FALSE CACHE BOOL "libjxl found" FORCE) + message(STATUS "Ignored incompatible version of libjxl") +endif () + +if (JPEGXL_LIBRARY AND NOT TARGET JPEGXL::jxl) + add_library(JPEGXL::jxl UNKNOWN IMPORTED GLOBAL) + set_target_properties(JPEGXL::jxl PROPERTIES + IMPORTED_LOCATION "${JPEGXL_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${JPEGXL_COMPILE_OPTIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${JPEGXL_INCLUDE_DIR}" + ) +endif () +mark_as_advanced(JPEGXL_INCLUDE_DIR JPEGXL_LIBRARY) +if (JPEGXL_FOUND) + set(JPEGXL_LIBRARIES ${JPEGXL_LIBRARY}) + set(JPEGXL_INCLUDE_DIRS ${JPEGXL_INCLUDE_DIR}) + if (NOT JPEGXL_VERSION) + file(READ "${JPEGXL_INCLUDE_DIR}/jxl/version.h" VERSION_HEADER_CONTENTS) + string(REGEX MATCH "#define JPEGXL_MAJOR_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS}) + set(JXL_VERSION_MAJOR ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define JPEGXL_MINOR_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS}) + set(JXL_VERSION_MINOR ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define JPEGXL_PATCH_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS}) + set(JXL_VERSION_PATCH ${CMAKE_MATCH_1}) + set(JPEGXL_VERSION "${JXL_VERSION_MAJOR}.${JXL_VERSION_MINOR}.${JXL_VERSION_PATCH}") + endif() +endif () +endif() diff --git a/cmake/OpenCVFindLibsGrfmt.cmake b/cmake/OpenCVFindLibsGrfmt.cmake index 340753e6fa..5fb60c7fae 100644 --- a/cmake/OpenCVFindLibsGrfmt.cmake +++ b/cmake/OpenCVFindLibsGrfmt.cmake @@ -189,19 +189,45 @@ if(WITH_WEBP AND NOT WEBP_FOUND endif() if(NOT WEBP_VERSION AND WEBP_INCLUDE_DIR) - ocv_clear_vars(ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION) - if(EXISTS "${WEBP_INCLUDE_DIR}/enc/vp8enci.h") - ocv_parse_header("${WEBP_INCLUDE_DIR}/enc/vp8enci.h" WEBP_VERSION_LINES ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION) - set(WEBP_VERSION "${ENC_MAJ_VERSION}.${ENC_MIN_VERSION}.${ENC_REV_VERSION}") - elseif(EXISTS "${WEBP_INCLUDE_DIR}/webp/encode.h") + if(EXISTS "${WEBP_INCLUDE_DIR}/webp/encode.h") file(STRINGS "${WEBP_INCLUDE_DIR}/webp/encode.h" WEBP_ENCODER_ABI_VERSION REGEX "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)" ) if(WEBP_ENCODER_ABI_VERSION MATCHES "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)") set(WEBP_ENCODER_ABI_VERSION "${CMAKE_MATCH_1}") - set(WEBP_VERSION "encoder: ${WEBP_ENCODER_ABI_VERSION}") else() unset(WEBP_ENCODER_ABI_VERSION) endif() endif() + + if(EXISTS "${WEBP_INCLUDE_DIR}/webp/decode.h") + file(STRINGS "${WEBP_INCLUDE_DIR}/webp/decode.h" WEBP_DECODER_ABI_VERSION REGEX "#define[ \t]+WEBP_DECODER_ABI_VERSION[ \t]+([x0-9a-f]+)" ) + if(WEBP_DECODER_ABI_VERSION MATCHES "#define[ \t]+WEBP_DECODER_ABI_VERSION[ \t]+([x0-9a-f]+)") + set(WEBP_DECODER_ABI_VERSION "${CMAKE_MATCH_1}") + else() + unset(WEBP_DECODER_ABI_VERSION) + endif() + endif() + + if(EXISTS "${WEBP_INCLUDE_DIR}/webp/demux.h") + file(STRINGS "${WEBP_INCLUDE_DIR}/webp/demux.h" WEBP_DEMUX_ABI_VERSION REGEX "#define[ \t]+WEBP_DEMUX_ABI_VERSION[ \t]+([x0-9a-f]+)" ) + if(WEBP_DEMUX_ABI_VERSION MATCHES "#define[ \t]+WEBP_DEMUX_ABI_VERSION[ \t]+([x0-9a-f]+)") + set(WEBP_DEMUX_ABI_VERSION "${CMAKE_MATCH_1}") + else() + unset(WEBP_DEMUX_ABI_VERSION) + endif() + endif() + +set(WEBP_VERSION "decoder: ${WEBP_DECODER_ABI_VERSION}, encoder: ${WEBP_ENCODER_ABI_VERSION}, demux: ${WEBP_DEMUX_ABI_VERSION}") +endif() + +# --- libjxl (optional) --- +if(WITH_JPEGXL) + ocv_clear_vars(HAVE_JPEGXL) + ocv_clear_internal_cache_vars(JPEGXL_INCLUDE_PATHS JPEGXL_LIBRARIES JPEGXL_VERSION) + include("${OpenCV_SOURCE_DIR}/cmake/OpenCVFindJPEGXL.cmake") + if(JPEGXL_FOUND) + set(HAVE_JPEGXL YES) + message(STATUS "Found system JPEG-XL: ver ${JPEGXL_VERSION}") + endif() endif() # --- libopenjp2 (optional, check before libjasper) --- @@ -364,6 +390,11 @@ if(WITH_GDCM) endif() endif() +if(WITH_IMGCODEC_GIF) + set(HAVE_IMGCODEC_GIF ON) +elseif(DEFINED WITH_IMGCODEC_GIF) + set(HAVE_IMGCODEC_GIF OFF) +endif() if(WITH_IMGCODEC_HDR) set(HAVE_IMGCODEC_HDR ON) elseif(DEFINED WITH_IMGCODEC_HDR) diff --git a/cmake/OpenCVFindLibsPerf.cmake b/cmake/OpenCVFindLibsPerf.cmake index 4400a88172..a72d9976f8 100644 --- a/cmake/OpenCVFindLibsPerf.cmake +++ b/cmake/OpenCVFindLibsPerf.cmake @@ -174,3 +174,27 @@ if(WITH_KLEIDICV) set(HAVE_KLEIDICV OFF) endif() endif(WITH_KLEIDICV) + +# --- FastCV --- +if(WITH_FASTCV) + if((EXISTS ${FastCV_INCLUDE_PATH}) AND (EXISTS ${FastCV_LIB_PATH})) + message(STATUS "Use external FastCV ${FastCV_INCLUDE_PATH}, ${FastCV_LIB_PATH}") + set(HAVE_FASTCV TRUE CACHE BOOL "FastCV status") + else() + include("${OpenCV_SOURCE_DIR}/3rdparty/fastcv/fastcv.cmake") + set(FCV_ROOT_DIR "${OpenCV_BINARY_DIR}/3rdparty/fastcv") + download_fastcv(${FCV_ROOT_DIR}) + if(HAVE_FASTCV) + set(FastCV_INCLUDE_PATH "${FCV_ROOT_DIR}/inc" CACHE PATH "FastCV includes directory") + set(FastCV_LIB_PATH "${FCV_ROOT_DIR}/libs" CACHE PATH "FastCV library directory") + ocv_install_3rdparty_licenses(FastCV "${OpenCV_BINARY_DIR}/3rdparty/fastcv/LICENSE") + install(FILES "${FastCV_LIB_PATH}/libfastcvopt.so" + DESTINATION "${OPENCV_LIB_INSTALL_PATH}" COMPONENT "bin") + else() + set(HAVE_FASTCV FALSE CACHE BOOL "FastCV status") + endif() + endif() + if(HAVE_FASTCV) + set(FASTCV_LIBRARY "${FastCV_LIB_PATH}/libfastcvopt.so" CACHE PATH "FastCV library") + endif() +endif(WITH_FASTCV) diff --git a/cmake/OpenCVFindWebP.cmake b/cmake/OpenCVFindWebP.cmake index 24f4f93efe..3af0908cbb 100644 --- a/cmake/OpenCVFindWebP.cmake +++ b/cmake/OpenCVFindWebP.cmake @@ -10,8 +10,6 @@ # Look for the header file. -unset(WEBP_FOUND) - FIND_PATH(WEBP_INCLUDE_DIR NAMES webp/decode.h) if(NOT WEBP_INCLUDE_DIR) @@ -21,13 +19,14 @@ else() # Look for the library. FIND_LIBRARY(WEBP_LIBRARY NAMES webp) - MARK_AS_ADVANCED(WEBP_LIBRARY) + FIND_LIBRARY(WEBP_MUX_LIBRARY NAMES webpmux) + FIND_LIBRARY(WEBP_DEMUX_LIBRARY NAMES webpdemux) # handle the QUIETLY and REQUIRED arguments and set WEBP_FOUND to TRUE if # all listed variables are TRUE INCLUDE(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WebP DEFAULT_MSG WEBP_LIBRARY WEBP_INCLUDE_DIR) - SET(WEBP_LIBRARIES ${WEBP_LIBRARY}) + SET(WEBP_LIBRARIES ${WEBP_LIBRARY} ${WEBP_MUX_LIBRARY} ${WEBP_DEMUX_LIBRARY}) SET(WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIR}) endif() diff --git a/cmake/android/android_gradle_projects.cmake b/cmake/android/android_gradle_projects.cmake index 4278b10f8d..d8e65a036a 100644 --- a/cmake/android/android_gradle_projects.cmake +++ b/cmake/android/android_gradle_projects.cmake @@ -1,5 +1,5 @@ # https://developer.android.com/studio/releases/gradle-plugin -set(ANDROID_GRADLE_PLUGIN_VERSION "7.3.1" CACHE STRING "Android Gradle Plugin version") +set(ANDROID_GRADLE_PLUGIN_VERSION "8.6.0" CACHE STRING "Android Gradle Plugin version") message(STATUS "Android Gradle Plugin version: ${ANDROID_GRADLE_PLUGIN_VERSION}") set(KOTLIN_PLUGIN_VERSION "1.8.20" CACHE STRING "Kotlin Plugin version") @@ -13,16 +13,16 @@ else() set(KOTLIN_STD_LIB "" CACHE STRING "Kotlin Standard Library dependency") endif() -set(GRADLE_VERSION "7.6.3" CACHE STRING "Gradle version") +set(GRADLE_VERSION "8.11.1" CACHE STRING "Gradle version") message(STATUS "Gradle version: ${GRADLE_VERSION}") -set(ANDROID_COMPILE_SDK_VERSION "31" CACHE STRING "Android compileSdkVersion") +set(ANDROID_COMPILE_SDK_VERSION "34" CACHE STRING "Android compileSdkVersion") if(ANDROID_NATIVE_API_LEVEL GREATER 21) set(ANDROID_MIN_SDK_VERSION "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android minSdkVersion") else() set(ANDROID_MIN_SDK_VERSION "21" CACHE STRING "Android minSdkVersion") endif() -set(ANDROID_TARGET_SDK_VERSION "31" CACHE STRING "Android minSdkVersion") +set(ANDROID_TARGET_SDK_VERSION "34" CACHE STRING "Android targetSdkVersion") set(ANDROID_BUILD_BASE_DIR "${OpenCV_BINARY_DIR}/opencv_android" CACHE INTERNAL "") set(ANDROID_TMP_INSTALL_BASE_DIR "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/install/opencv_android") @@ -35,11 +35,11 @@ include '${ANDROID_ABI}' ") set(ANDROID_INSTALL_ABI_FILTER " -//reset() -//include 'armeabi-v7a' -//include 'arm64-v8a' -//include 'x86' -//include 'x86_64' + reset() + include 'armeabi-v7a' + include 'arm64-v8a' + include 'x86' + include 'x86_64' ") if(NOT INSTALL_CREATE_DISTRIB) set(ANDROID_INSTALL_ABI_FILTER "${ANDROID_BUILD_ABI_FILTER}") @@ -54,7 +54,9 @@ set(ANDROID_STRICT_BUILD_CONFIGURATION "true") configure_file("${OpenCV_SOURCE_DIR}/samples/android/build.gradle.in" "${ANDROID_BUILD_BASE_DIR}/build.gradle" @ONLY) set(ANDROID_ABI_FILTER "${ANDROID_INSTALL_ABI_FILTER}") -set(ANDROID_STRICT_BUILD_CONFIGURATION "false") +# CI uses NDK 26d to overcome https://github.com/opencv/opencv/issues/26072 +# It's ahead of default configuration and we have to force version to get non-controversial parts of the package +#set(ANDROID_STRICT_BUILD_CONFIGURATION "false") configure_file("${OpenCV_SOURCE_DIR}/samples/android/build.gradle.in" "${ANDROID_TMP_INSTALL_BASE_DIR}/${ANDROID_INSTALL_SAMPLES_DIR}/build.gradle" @ONLY) install(FILES "${ANDROID_TMP_INSTALL_BASE_DIR}/${ANDROID_INSTALL_SAMPLES_DIR}/build.gradle" DESTINATION "${ANDROID_INSTALL_SAMPLES_DIR}" COMPONENT samples) diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index b4e6903943..793c4940c3 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -75,6 +75,9 @@ /* IJG JPEG codec */ #cmakedefine HAVE_JPEG +/* JPEG XL codec */ +#cmakedefine HAVE_JPEGXL + /* GDCM DICOM codec */ #cmakedefine HAVE_GDCM diff --git a/doc/opencv.bib b/doc/opencv.bib index 2d6375dede..9abbf3a5f9 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -402,20 +402,11 @@ publisher = {BMVA Press}, url = {https://www.researchgate.net/profile/Robert_Fisher5/publication/2237785_A_Buyer's_Guide_to_Conic_Fitting/links/0fcfd50f59aeded518000000/A-Buyers-Guide-to-Conic-Fitting.pdf} } -@article{fitzgibbon1999, - abstract = {This work presents a new efficient method for fitting ellipses to scattered data. Previous algorithms either fitted general conics or were computationally expensive. By minimizing the algebraic distance subject to the constraint 4ac-b2=1, the new method incorporates the ellipticity constraint into the normalization factor. The proposed method combines several advantages: It is ellipse-specific, so that even bad data will always return an ellipse. It can be solved naturally by a generalized eigensystem. It is extremely robust, efficient, and easy to implement}, - author = {Fitzgibbon, Andrew and Pilu, Maurizio and Fisher, Robert B.}, - doi = {10.1109/34.765658}, - isbn = {0162-8828}, - issn = {01628828}, - journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence}, - number = {5}, - pages = {476--480}, - pmid = {708}, - title = {Direct least square fitting of ellipses}, - volume = {21}, - year = {1999}, - url = {https://pdfs.semanticscholar.org/090d/25f94cb021bdd3400a2f547f989a6a5e07ec.pdf} +@inproceedings{oy1998NumericallySD, + title = {Numerically Stable Direct Least Squares Fitting of Ellipses}, + author = {Radim Hal oy and Jan Flusser}, + year = {1998}, + url = {https://www.semanticscholar.org/paper/Numerically-Stable-Direct-Least-Squares-Fitting-of-oy-Flusser/9a8607575ba9c6016e9f3db5e52f5ed4d14d5dfd} } @article{Gallego2014ACF, title = {A Compact Formula for the Derivative of a 3-D Rotation in Exponential Coordinates}, diff --git a/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown b/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown index 83a75e96f3..fa36540d69 100644 --- a/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown +++ b/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown @@ -36,7 +36,8 @@ Now let's create a function, draw which takes the corners in the chessboard (obt **cv.findChessboardCorners()**) and **axis points** to draw a 3D axis. @code{.py} def draw(img, corners, imgpts): - corner = tuple(corners[0].ravel()) + corner = tuple(corners[0].ravel().astype("int32")) + imgpts = imgpts.astype("int32") img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5) img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5) img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5) diff --git a/doc/tutorials/app/animations.markdown b/doc/tutorials/app/animations.markdown new file mode 100644 index 0000000000..2f7c5e1cc8 --- /dev/null +++ b/doc/tutorials/app/animations.markdown @@ -0,0 +1,91 @@ +Handling Animated Image Files {#tutorial_animations} +=========================== + +@tableofcontents + +| | | +| -: | :- | +| Original author | Suleyman Turkmen (with help of ChatGPT) | +| Compatibility | OpenCV >= 4.11 | + +Goal +---- +In this tutorial, you will learn how to: + +- Use `cv::imreadanimation` to load frames from animated image files. +- Understand the structure and parameters of the `cv::Animation` structure. +- Display individual frames from an animation. +- Use `cv::imwriteanimation` to write `cv::Animation` to a file. + +Source Code +----------- + +@add_toggle_cpp +- **Downloadable code**: Click + [here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/imgcodecs/animations.cpp) + +- **Code at a glance:** + @include samples/cpp/tutorial_code/imgcodecs/animations.cpp +@end_toggle + +@add_toggle_python +- **Downloadable code**: Click + [here](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/imgcodecs/animations.py) + +- **Code at a glance:** + @include samples/python/tutorial_code/imgcodecs/animations.py +@end_toggle + +Explanation +----------- + +## Initializing the Animation Structure + + Initialize a `cv::Animation` structure to hold the frames from the animated image file. + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp init_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py init_animation +@end_toggle + +## Loading Frames + + Use `cv::imreadanimation` to load frames from the specified file. Here, we load all frames from an animated WebP image. + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp read_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py read_animation +@end_toggle + +## Displaying Frames + + Each frame in the `animation.frames` vector can be displayed as a standalone image. This loop iterates through each frame, displaying it in a window with a short delay to simulate the animation. + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp show_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py show_animation +@end_toggle + +## Saving Animation + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp write_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py write_animation +@end_toggle + +## Summary + +The `cv::imreadanimation` and `cv::imwriteanimation` functions make it easy to work with animated image files by loading frames into a `cv::Animation` structure, allowing frame-by-frame processing. +With these functions, you can load, process, and save frames from animated image files like GIF, AVIF, APNG, and WebP. diff --git a/doc/tutorials/app/table_of_content_app.markdown b/doc/tutorials/app/table_of_content_app.markdown index 867f570078..714fb66b0c 100644 --- a/doc/tutorials/app/table_of_content_app.markdown +++ b/doc/tutorials/app/table_of_content_app.markdown @@ -10,3 +10,4 @@ Application utils (highgui, imgcodecs, videoio modules) {#tutorial_table_of_cont - @subpage tutorial_orbbec_uvc - @subpage tutorial_intelperc - @subpage tutorial_wayland_ubuntu +- @subpage tutorial_animations diff --git a/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown b/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown index fd9d087c9d..072e6ca093 100644 --- a/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown +++ b/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown @@ -83,7 +83,7 @@ Arranging the terms: \f$r = x \cos \theta + y \sin \theta\f$ ### Standard and Probabilistic Hough Line Transform -OpenCV implements two kind of Hough Line Transforms: +OpenCV implements three kind of Hough Line Transforms: a. **The Standard Hough Transform** @@ -97,6 +97,12 @@ b. **The Probabilistic Hough Line Transform** of the detected lines \f$(x_{0}, y_{0}, x_{1}, y_{1})\f$ - In OpenCV it is implemented with the function **HoughLinesP()** +c. **The Weighted Hough Transform** + +- Uses edge intensity instead binary 0 or 1 values in standard Hough transform. +- In OpenCV it is implemented with the function **HoughLines()** with use_edgeval=true. +- See the example in samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp. + ### What does this program do? - Loads an image - Applies a *Standard Hough Line Transform* and a *Probabilistic Line Transform*. diff --git a/doc/tutorials/introduction/config_reference/config_reference.markdown b/doc/tutorials/introduction/config_reference/config_reference.markdown index 0feaefdc84..8cc4712ec6 100644 --- a/doc/tutorials/introduction/config_reference/config_reference.markdown +++ b/doc/tutorials/introduction/config_reference/config_reference.markdown @@ -217,7 +217,7 @@ Following options can be used to produce special builds with instrumentation or | `ENABLE_BUILD_HARDENING` | GCC, Clang, MSVC | Enable compiler options which reduce possibility of code exploitation. | | `ENABLE_LTO` | GCC, Clang, MSVC | Enable Link Time Optimization (LTO). | | `ENABLE_THIN_LTO` | Clang | Enable thin LTO which incorporates intermediate bitcode to binaries allowing consumers optimize their applications later. | -| `OPENCV_ALGO_HINT_DEFAULT` | Any | Set default OpenCV implementation hint value: `ALGO_HINT_ACCURATE` or `ALGO_HINT_APROX`. Dangerous! The option changes behaviour globally and may affect accuracy of many algorithms. | +| `OPENCV_ALGO_HINT_DEFAULT` | Any | Set default OpenCV implementation hint value: `ALGO_HINT_ACCURATE` or `ALGO_HINT_APPROX`. Dangerous! The option changes behaviour globally and may affect accuracy of many algorithms. | @see [GCC instrumentation](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html) @see [Build hardening](https://en.wikipedia.org/wiki/Hardening_(computing)) @@ -310,11 +310,12 @@ Following formats can be read by OpenCV without help of any third-party library: | [JPEG2000 with OpenJPEG](https://en.wikipedia.org/wiki/OpenJPEG) | `WITH_OPENJPEG` | _ON_ | `BUILD_OPENJPEG` | | [JPEG2000 with JasPer](https://en.wikipedia.org/wiki/JasPer) | `WITH_JASPER` | _ON_ (see note) | `BUILD_JASPER` | | [EXR](https://en.wikipedia.org/wiki/OpenEXR) | `WITH_OPENEXR` | _ON_ | `BUILD_OPENEXR` | +| [JPEG XL](https://en.wikipedia.org/wiki/JPEG_XL) | `WITH_JPEGXL` | _ON_ | Not supported. (see note) | All libraries required to read images in these formats are included into OpenCV and will be built automatically if not found at the configuration stage. Corresponding `BUILD_*` options will force building and using own libraries, they are enabled by default on some platforms, e.g. Windows. @note OpenJPEG have higher priority than JasPer which is deprecated. In order to use JasPer, OpenJPEG must be disabled. - +@note (JPEG XL) OpenCV doesn't contain libjxl source code, so `BUILD_JPEGXL` is not supported. ### GDAL integration diff --git a/doc/tutorials/introduction/macos_install/macos_install.markdown b/doc/tutorials/introduction/macos_install/macos_install.markdown index 80ce08ef6d..4753fb8eab 100644 --- a/doc/tutorials/introduction/macos_install/macos_install.markdown +++ b/doc/tutorials/introduction/macos_install/macos_install.markdown @@ -9,41 +9,49 @@ Installation in MacOS {#tutorial_macos_install} | Original author | `@sajarindider` | | Compatibility | OpenCV >= 3.4 | -The following steps have been tested for MacOSX (Mavericks) but should work with other versions as well. +The following steps have been tested for macOS (Mavericks) but should work with other versions as well. Required Packages ----------------- - CMake 3.9 or higher - Git -- Python 2.7 or later and Numpy 1.5 or later +- Python 3.x and NumPy 1.5 or later This tutorial will assume you have [Python](https://docs.python.org/3/using/mac.html), -[Numpy](https://docs.scipy.org/doc/numpy-1.10.1/user/install.html) and -[Git](https://www.atlassian.com/git/tutorials/install-git) installed on your machine. +[NumPy](https://numpy.org/install/) and +[Git](https://git-scm.com/downloads/mac) installed on your machine. @note -OSX comes with Python 2.7 by default, you will need to install Python 3.8 if you want to use it specifically. +- macOS up to 12.2 (Monterey): Comes with Python 2.7 pre-installed. +- macOS 12.3 and later: Python 2.7 has been removed, and no version of Python is included by default. + +It is recommended to install the latest version of Python 3.x (at least Python 3.8) for compatibility with the latest OpenCV Python bindings. @note -If you XCode and XCode Command Line-Tools installed, you already have git installed on your machine. +If you have Xcode and Xcode Command Line Tools installed, Git is already available on your machine. Installing CMake ---------------- -# Find the version for your system and download CMake from their release's [page](https://cmake.org/download/) --# Install the dmg package and launch it from Applications. That will give you the UI app of CMake +-# Install the `.dmg` package and launch it from Applications. That will give you the UI app of CMake -# From the CMake app window, choose menu Tools --> How to Install For Command Line Use. Then, follow the instructions from the pop-up there. --# Install folder will be /usr/bin/ by default, submit it by choosing Install command line links. +-# The install folder will be `/usr/local/bin/` by default. Complete the installation by choosing Install command line links. + +-# Test that CMake is installed correctly by running: --# Test that it works by running @code{.bash} cmake --version @endcode -@note You can use [Homebrew](https://brew.sh/) to install CMake with @code{.bash} brew install cmake @endcode +@note You can use [Homebrew](https://brew.sh/) to install CMake with: + + @code{.bash} + brew install cmake + @endcode Getting OpenCV Source Code -------------------------- @@ -53,20 +61,22 @@ You can use the latest stable OpenCV version or you can grab the latest snapshot ### Getting the Latest Stable OpenCV Version -- Go to our [downloads page](https://opencv.org/releases). -- Download the source archive and unpack it. +- Go to our [OpenCV releases page](https://opencv.org/releases). +- Download the source archive of the latest version (e.g., OpenCV 4.x) and unpack it. ### Getting the Cutting-edge OpenCV from the Git Repository -Launch Git client and clone [OpenCV repository](http://github.com/opencv/opencv). -If you need modules from [OpenCV contrib repository](http://github.com/opencv/opencv_contrib) then clone it as well. +Launch Git client and clone [OpenCV repository](https://github.com/opencv/opencv). +If you need modules from [OpenCV contrib repository](https://github.com/opencv/opencv_contrib) then clone it as well. + +For example: + + @code{.bash} + cd ~/ + git clone https://github.com/opencv/opencv.git + git clone https://github.com/opencv/opencv_contrib.git + @endcode -For example -@code{.bash} -cd ~/ -git clone https://github.com/opencv/opencv.git -git clone https://github.com/opencv/opencv_contrib.git -@endcode Building OpenCV from Source Using CMake --------------------------------------- @@ -74,51 +84,96 @@ Building OpenCV from Source Using CMake the generated Makefiles, project files as well the object files and output binaries and enter there. - For example + For example: + @code{.bash} mkdir build_opencv cd build_opencv @endcode - @note It is good practice to keep clean your source code directories. Create build directory outside of source tree. + @note It is good practice to keep your source code directories clean. Create the build directory outside of the source tree. -# Configuring. Run `cmake [] ` - For example + For example: + @code{.bash} cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON ../opencv @endcode - or cmake-gui + Alternatively, you can use the CMake GUI (`cmake-gui`): - - set the OpenCV source code path to, e.g. `/home/user/opencv` - - set the binary build path to your CMake build directory, e.g. `/home/user/build_opencv` + - set the OpenCV source code path to, e.g. `/Users/your_username/opencv` + - set the binary build path to your CMake build directory, e.g. `/Users/your_username/build_opencv` - set optional parameters - run: "Configure" - run: "Generate" -# Description of some parameters - - build type: `CMAKE_BUILD_TYPE=Release` (or `Debug`) - - to build with modules from opencv_contrib set `OPENCV_EXTRA_MODULES_PATH` to `/modules` - - set `BUILD_DOCS=ON` for building documents (doxygen is required) - - set `BUILD_EXAMPLES=ON` to build all examples + - build type: `-DCMAKE_BUILD_TYPE=Release` (or `Debug`). + - include Extra Modules: If you cloned the `opencv_contrib` repository and want to include its modules, set: + + @code{.bash} + -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules + @endcode + - set `-DBUILD_DOCS=ON` for building documents (doxygen is required) + - set `-DBUILD_EXAMPLES=ON` to build all examples -# [optional] Building python. Set the following python parameters: - - `PYTHON3_EXECUTABLE = ` - - `PYTHON3_INCLUDE_DIR = /usr/include/python` - - `PYTHON3_NUMPY_INCLUDE_DIRS = - /usr/lib/python/dist-packages/numpy/core/include/` + - `-DPYTHON3_EXECUTABLE=$(which python3)` + - `-DPYTHON3_INCLUDE_DIR=$(python3 -c "from sysconfig import get_paths as gp; print(gp()['include'])")` + - `-DPYTHON3_NUMPY_INCLUDE_DIRS=$(python3 -c "import numpy; print(numpy.get_include())")` -# Build. From build directory execute *make*, it is recommended to do this in several threads - For example + For example: + @code{.bash} - make -j7 # runs 7 jobs in parallel + make -j$(sysctl -n hw.ncpu) # runs the build using all available CPU cores @endcode --# To use OpenCV in your CMake-based projects through `find_package(OpenCV)` specify `OpenCV_DIR=` variable. +-# After building, you can install OpenCV system-wide using: + + @code{.bash} + sudo make install + @endcode + +-# To use OpenCV in your CMake-based projects through `find_package(OpenCV)`, specify the `OpenCV_DIR` variable pointing to the build or install directory. + + For example: + + @code{.bash} + cmake -DOpenCV_DIR=~/build_opencv .. + @endcode + +### Verifying the OpenCV Installation + +After building (and optionally installing) OpenCV, you can verify the installation by checking the version using Python: + + @code{.bash} + python3 -c "import cv2; print(cv2.__version__)" + @endcode + +This command should output the version of OpenCV you have installed. @note You can also use a package manager like [Homebrew](https://brew.sh/) or [pip](https://pip.pypa.io/en/stable/) to install releases of OpenCV only (Not the cutting edge). + +- Installing via Homebrew: + + For example: + + @code{.bash} + brew install opencv + @endcode + +- Installing via pip: + + For example: + + @code{.bash} + pip install opencv-python + @endcode + + @note To access the extra modules from `opencv_contrib`, install the `opencv-contrib-python` package using `pip install opencv-contrib-python`. diff --git a/modules/3d/misc/js/gen_dict.json b/modules/3d/misc/js/gen_dict.json index be972043fe..b590b3ceec 100644 --- a/modules/3d/misc/js/gen_dict.json +++ b/modules/3d/misc/js/gen_dict.json @@ -15,6 +15,7 @@ "undistort", "fisheye_initUndistortRectifyMap", "fisheye_projectPoints" - ] + ], + "UsacParams": ["UsacParams"] } } diff --git a/modules/3d/src/fisheye.cpp b/modules/3d/src/fisheye.cpp index f8b8dbf54e..14902b4467 100644 --- a/modules/3d/src/fisheye.cpp +++ b/modules/3d/src/fisheye.cpp @@ -534,8 +534,8 @@ void cv::fisheye::initUndistortRectifyMap( InputArray K, InputArray D, InputArra if( m1type == CV_16SC2 ) { - int iu = cv::saturate_cast(u*cv::INTER_TAB_SIZE); - int iv = cv::saturate_cast(v*cv::INTER_TAB_SIZE); + int iu = cv::saturate_cast(u*static_cast(cv::INTER_TAB_SIZE)); + int iv = cv::saturate_cast(v*static_cast(cv::INTER_TAB_SIZE)); m1[j*2+0] = (short)(iu >> cv::INTER_BITS); m1[j*2+1] = (short)(iv >> cv::INTER_BITS); m2[j] = (ushort)((iv & (cv::INTER_TAB_SIZE-1))*cv::INTER_TAB_SIZE + (iu & (cv::INTER_TAB_SIZE-1))); diff --git a/modules/3d/src/fundam.cpp b/modules/3d/src/fundam.cpp index 6e9b9f7a6c..2318e22cbf 100644 --- a/modules/3d/src/fundam.cpp +++ b/modules/3d/src/fundam.cpp @@ -353,6 +353,11 @@ Mat findHomography( InputArray _points1, InputArray _points2, if( result && npoints > 4 && method != RHO) { + // save the original points before compressing + const int npoints_input = npoints; + const Mat src_input = src.clone(); + const Mat dst_input = dst.clone(); + compressElems( src.ptr(), tempMask.ptr(), 1, npoints ); npoints = compressElems( dst.ptr(), tempMask.ptr(), 1, npoints ); if( npoints > 0 ) @@ -414,6 +419,16 @@ Mat findHomography( InputArray _points1, InputArray _points2, .setGeodesic(true)); solver.optimize(); H.convertTo(H, H.type(), scaleFor(H.at(2, 2))); + + // find new inliers + const float thr_sqr = static_cast(ransacReprojThreshold * ransacReprojThreshold); + cv::Mat errors; + cb->computeError(src_input, dst_input, H, errors); + uchar* maskptr = tempMask.ptr(); + const float * const errors_ptr = errors.ptr(); + for (int i = 0; i < npoints_input; i++) { + maskptr[i] = static_cast(errors_ptr[i] <= thr_sqr); + } } } diff --git a/modules/3d/src/undistort.simd.hpp b/modules/3d/src/undistort.simd.hpp index 70bac44702..f944407058 100644 --- a/modules/3d/src/undistort.simd.hpp +++ b/modules/3d/src/undistort.simd.hpp @@ -259,8 +259,8 @@ public: double v = fy*invProj*vecTilt(1) + v0; if( m1type == CV_16SC2 ) { - int iu = saturate_cast(u*INTER_TAB_SIZE); - int iv = saturate_cast(v*INTER_TAB_SIZE); + int iu = saturate_cast(u*static_cast(INTER_TAB_SIZE)); + int iv = saturate_cast(v*static_cast(INTER_TAB_SIZE)); m1[j*2] = (short)(iu >> INTER_BITS); m1[j*2+1] = (short)(iv >> INTER_BITS); m2[j] = (ushort)((iv & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (iu & (INTER_TAB_SIZE-1))); diff --git a/modules/calib/test/test_cameracalibration.cpp b/modules/calib/test/test_cameracalibration.cpp index 52e92e1afc..e8ccaf4c5b 100644 --- a/modules/calib/test/test_cameracalibration.cpp +++ b/modules/calib/test/test_cameracalibration.cpp @@ -1384,11 +1384,11 @@ void CV_StereoCalibrationTest::run( int ) rotMats1, transVecs1, rmsErrorPerView1, rmsErrorPerView2, TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 1e-6), CALIB_SAME_FOCAL_LENGTH - //+ CV_CALIB_FIX_ASPECT_RATIO + //+ CALIB_FIX_ASPECT_RATIO + CALIB_FIX_PRINCIPAL_POINT + CALIB_ZERO_TANGENT_DIST + CALIB_FIX_K3 - + CALIB_FIX_K4 + CALIB_FIX_K5 //+ CV_CALIB_FIX_K6 + + CALIB_FIX_K4 + CALIB_FIX_K5 //+ CALIB_FIX_K6 ); /* rmsErrorFromStereoCalib /= nframes*npoints; */ diff --git a/modules/core/include/opencv2/core/cuda.hpp b/modules/core/include/opencv2/core/cuda.hpp index 741adedfff..0558081879 100644 --- a/modules/core/include/opencv2/core/cuda.hpp +++ b/modules/core/include/opencv2/core/cuda.hpp @@ -413,8 +413,9 @@ public: data, which means that no data is copied. This operation is very efficient and can be used to process external data using OpenCV functions. The external data is not automatically deallocated, so you should take care of it. - @param step Array of _size.size()-1 steps in case of a multi-dimensional array (the last step is always - set to the element size). If not specified, the matrix is assumed to be continuous. + @param step Array of _size.size() or _size.size()-1 steps in case of a multi-dimensional array + (if specified, the last step must be equal to the element size, otherwise it will be added as such). + If not specified, the matrix is assumed to be continuous. */ GpuMatND(SizeArray size, int type, void* data, StepArray step = StepArray()); diff --git a/modules/core/include/opencv2/core/hal/intrin_vsx.hpp b/modules/core/include/opencv2/core/hal/intrin_vsx.hpp index 2157e1e870..0a0915a22f 100644 --- a/modules/core/include/opencv2/core/hal/intrin_vsx.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_vsx.hpp @@ -262,7 +262,7 @@ OPENCV_HAL_IMPL_VSX_EXTRACT_N(v_float64x2, double) inline _Tpvec v_setzero_##suffix() { return _Tpvec(vec_splats((_Tp)0)); } \ inline _Tpvec v_setall_##suffix(_Tp v) { return _Tpvec(vec_splats((_Tp)v));} \ template <> inline _Tpvec v_setzero_() { return v_setzero_##suffix(); } \ -template <> inline _Tpvec v_setall_(_Tp v) { return v_setall_##suffix(_Tp v); } \ +template <> inline _Tpvec v_setall_(_Tp v) { return v_setall_##suffix(v); } \ template inline _Tpvec v_reinterpret_as_##suffix(const _Tpvec0 &a) \ { return _Tpvec((cast)a.val); } @@ -650,11 +650,11 @@ OPENCV_HAL_IMPL_VSX_SELECT(v_float64x2, vec_bdword2_c) #define OPENCV_HAL_IMPL_VSX_INT_CMP_OP(_Tpvec) \ inline _Tpvec v_eq(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmpeq(a.val, b.val)); } \ -inline _Tpvec V_ne(const _Tpvec& a, const _Tpvec& b) \ +inline _Tpvec v_ne(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmpne(a.val, b.val)); } \ inline _Tpvec v_lt(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmplt(a.val, b.val)); } \ -inline _Tpvec V_gt(const _Tpvec& a, const _Tpvec& b) \ +inline _Tpvec v_gt(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmpgt(a.val, b.val)); } \ inline _Tpvec v_le(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmple(a.val, b.val)); } \ @@ -1507,7 +1507,7 @@ inline v_float64x2 v_dotprod_expand(const v_int32x4& a, const v_int32x4& b, cons inline v_int32x4 v_dotprod_fast(const v_int16x8& a, const v_int16x8& b) { return v_dotprod(a, b); } inline v_int32x4 v_dotprod_fast(const v_int16x8& a, const v_int16x8& b, const v_int32x4& c) -{ return v_int32x4(vec_msum(a.val, b.val, vec_int4_z)) + c; } +{ return v_add(v_int32x4(vec_msum(a.val, b.val, vec_int4_z)), c); } // 32 >> 64 inline v_int64x2 v_dotprod_fast(const v_int32x4& a, const v_int32x4& b) { return v_dotprod(a, b); } @@ -1518,7 +1518,7 @@ inline v_int64x2 v_dotprod_fast(const v_int32x4& a, const v_int32x4& b, const v_ inline v_uint32x4 v_dotprod_expand_fast(const v_uint8x16& a, const v_uint8x16& b) { return v_dotprod_expand(a, b); } inline v_uint32x4 v_dotprod_expand_fast(const v_uint8x16& a, const v_uint8x16& b, const v_uint32x4& c) -{ return v_uint32x4(vec_msum(a.val, b.val, vec_uint4_z)) + c; } +{ return v_add(v_uint32x4(vec_msum(a.val, b.val, vec_uint4_z)), c); } inline v_int32x4 v_dotprod_expand_fast(const v_int8x16& a, const v_int8x16& b) { diff --git a/modules/core/include/opencv2/core/mat.inl.hpp b/modules/core/include/opencv2/core/mat.inl.hpp index 649ebd2657..59df23e83b 100644 --- a/modules/core/include/opencv2/core/mat.inl.hpp +++ b/modules/core/include/opencv2/core/mat.inl.hpp @@ -538,7 +538,7 @@ CV__DEBUG_NS_END template inline Mat::Mat(const std::vector<_Tp>& vec, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), cols((int)vec.size()), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&cols) { @@ -583,7 +583,7 @@ Mat::Mat(const std::initializer_list sizes, const std::initializer_list<_Tp template inline Mat::Mat(const std::array<_Tp, _Nm>& arr, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), cols((int)arr.size()), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&cols), step(0) { @@ -604,7 +604,7 @@ Mat::Mat(const std::array<_Tp, _Nm>& arr, bool copyData) template inline Mat::Mat(const Vec<_Tp, n>& vec, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), cols(n), data(0), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), cols(n), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&cols), step(0) { if( !copyData ) @@ -639,7 +639,7 @@ Mat::Mat(const Matx<_Tp,m,n>& M, bool copyData) template inline Mat::Mat(const Point_<_Tp>& pt, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), cols(2), data(0), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), cols(2), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&cols), step(0) { if( !copyData ) @@ -661,8 +661,8 @@ Mat::Mat(const Point_<_Tp>& pt, bool copyData) template inline Mat::Mat(const Point3_<_Tp>& pt, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(1), rows(1), cols(3), data(0), - datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&cols), step(0) + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows(3), cols(1), data(0), + datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&rows), step(0) { if( !copyData ) { @@ -683,7 +683,7 @@ Mat::Mat(const Point3_<_Tp>& pt, bool copyData) template inline Mat::Mat(const MatCommaInitializer_<_Tp>& commaInitializer) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(0), rows(0), cols(0), data(0), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0), allocator(0), u(0), size(&rows) { *this = commaInitializer.operator Mat_<_Tp>(); @@ -2250,7 +2250,7 @@ SparseMatConstIterator_<_Tp> SparseMat::end() const template inline SparseMat_<_Tp>::SparseMat_() { - flags = MAGIC_VAL + traits::Type<_Tp>::value; + flags = +MAGIC_VAL + traits::Type<_Tp>::value; } template inline diff --git a/modules/core/src/bindings_utils.cpp b/modules/core/src/bindings_utils.cpp index 78093002f7..6191f26f7c 100644 --- a/modules/core/src/bindings_utils.cpp +++ b/modules/core/src/bindings_utils.cpp @@ -9,7 +9,12 @@ #include #include -namespace cv { namespace utils { +namespace cv { +static inline std::ostream& operator<<(std::ostream& os, const Rect& rect) +{ + return os << "[x=" << rect.x << ", y=" << rect.y << ", w=" << rect.width << ", h=" << rect.height << ']'; +} +namespace utils { String dumpInputArray(InputArray argument) { @@ -51,9 +56,13 @@ String dumpInputArray(InputArray argument) ss << " type(-1)=" << cv::typeToString(argument.type(-1)); } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } @@ -104,9 +113,13 @@ CV_EXPORTS_W String dumpInputArrayOfArrays(InputArrayOfArrays argument) } } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } @@ -151,9 +164,13 @@ CV_EXPORTS_W String dumpInputOutputArray(InputOutputArray argument) ss << " type(-1)=" << cv::typeToString(argument.type(-1)); } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } @@ -204,28 +221,28 @@ CV_EXPORTS_W String dumpInputOutputArrayOfArrays(InputOutputArrayOfArrays argume } } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } -static inline std::ostream& operator<<(std::ostream& os, const cv::Rect& rect) -{ - return os << "[x=" << rect.x << ", y=" << rect.y << ", w=" << rect.width << ", h=" << rect.height << ']'; -} - template static inline String dumpVector(const std::vector& vec, Formatter format) { std::ostringstream oss("[", std::ios::ate); if (!vec.empty()) { - oss << format << vec[0]; + format(oss) << vec[0]; for (std::size_t i = 1; i < vec.size(); ++i) { - oss << ", " << format << vec[i]; + oss << ", "; + format(oss) << vec[i]; } } oss << "]"; diff --git a/modules/core/src/cuda_gpu_mat_nd.cpp b/modules/core/src/cuda_gpu_mat_nd.cpp index 34f64ddb6b..d810ba2854 100644 --- a/modules/core/src/cuda_gpu_mat_nd.cpp +++ b/modules/core/src/cuda_gpu_mat_nd.cpp @@ -12,7 +12,8 @@ GpuMatND::~GpuMatND() = default; GpuMatND::GpuMatND(SizeArray _size, int _type, void* _data, StepArray _step) : flags(0), dims(0), data(static_cast(_data)), offset(0) { - CV_Assert(_step.empty() || _size.size() == _step.size() + 1); + CV_Assert(_step.empty() || _size.size() == _step.size() + 1 || + (_size.size() == _step.size() && _step.back() == (size_t)CV_ELEM_SIZE(_type))); setFields(std::move(_size), _type, std::move(_step)); } @@ -118,7 +119,8 @@ void GpuMatND::setFields(SizeArray _size, int _type, StepArray _step) else { step = std::move(_step); - step.push_back(elemSize()); + if (step.size() < size.size()) + step.push_back(elemSize()); flags = cv::updateContinuityFlag(flags, dims, size.data(), step.data()); } diff --git a/modules/core/src/matmul.simd.hpp b/modules/core/src/matmul.simd.hpp index 6f77adc68b..a64cd7ba6a 100644 --- a/modules/core/src/matmul.simd.hpp +++ b/modules/core/src/matmul.simd.hpp @@ -2536,8 +2536,7 @@ double dotProd_16s(const short* src1, const short* src2, int len) double dotProd_32s(const int* src1, const int* src2, int len) { -#if CV_SIMD_64F // TODO: enable for CV_SIMD_SCALABLE_64F -// Test failed on RVV(QEMU): Too big difference (=1.20209e-08 > 1.11022e-12) +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F double r = .0; int i = 0; const int step = VTraits::vlanes(); diff --git a/modules/core/src/matrix_sparse.cpp b/modules/core/src/matrix_sparse.cpp index 9903e39dea..6867e598a1 100644 --- a/modules/core/src/matrix_sparse.cpp +++ b/modules/core/src/matrix_sparse.cpp @@ -170,7 +170,14 @@ void SparseMat::Hdr::clear() hashtab.clear(); hashtab.resize(HASH_SIZE0); pool.clear(); +#if defined(__GNUC__) && (__GNUC__ == 13) && !defined(__clang__) && (__cplusplus >= 202002L) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif pool.resize(nodeSize); +#if defined(__GNUC__) && (__GNUC__ == 13) && !defined(__clang__) && (__cplusplus >= 202002L) +#pragma GCC diagnostic pop +#endif nodeCount = freeList = 0; } diff --git a/modules/core/src/stat.simd.hpp b/modules/core/src/stat.simd.hpp index a5fb05476d..e363313e5b 100644 --- a/modules/core/src/stat.simd.hpp +++ b/modules/core/src/stat.simd.hpp @@ -48,12 +48,16 @@ int normHamming(const uchar* a, int n) # if defined CV_POPCNT_U64 for(; i <= n - 8; i += 8) { - result += (int)CV_POPCNT_U64(*(uint64*)(a + i)); + uint64_t val; + std::memcpy(&val, a + i, sizeof(val)); + result += (int)CV_POPCNT_U64(val); } # endif for(; i <= n - 4; i += 4) { - result += CV_POPCNT_U32(*(uint*)(a + i)); + uint32_t val; + std::memcpy(&val, a + i, sizeof(val)); + result += CV_POPCNT_U32(val); } } #endif @@ -92,12 +96,18 @@ int normHamming(const uchar* a, const uchar* b, int n) # if defined CV_POPCNT_U64 for(; i <= n - 8; i += 8) { - result += (int)CV_POPCNT_U64(*(uint64*)(a + i) ^ *(uint64*)(b + i)); + uint64_t val_a, val_b; + std::memcpy(&val_a, a + i, sizeof(val_a)); + std::memcpy(&val_b, b + i, sizeof(val_b)); + result += (int)CV_POPCNT_U64(val_a ^ val_b); } # endif for(; i <= n - 4; i += 4) { - result += CV_POPCNT_U32(*(uint*)(a + i) ^ *(uint*)(b + i)); + uint32_t val_a, val_b; + std::memcpy(&val_a, a + i, sizeof(val_a)); + std::memcpy(&val_b, b + i, sizeof(val_b)); + result += (int)CV_POPCNT_U32(val_a ^ val_b); } } #endif diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index e8da583fab..ee88ff578e 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -1655,18 +1655,22 @@ TEST(Core_Mat_array, copyTo_roi_row) EXPECT_EQ(5, (int)dst2[4]); } -TEST(Core_Mat_array, SplitMerge) +typedef testing::TestWithParam< tuple > Core_Mat_arrays; + +TEST_P(Core_Mat_arrays, SplitMerge) { - std::array src; + int cn = get<0>(GetParam()); + int type = get<1>(GetParam()); + std::vector src(cn); for (size_t i = 0; i < src.size(); ++i) { - src[i] = Mat(10, 10, CV_8U, Scalar((double)(16 * (i + 1)))); + src[i] = Mat(10, 10, type, Scalar((double)(16 * (i + 1)))); } Mat merged; merge(src, merged); - std::array dst; + std::vector dst(cn); split(merged, dst); for (size_t i = 0; i < dst.size(); ++i) @@ -1675,6 +1679,17 @@ TEST(Core_Mat_array, SplitMerge) } } +INSTANTIATE_TEST_CASE_P(/*nothing*/, Core_Mat_arrays, testing::Combine( + testing::Range(1, 9), + testing::Values( + perf::MatType(CV_8U), + perf::MatType(CV_16U), + perf::MatType(CV_32S), + perf::MatType(CV_64F) + ) +) +); + TEST(Mat, regression_8680) { Mat_ mat(3,1); diff --git a/modules/core/test/test_math.cpp b/modules/core/test/test_math.cpp index 5dd891f85f..1fc826c396 100644 --- a/modules/core/test/test_math.cpp +++ b/modules/core/test/test_math.cpp @@ -478,8 +478,9 @@ class Core_DotProductTest : public Core_MatrixTest public: Core_DotProductTest(); protected: - void run_func(); - void prepare_to_validation( int test_case_idx ); + void run_func() CV_OVERRIDE; + void prepare_to_validation( int test_case_idx ) CV_OVERRIDE; + double get_success_error_level( int test_case_idx, int i, int j ) CV_OVERRIDE; }; @@ -499,6 +500,15 @@ void Core_DotProductTest::prepare_to_validation( int ) test_mat[REF_OUTPUT][0].at(0,0) = Scalar(cvtest::crossCorr( test_mat[INPUT][0], test_mat[INPUT][1] )); } +double Core_DotProductTest::get_success_error_level( int test_case_idx, int i, int j ) +{ +#ifdef __riscv + const int depth = test_mat[i][j].depth(); + if (depth == CV_64F) + return 1.7e-5; +#endif + return Core_MatrixTest::get_success_error_level( test_case_idx, i, j ); +} ///////// crossproduct ////////// diff --git a/modules/dnn/perf/perf_utils.cpp b/modules/dnn/perf/perf_utils.cpp new file mode 100644 index 0000000000..1ab544b3eb --- /dev/null +++ b/modules/dnn/perf/perf_utils.cpp @@ -0,0 +1,66 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2017, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include "perf_precomp.hpp" + +namespace opencv_test { + +using Utils_blobFromImage = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage, HWC_TO_NCHW) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_32FC3); + randu(input, -10.0f, 10.f); + + TEST_CYCLE() { + Mat blob = blobFromImage(input); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage, + Values(std::vector{ 32, 32}, + std::vector{ 64, 64}, + std::vector{ 128, 128}, + std::vector{ 256, 256}, + std::vector{ 512, 512}, + std::vector{1024, 1024}, + std::vector{2048, 2048}) +); + +using Utils_blobFromImages = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImages, HWC_TO_NCHW) { + std::vector input_shape = GetParam(); + int batch = input_shape.front(); + std::vector input_shape_no_batch(input_shape.begin()+1, input_shape.end()); + + std::vector inputs; + for (int i = 0; i < batch; i++) { + Mat input(input_shape_no_batch, CV_32FC3); + randu(input, -10.0f, 10.f); + inputs.push_back(input); + } + + TEST_CYCLE() { + Mat blobs = blobFromImages(inputs); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImages, + Values(std::vector{16, 32, 32}, + std::vector{16, 64, 64}, + std::vector{16, 128, 128}, + std::vector{16, 256, 256}, + std::vector{16, 512, 512}, + std::vector{16, 1024, 1024}, + std::vector{16, 2048, 2048}) +); + +} diff --git a/modules/dnn/src/caffe/caffe_importer.cpp b/modules/dnn/src/caffe/caffe_importer.cpp index eab8acbf64..188441a5a1 100644 --- a/modules/dnn/src/caffe/caffe_importer.cpp +++ b/modules/dnn/src/caffe/caffe_importer.cpp @@ -129,8 +129,8 @@ public: const google::protobuf::UnknownField& field = unknownFields.field(i); CV_Assert(field.type() == google::protobuf::UnknownField::TYPE_GROUP); CV_CheckGE(field.group().field_count(), 2, "UnknownField should have at least 2 items: name and value"); - std::string fieldName = field.group().field(0).length_delimited(); - std::string fieldValue = field.group().field(1).length_delimited(); + std::string fieldName(field.group().field(0).length_delimited()); + std::string fieldValue(field.group().field(1).length_delimited()); params.set(fieldName, fieldValue); } } @@ -140,7 +140,7 @@ public: const Reflection *refl = msg.GetReflection(); int type = field->cpp_type(); bool isRepeated = field->is_repeated(); - const std::string &name = field->name(); + const std::string name(field->name()); #define SET_UP_FILED(getter, arrayConstr, gtype) \ if (isRepeated) { \ @@ -192,7 +192,7 @@ public: params.set(name, DictValue::arrayString(buf.begin(), size)); } else { - params.set(name, refl->GetEnum(msg, field)->name()); + params.set(name, std::string(refl->GetEnum(msg, field)->name())); } break; default: @@ -215,7 +215,7 @@ public: { const FieldDescriptor *fd = msgDesc->field(fieldId); - if (!isInternal && !ends_with_param(fd->name())) + if (!isInternal && !ends_with_param(std::string(fd->name()))) continue; const google::protobuf::UnknownFieldSet& unknownFields = msgRefl->GetUnknownFields(msg); diff --git a/modules/dnn/src/dnn_utils.cpp b/modules/dnn/src/dnn_utils.cpp index 3ef75fad36..87561d0e50 100644 --- a/modules/dnn/src/dnn_utils.cpp +++ b/modules/dnn/src/dnn_utils.cpp @@ -126,6 +126,111 @@ Mat blobFromImagesWithParams(InputArrayOfArrays images, const Image2BlobParams& return blob; } +template +void blobFromImagesNCHWImpl(const std::vector& images, Mat& blob_, const Image2BlobParams& param) +{ + int w = images[0].cols; + int h = images[0].rows; + int wh = w * h; + int nch = images[0].channels(); + CV_Assert(nch == 1 || nch == 3 || nch == 4); + int sz[] = { (int)images.size(), nch, h, w}; + blob_.create(4, sz, param.ddepth); + + for (size_t k = 0; k < images.size(); ++k) + { + CV_Assert(images[k].depth() == images[0].depth()); + CV_Assert(images[k].channels() == images[0].channels()); + CV_Assert(images[k].size() == images[0].size()); + + Tout* p_blob = blob_.ptr() + k * nch * wh; + Tout* p_blob_r = p_blob; + Tout* p_blob_g = p_blob + wh; + Tout* p_blob_b = p_blob + 2 * wh; + Tout* p_blob_a = p_blob + 3 * wh; + + if (param.swapRB) + std::swap(p_blob_r, p_blob_b); + + for (size_t i = 0; i < h; ++i) + { + const Tinp* p_img_row = images[k].ptr(i); + + if (nch == 1) + { + for (size_t j = 0; j < w; ++j) + { + p_blob[i * w + j] = p_img_row[j]; + } + } + else if (nch == 3) + { + for (size_t j = 0; j < w; ++j) + { + p_blob_r[i * w + j] = p_img_row[j * 3 ]; + p_blob_g[i * w + j] = p_img_row[j * 3 + 1]; + p_blob_b[i * w + j] = p_img_row[j * 3 + 2]; + } + } + else // if (nch == 4) + { + for (size_t j = 0; j < w; ++j) + { + p_blob_r[i * w + j] = p_img_row[j * 4 ]; + p_blob_g[i * w + j] = p_img_row[j * 4 + 1]; + p_blob_b[i * w + j] = p_img_row[j * 4 + 2]; + p_blob_a[i * w + j] = p_img_row[j * 4 + 3]; + } + } + } + } + + if (param.mean == Scalar() && param.scalefactor == Scalar::all(1.0)) + return; + CV_CheckTypeEQ(param.ddepth, CV_32F, "Scaling and mean substraction is supported only for CV_32F blob depth"); + + for (size_t k = 0; k < images.size(); ++k) + { + for (size_t ch = 0; ch < nch; ++ch) + { + float cur_mean = param.mean[ch]; + float cur_scale = param.scalefactor[ch]; + Tout* p_blob = blob_.ptr() + k * nch * wh + ch * wh; + for (size_t i = 0; i < wh; ++i) + { + p_blob[i] = (p_blob[i] - cur_mean) * cur_scale; + } + } + } +} + +template +void blobFromImagesNCHW(const std::vector& images, Mat& blob_, const Image2BlobParams& param) +{ + if (images[0].depth() == CV_8U) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_8S) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_16U) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_16S) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_32S) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_32F) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_64F) + blobFromImagesNCHWImpl(images, blob_, param); + else + CV_Error(Error::BadDepth, "Unsupported input image depth for blobFromImagesNCHW"); +} + +template +void blobFromImagesNCHW(const std::vector& images, UMat& blob_, const Image2BlobParams& param) +{ + CV_Error(Error::StsNotImplemented, ""); +} + template void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const Image2BlobParams& param) { @@ -154,19 +259,6 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const Scalar scalefactor = param.scalefactor; Scalar mean = param.mean; - if (param.swapRB) - { - if (nch > 2) - { - std::swap(mean[0], mean[2]); - std::swap(scalefactor[0], scalefactor[2]); - } - else - { - CV_LOG_WARNING(NULL, "Red/blue color swapping requires at least three image channels."); - } - } - for (size_t i = 0; i < images.size(); i++) { Size imgSize = images[i].size(); @@ -203,18 +295,35 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const resize(images[i], images[i], size, 0, 0, INTER_LINEAR); } } - - if (images[i].depth() == CV_8U && param.ddepth == CV_32F) - images[i].convertTo(images[i], CV_32F); - - subtract(images[i], mean, images[i]); - multiply(images[i], scalefactor, images[i]); } size_t nimages = images.size(); Tmat image0 = images[0]; CV_Assert(image0.dims == 2); + if (std::is_same::value && param.datalayout == DNN_LAYOUT_NCHW) + { + // Fast implementation for HWC cv::Mat images -> NCHW cv::Mat blob + if (param.ddepth == CV_8U) + blobFromImagesNCHW(images, blob_, param); + else + blobFromImagesNCHW(images, blob_, param); + return; + } + + if (param.swapRB) + { + if (nch > 2) + { + std::swap(mean[0], mean[2]); + std::swap(scalefactor[0], scalefactor[2]); + } + else + { + CV_LOG_WARNING(NULL, "Red/blue color swapping requires at least three image channels."); + } + } + if (param.datalayout == DNN_LAYOUT_NCHW) { if (nch == 3 || nch == 4) @@ -225,7 +334,14 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const for (size_t i = 0; i < nimages; i++) { - const Tmat& image = images[i]; + Tmat& image = images[i]; + if (image.depth() == CV_8U && param.ddepth == CV_32F) + image.convertTo(image, CV_32F); + if (mean != Scalar()) + subtract(image, mean, image); + if (scalefactor != Scalar::all(1.0)) + multiply(image, scalefactor, image); + CV_Assert(image.depth() == blob_.depth()); nch = image.channels(); CV_Assert(image.dims == 2 && (nch == 3 || nch == 4)); @@ -250,7 +366,14 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const for (size_t i = 0; i < nimages; i++) { - const Tmat& image = images[i]; + Tmat& image = images[i]; + if (image.depth() == CV_8U && param.ddepth == CV_32F) + image.convertTo(image, CV_32F); + if (mean != Scalar()) + subtract(image, mean, image); + if (scalefactor != Scalar::all(1.0)) + multiply(image, scalefactor, image); + CV_Assert(image.depth() == blob_.depth()); nch = image.channels(); CV_Assert(image.dims == 2 && (nch == 1)); @@ -269,7 +392,14 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const int subMatType = CV_MAKETYPE(param.ddepth, nch); for (size_t i = 0; i < nimages; i++) { - const Tmat& image = images[i]; + Tmat& image = images[i]; + if (image.depth() == CV_8U && param.ddepth == CV_32F) + image.convertTo(image, CV_32F); + if (mean != Scalar()) + subtract(image, mean, image); + if (scalefactor != Scalar::all(1.0)) + multiply(image, scalefactor, image); + CV_Assert(image.depth() == blob_.depth()); CV_Assert(image.channels() == image0.channels()); CV_Assert(image.size() == image0.size()); diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index ab224b68a9..6ebf7e7867 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -809,6 +809,15 @@ struct GeluFunctor : public BaseFunctor { } #endif // HAVE_DNN_NGRAPH +#ifdef HAVE_CANN + Ptr initCannOp(const std::string& name, + const std::vector > &inputs, + const std::vector >& nodes) + { + CV_Error(Error::StsNotImplemented, ""); + } +#endif // HAVE_CANN + int64 getFLOPSPerElement() const { return 100; } }; diff --git a/modules/dnn/src/op_cuda.cpp b/modules/dnn/src/op_cuda.cpp index 954864936d..372c36f11a 100644 --- a/modules/dnn/src/op_cuda.cpp +++ b/modules/dnn/src/op_cuda.cpp @@ -108,3 +108,18 @@ void Net::Impl::initCUDABackend(const std::vector& blobsToKeep_) CV__DNN_INLINE_NS_END }} // namespace cv::dnn #endif // HAVE_CUDA + +namespace cv { namespace dnn { + +bool haveCUDA() +{ +#ifdef HAVE_CUDA + int dev = 0; + static bool ret = (cudaGetDevice(&dev) == cudaSuccess); + return ret; +#else + return false; +#endif +} + +}} // namespace cv::dnn diff --git a/modules/dnn/src/op_cuda.hpp b/modules/dnn/src/op_cuda.hpp index 45d577a57a..ecc8b3c856 100644 --- a/modules/dnn/src/op_cuda.hpp +++ b/modules/dnn/src/op_cuda.hpp @@ -29,13 +29,7 @@ namespace cv { namespace dnn { return id == DNN_TARGET_CUDA_FP16 || id == DNN_TARGET_CUDA; } - constexpr bool haveCUDA() { -#ifdef HAVE_CUDA - return true; -#else - return false; -#endif - } + bool haveCUDA(); #ifdef HAVE_CUDA namespace cuda4dnn { namespace csl { diff --git a/modules/dnn/test/test_ie_models.cpp b/modules/dnn/test/test_ie_models.cpp index 647ee907bc..9ad3c80355 100644 --- a/modules/dnn/test/test_ie_models.cpp +++ b/modules/dnn/test/test_ie_models.cpp @@ -103,25 +103,6 @@ static const std::map& getOpenVINOTestMo "intel/age-gender-recognition-retail-0013/FP16/age-gender-recognition-retail-0013", "intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013" }}, -#endif -#if INF_ENGINE_RELEASE >= 2021020000 - // OMZ: 2020.2 - { "face-detection-0105", { - "intel/face-detection-0105/FP32/face-detection-0105", - "intel/face-detection-0105/FP16/face-detection-0105" - }}, - { "face-detection-0106", { - "intel/face-detection-0106/FP32/face-detection-0106", - "intel/face-detection-0106/FP16/face-detection-0106" - }}, -#endif -#if INF_ENGINE_RELEASE >= 2021040000 - // OMZ: 2021.4 - { "person-vehicle-bike-detection-2004", { - "intel/person-vehicle-bike-detection-2004/FP32/person-vehicle-bike-detection-2004", - "intel/person-vehicle-bike-detection-2004/FP16/person-vehicle-bike-detection-2004" - //"intel/person-vehicle-bike-detection-2004/FP16-INT8/person-vehicle-bike-detection-2004" - }}, #endif }; @@ -228,7 +209,12 @@ void runIE(Target target, const std::string& xmlPath, const std::string& binPath for (auto&& it : model->inputs()) { auto type = it.get_element_type(); - auto shape = it.get_shape(); + auto shape_ = it.get_partial_shape(); + if (shape_.is_dynamic()) + { + FAIL() << "Model should not have dynamic shapes (" << it.get_any_name() << " => " << shape_ << ")"; + } + auto shape = shape_.to_shape(); auto& m = inputsMap[it.get_any_name()]; auto tensor = ov::Tensor(type, shape); @@ -265,10 +251,10 @@ void runIE(Target target, const std::string& xmlPath, const std::string& binPath for (const auto& it : model->outputs()) { auto type = it.get_element_type(); - auto shape = it.get_shape(); auto& m = outputsMap[it.get_any_name()]; auto tensor = infRequest.get_tensor(it); + auto shape = tensor.get_shape(); if (type == ov::element::f32) { m.create(std::vector(shape.begin(), shape.end()), CV_32F); @@ -341,22 +327,9 @@ TEST_P(DNNTestOpenVINO, models) if (targetId == DNN_TARGET_MYRIAD && (false || modelName == "person-detection-retail-0013" // ncDeviceOpen:1013 Failed to find booted device after boot || modelName == "age-gender-recognition-retail-0013" // ncDeviceOpen:1013 Failed to find booted device after boot - || modelName == "face-detection-0105" // get_element_type() must be called on a node with exactly one output - || modelName == "face-detection-0106" // get_element_type() must be called on a node with exactly one output - || modelName == "person-vehicle-bike-detection-2004" // 2021.4+: ncDeviceOpen:1013 Failed to find booted device after boot ) ) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - if (targetId == DNN_TARGET_OPENCL && (false - || modelName == "face-detection-0106" // Operation: 2278 of type ExperimentalDetectronPriorGridGenerator(op::v6) is not supported - ) - ) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - if (targetId == DNN_TARGET_OPENCL_FP16 && (false - || modelName == "face-detection-0106" // Operation: 2278 of type ExperimentalDetectronPriorGridGenerator(op::v6) is not supported - ) - ) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); #endif #if INF_ENGINE_VER_MAJOR_GE(2020020000) @@ -399,14 +372,6 @@ TEST_P(DNNTestOpenVINO, models) if (targetId == DNN_TARGET_CPU && checkHardwareSupport(CV_CPU_AVX_512F)) eps = 1e-5; #endif -#if INF_ENGINE_VER_MAJOR_GE(2021030000) - if (targetId == DNN_TARGET_CPU && modelName == "face-detection-0105") - eps = 2e-4; -#endif -#if INF_ENGINE_VER_MAJOR_GE(2021040000) - if (targetId == DNN_TARGET_CPU && modelName == "person-vehicle-bike-detection-2004") - eps = 1e-6; -#endif EXPECT_EQ(ieOutputsMap.size(), cvOutputsMap.size()); for (auto& srcIt : ieOutputsMap) diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index 7963741b70..8ff0e17974 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -2812,7 +2812,7 @@ void yoloPostProcessing( if (model_name == "yolonas"){ EXPECT_EQ(cv::MatShape({1, 8400, 80}), outs[0].shape()); EXPECT_EQ(cv::MatShape({1, 8400, 4}), outs[1].shape()); - // outs contains 2 elemets of shape [1, 8400, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84] + // outs contains 2 elemets of shape [1, 8400, nc] and [1, 8400, 4]. Concat them to get [1, 8400, 84] Mat concat_out; // squeeze the first dimension outs[0] = outs[0].reshape(1, outs[0].size[1]); @@ -2822,12 +2822,12 @@ void yoloPostProcessing( // remove the second element outs.pop_back(); // unsqueeze the first dimension - outs[0] = outs[0].reshape(0, std::vector{1, 8400, 84}); + outs[0] = outs[0].reshape(0, std::vector{1, outs[0].size[0], outs[0].size[1]}); } - // assert if last dim is 85 or 84 - CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, 85 or 84]"); - CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == 80 + 4), true, "Invalid output shape: "); + // assert if last dim is nc+5 or nc+4 + CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, nc+5 or nc+4]"); + CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == nc + 4), true, "Invalid output shape: "); for (auto preds : outs){ diff --git a/modules/dnn/test/test_tflite_importer.cpp b/modules/dnn/test/test_tflite_importer.cpp index bc01ee2ee1..91715d4a4d 100644 --- a/modules/dnn/test/test_tflite_importer.cpp +++ b/modules/dnn/test/test_tflite_importer.cpp @@ -93,7 +93,7 @@ TEST_P(Test_TFLite, face_landmark) { if (backend == DNN_BACKEND_CUDA && target == DNN_TARGET_CUDA_FP16) applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA_FP16); - double l1 = 2e-5, lInf = 2e-4; + double l1 = 2.2e-5, lInf = 2e-4; if (target == DNN_TARGET_CPU_FP16 || target == DNN_TARGET_CUDA_FP16 || target == DNN_TARGET_OPENCL_FP16 || target == DNN_TARGET_MYRIAD || (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL)) { diff --git a/modules/features/test/test_matchers_algorithmic.cpp b/modules/features/test/test_matchers_algorithmic.cpp index 203b640209..0679d9ce38 100644 --- a/modules/features/test/test_matchers_algorithmic.cpp +++ b/modules/features/test/test_matchers_algorithmic.cpp @@ -43,9 +43,6 @@ namespace opencv_test { namespace { -const string FEATURES2D_DIR = "features2d"; -const string IMAGE_FILENAME = "tsukuba.png"; - /****************************************************************************************\ * Algorithmic tests for descriptor matchers * \****************************************************************************************/ diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index ab5e5391ec..f0a668e4b5 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -83,6 +83,7 @@ if(WITH_WAYLAND AND HAVE_WAYLAND) endif() endif() + ocv_module_include_directories(${WAYLAND_CLIENT_INCLUDE_DIRS} ${XKBCOMMON_INCLUDE_DIRS}) elseif(HAVE_QT) set(OPENCV_HIGHGUI_BUILTIN_BACKEND "QT${QT_VERSION_MAJOR}") add_definitions(-DHAVE_QT) diff --git a/modules/highgui/include/opencv2/highgui.hpp b/modules/highgui/include/opencv2/highgui.hpp index 69e18ac1f8..c0a1f648de 100644 --- a/modules/highgui/include/opencv2/highgui.hpp +++ b/modules/highgui/include/opencv2/highgui.hpp @@ -532,16 +532,20 @@ control panel. Clicking the label of each trackbar enables editing the trackbar values manually. @param trackbarname Name of the created trackbar. -@param winname Name of the window that will be used as a parent of the created trackbar. -@param value Optional pointer to an integer variable whose value reflects the position of the -slider. Upon creation, the slider position is defined by this variable. -@param count Maximal position of the slider. The minimal position is always 0. -@param onChange Pointer to the function to be called every time the slider changes position. This -function should be prototyped as void Foo(int,void\*); , where the first parameter is the trackbar -position and the second parameter is the user data (see the next parameter). If the callback is -the NULL pointer, no callbacks are called, but only value is updated. -@param userdata User data that is passed as is to the callback. It can be used to handle trackbar -events without using global variables. +@param winname Name of the window that will contain the trackbar. +@param value Pointer to the integer value that will be changed by the trackbar. +Pass `nullptr` if the value pointer is not used. In this case, manually handle +the trackbar position in the callback function. +@param count Maximum position of the trackbar. +@param onChange Pointer to the function to be called every time the slider changes position. +This function should have the prototype void Foo(int, void\*);, where the first parameter is +the trackbar position, and the second parameter is the user data (see the next parameter). +If the callback is a nullptr, no callbacks are called, but the trackbar's value will still be +updated automatically. +@param userdata Optional user data that is passed to the callback. +@note If the `value` pointer is `nullptr`, the trackbar position must be manually managed. +Call the callback function manually with the desired initial value to avoid runtime warnings. +@see \ref tutorial_trackbar */ CV_EXPORTS int createTrackbar(const String& trackbarname, const String& winname, int* value, int count, diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index fb95ca929b..321269bd05 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -219,7 +219,7 @@ void cv::setWindowProperty(const String& name, int prop_id, double prop_value) //change between fullscreen or not. case cv::WND_PROP_FULLSCREEN: - if (prop_value != cv::WINDOW_NORMAL && prop_value != cv::WINDOW_FULLSCREEN) // bad argument + if ((int)prop_value != cv::WINDOW_NORMAL && (int)prop_value != cv::WINDOW_FULLSCREEN) // bad argument break; #if defined (HAVE_QT) diff --git a/modules/highgui/src/window_cocoa.mm b/modules/highgui/src/window_cocoa.mm index c7ddd38004..84293dc652 100644 --- a/modules/highgui/src/window_cocoa.mm +++ b/modules/highgui/src/window_cocoa.mm @@ -595,14 +595,16 @@ cv::Rect cvGetWindowRect_COCOA( const char* name ) { CV_Error( cv::Error::StsNullPtr, "NULL window" ); } else { - NSRect rect = [window frame]; + @autoreleasepool { + NSRect rect = [window frame]; #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6 - NSPoint pt = [window convertRectToScreen:rect].origin; + NSPoint pt = [window convertRectToScreen:rect].origin; #else - NSPoint pt = [window convertBaseToScreen:rect.origin]; + NSPoint pt = [window convertBaseToScreen:rect.origin]; #endif - NSSize sz = [[[window contentView] image] size]; - result = cv::Rect(pt.x, pt.y, sz.width, sz.height); + NSSize sz = [[[window contentView] image] size]; + result = cv::Rect(pt.x, pt.y, sz.width, sz.height); + } } return result; } diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index d2de46a5c9..ba08a336b6 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -2108,7 +2108,7 @@ public: switch (prop) { case cv::WND_PROP_FULLSCREEN: - if (value != cv::WINDOW_NORMAL && value != cv::WINDOW_FULLSCREEN) // bad arg + if ((int)value != cv::WINDOW_NORMAL && (int)value != cv::WINDOW_FULLSCREEN) // bad arg break; setModeWindow_(window, value); return true; diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index 75a573c36d..7132275e45 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -2111,6 +2111,9 @@ static void showSaveDialog(CvWindow& window) #ifdef HAVE_JPEG "JPEG files (*.jpeg;*.jpg;*.jpe)\0*.jpeg;*.jpg;*.jpe\0" #endif +#ifdef HAVE_JPEGXL + "JPEG XL files (*.jxl)\0*.jxl\0" +#endif #ifdef HAVE_TIFF "TIFF Files (*.tiff;*.tif)\0*.tiff;*.tif\0" #endif @@ -2686,7 +2689,7 @@ public: switch ((WindowPropertyFlags)prop) { case WND_PROP_FULLSCREEN: - if (value != WINDOW_NORMAL && value != WINDOW_FULLSCREEN) // bad arg + if ((int)value != WINDOW_NORMAL && (int)value != WINDOW_FULLSCREEN) // bad arg break; setModeWindow_(window, (int)value); return true; diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 45b19da643..7f97dde391 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -23,6 +23,11 @@ if(HAVE_JPEG) list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES}) endif() +if(HAVE_JPEGXL) + ocv_include_directories(${OPENJPEG_INCLUDE_DIRS}) + list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES}) +endif() + if(HAVE_WEBP) add_definitions(-DHAVE_WEBP) ocv_include_directories(${WEBP_INCLUDE_DIR}) @@ -51,6 +56,12 @@ if(HAVE_TIFF) list(APPEND GRFMT_LIBS ${TIFF_LIBRARIES}) endif() +if(HAVE_JPEGXL) + ocv_include_directories(${JPEGXL_INCLUDE_DIRS}) + message(STATUS "JPEGXL_INCLUDE_DIRS: ${JPEGXL_INCLUDE_DIRS}") + list(APPEND GRFMT_LIBS ${JPEGXL_LIBRARIES}) +endif() + if(HAVE_OPENJPEG) ocv_include_directories(${OPENJPEG_INCLUDE_DIRS}) list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES}) @@ -78,7 +89,7 @@ if(HAVE_OPENEXR) endif() endif() -if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG) +if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG OR HAVE_JPEGXL) ocv_include_directories(${ZLIB_INCLUDE_DIRS}) list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES}) endif() @@ -88,6 +99,10 @@ if(HAVE_GDAL) list(APPEND GRFMT_LIBS ${GDAL_LIBRARY}) endif() +if(HAVE_IMGCODEC_GIF) + add_definitions(-DHAVE_IMGCODEC_GIF) +endif() + if(HAVE_IMGCODEC_HDR) add_definitions(-DHAVE_IMGCODEC_HDR) endif() diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 6cc785e07d..54a5dcfe08 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -111,8 +111,18 @@ enum ImwriteFlags { IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000. IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95. IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8. - IMWRITE_AVIF_SPEED = 514 //!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9. - }; + IMWRITE_AVIF_SPEED = 514,//!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9. + IMWRITE_JPEGXL_QUALITY = 640,//!< For JPEG XL, it can be a quality from 0 to 100 (the higher is the better). Default value is 95. If set, distance parameter is re-calicurated from quality level automatically. This parameter request libjxl v0.10 or later. + IMWRITE_JPEGXL_EFFORT = 641,//!< For JPEG XL, encoder effort/speed level without affecting decoding speed; it is between 1 (fastest) and 10 (slowest). Default is 7. + IMWRITE_JPEGXL_DISTANCE = 642,//!< For JPEG XL, distance level for lossy compression: target max butteraugli distance, lower = higher quality, 0 = lossless; range: 0 .. 25. Default is 1. + IMWRITE_JPEGXL_DECODING_SPEED = 643,//!< For JPEG XL, decoding speed tier for the provided options; minimum is 0 (slowest to decode, best quality/density), and maximum is 4 (fastest to decode, at the cost of some quality/density). Default is 0. + IMWRITE_GIF_LOOP = 1024,//!< For GIF, it can be a loop flag from 0 to 65535. Default is 0 - loop forever. + IMWRITE_GIF_SPEED = 1025,//!< For GIF, it is between 1 (slowest) and 100 (fastest). Default is 96. + IMWRITE_GIF_QUALITY = 1026, //!< For GIF, it can be a quality from 1 to 8. Default is 2. See cv::ImwriteGifCompressionFlags. + IMWRITE_GIF_DITHER = 1027, //!< For GIF, it can be a quality from -1(most dither) to 3(no dither). Default is 0. + IMWRITE_GIF_TRANSPARENCY = 1028, //!< For GIF, the alpha channel lower than this will be set to transparent. Default is 1. + IMWRITE_GIF_COLORTABLE = 1029 //!< For GIF, 0 means global color table is used, 1 means local color table is used. Default is 0. +}; enum ImwriteJPEGSamplingFactorParams { IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111, //!< 4x1,1x1,1x1 @@ -216,8 +226,50 @@ enum ImwriteHDRCompressionFlags { IMWRITE_HDR_COMPRESSION_RLE = 1 }; +//! Imwrite GIF specific values for IMWRITE_GIF_QUALITY parameter key, if larger than 3, then its related to the size of the color table. +enum ImwriteGIFCompressionFlags { + IMWRITE_GIF_FAST_NO_DITHER = 1, + IMWRITE_GIF_FAST_FLOYD_DITHER = 2, + IMWRITE_GIF_COLORTABLE_SIZE_8 = 3, + IMWRITE_GIF_COLORTABLE_SIZE_16 = 4, + IMWRITE_GIF_COLORTABLE_SIZE_32 = 5, + IMWRITE_GIF_COLORTABLE_SIZE_64 = 6, + IMWRITE_GIF_COLORTABLE_SIZE_128 = 7, + IMWRITE_GIF_COLORTABLE_SIZE_256 = 8 +}; + //! @} imgcodecs_flags +/** @brief Represents an animation with multiple frames. +The `Animation` struct is designed to store and manage data for animated sequences such as those from animated formats (e.g., GIF, AVIF, APNG, WebP). +It provides support for looping, background color settings, frame timing, and frame storage. +*/ +struct CV_EXPORTS_W_SIMPLE Animation +{ + //! Number of times the animation should loop. 0 means infinite looping. + CV_PROP_RW int loop_count; + //! Background color of the animation in BGRA format. + CV_PROP_RW Scalar bgcolor; + //! Duration for each frame in milliseconds. + CV_PROP_RW std::vector durations; + //! Vector of frames, where each Mat represents a single frame. + CV_PROP_RW std::vector frames; + + /** @brief Constructs an Animation object with optional loop count and background color. + + @param loopCount An integer representing the number of times the animation should loop: + - `0` (default) indicates infinite looping, meaning the animation will replay continuously. + - Positive values denote finite repeat counts, allowing the animation to play a limited number of times. + - If a negative value or a value beyond the maximum of `0xffff` (65535) is provided, it is reset to `0` + (infinite looping) to maintain valid bounds. + + @param bgColor A `Scalar` object representing the background color in BGRA format: + - Defaults to `Scalar()`, indicating an empty color (usually transparent if supported). + - This background color provides a solid fill behind frames that have transparency, ensuring a consistent display appearance. + */ + Animation(int loopCount = 0, Scalar bgColor = Scalar()); +}; + /** @brief Loads an image from a file. @anchor imread @@ -229,6 +281,7 @@ returns an empty matrix. Currently, the following file formats are supported: - Windows bitmaps - \*.bmp, \*.dib (always supported) +- GIF files - \*.gif (always supported) - JPEG files - \*.jpeg, \*.jpg, \*.jpe (see the *Note* section) - JPEG 2000 files - \*.jp2 (see the *Note* section) - Portable Network Graphics - \*.png (see the *Note* section) @@ -304,6 +357,38 @@ The function imreadmulti loads a specified range from a multi-page image from th */ CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector& mats, int start, int count, int flags = IMREAD_ANYCOLOR); +/** @example samples/cpp/tutorial_code/imgcodecs/animations.cpp +An example to show usage of cv::imreadanimation and cv::imwriteanimation functions. +Check @ref tutorial_animations "the corresponding tutorial" for more details +*/ + +/** @brief Loads frames from an animated image file into an Animation structure. + +The function imreadanimation loads frames from an animated image file (e.g., GIF, AVIF, APNG, WEBP) into the provided Animation struct. + +@param filename A string containing the path to the file. +@param animation A reference to an Animation structure where the loaded frames will be stored. It should be initialized before the function is called. +@param start The index of the first frame to load. This is optional and defaults to 0. +@param count The number of frames to load. This is optional and defaults to 32767. + +@return Returns true if the file was successfully loaded and frames were extracted; returns false otherwise. +*/ +CV_EXPORTS_W bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX); + +/** @brief Saves an Animation to a specified file. + +The function imwriteanimation saves the provided Animation data to the specified file in an animated format. +Supported formats depend on the implementation and may include formats like GIF, AVIF, APNG, or WEBP. + +@param filename The name of the file where the animation will be saved. The file extension determines the format. +@param animation A constant reference to an Animation struct containing the frames and metadata to be saved. +@param params Optional format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ...). +These parameters are used to specify additional options for the encoding process. Refer to `cv::ImwriteFlags` for details on possible parameters. + +@return Returns true if the animation was successfully saved; returns false otherwise. +*/ +CV_EXPORTS_W bool imwriteanimation(const String& filename, const Animation& animation, const std::vector& params = std::vector()); + /** @brief Returns the number of images inside the given file The function imcount returns the number of pages in a multi-page image (e.g. TIFF), the number of frames in an animation (e.g. AVIF), and 1 otherwise. @@ -326,6 +411,10 @@ can be saved using this function, with these exceptions: - With Radiance HDR encoder, non 64-bit float (CV_64F) images can be saved. - All images will be converted to 32-bit float (CV_32F). - With JPEG 2000 encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved. +- With JPEG XL encoder, 8-bit unsigned (CV_8U), 16-bit unsigned (CV_16U) and 32-bit float(CV_32F) images can be saved. + - JPEG XL images with an alpha channel can be saved using this function. + To do this, create 8-bit (or 16-bit, 32-bit float) 4-channel image BGRA, where the alpha channel goes last. + Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535/1.0. - With PAM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved. - With PNG encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved. - PNG images with an alpha channel can be saved using this function. To do this, create diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index 4752c6ece4..d3fb500604 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "opencv2/imgproc.hpp" #include "grfmt_avif.hpp" @@ -242,6 +243,8 @@ bool AvifDecoder::readData(Mat &img) { return false; } + m_animation.durations.push_back(decoder_->imageTiming.duration * 1000); + if (decoder_->image->exif.size > 0) { m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size); } @@ -297,16 +300,11 @@ bool AvifEncoder::isFormatSupported(int depth) const { bool AvifEncoder::write(const Mat &img, const std::vector ¶ms) { std::vector img_vec(1, img); - return writeToOutput(img_vec, params); + return writemulti(img_vec, params); } -bool AvifEncoder::writemulti(const std::vector &img_vec, - const std::vector ¶ms) { - return writeToOutput(img_vec, params); -} - -bool AvifEncoder::writeToOutput(const std::vector &img_vec, - const std::vector ¶ms) { +bool AvifEncoder::writeanimation(const Animation& animation, + const std::vector ¶ms) { int bit_depth = 8; int speed = AVIF_SPEED_FASTEST; for (size_t i = 0; i < params.size(); i += 2) { @@ -340,12 +338,12 @@ bool AvifEncoder::writeToOutput(const std::vector &img_vec, #endif encoder_->speed = speed; - const avifAddImageFlags flag = (img_vec.size() == 1) + const avifAddImageFlags flag = (animation.frames.size() == 1) ? AVIF_ADD_IMAGE_FLAG_SINGLE : AVIF_ADD_IMAGE_FLAG_NONE; std::vector images; std::vector imgs_scaled; - for (const cv::Mat &img : img_vec) { + for (const cv::Mat &img : animation.frames) { CV_CheckType( img.type(), (bit_depth == 8 && img.depth() == CV_8U) || @@ -358,13 +356,15 @@ bool AvifEncoder::writeToOutput(const std::vector &img_vec, images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth)); } - for (const AvifImageUniquePtr &image : images) { + + for (size_t i = 0; i < images.size(); i++) + { OPENCV_AVIF_CHECK_STATUS( - avifEncoderAddImage(encoder_, image.get(), /*durationInTimescale=*/1, - flag), + avifEncoderAddImage(encoder_, images[i].get(), animation.durations[i], flag), encoder_); } + encoder_->timescale = 1000; OPENCV_AVIF_CHECK_STATUS(avifEncoderFinish(encoder_, output.get()), encoder_); if (m_buf) { diff --git a/modules/imgcodecs/src/grfmt_avif.hpp b/modules/imgcodecs/src/grfmt_avif.hpp index e64357366a..87b765619e 100644 --- a/modules/imgcodecs/src/grfmt_avif.hpp +++ b/modules/imgcodecs/src/grfmt_avif.hpp @@ -41,17 +41,12 @@ class AvifEncoder CV_FINAL : public BaseImageEncoder { ~AvifEncoder() CV_OVERRIDE; bool isFormatSupported(int depth) const CV_OVERRIDE; - bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; - - bool writemulti(const std::vector& img_vec, - const std::vector& params) CV_OVERRIDE; + bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; private: - bool writeToOutput(const std::vector& img_vec, - const std::vector& params); avifEncoder* encoder_; }; diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index 6ae9d98f24..1e09882780 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -43,6 +43,7 @@ #include "grfmt_base.hpp" #include "bitstrm.hpp" +#include namespace cv { @@ -139,7 +140,22 @@ bool BaseImageEncoder::setDestination( std::vector& buf ) return true; } -bool BaseImageEncoder::writemulti(const std::vector&, const std::vector& ) +bool BaseImageEncoder::writemulti(const std::vector& img_vec, const std::vector& params) +{ + if(img_vec.size() > 1) + CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); + + Animation animation; + animation.frames = img_vec; + + for (size_t i = 0; i < animation.frames.size(); i++) + { + animation.durations.push_back(1000); + } + return writeanimation(animation, params); +} + +bool BaseImageEncoder::writeanimation(const Animation&, const std::vector& ) { return false; } diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index f6b5ba1b27..a90bd8a3de 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -133,6 +133,8 @@ public: */ virtual bool checkSignature(const String& signature) const; + const Animation& animation() const { return m_animation; }; + /** * @brief Create and return a new instance of the derived image decoder. * @return A new ImageDecoder object. @@ -151,6 +153,7 @@ protected: bool m_use_rgb; ///< Flag indicating whether to decode the image in RGB order. ExifReader m_exif; ///< Object for reading EXIF metadata from the image. size_t m_frame_count; ///< Number of frames in the image (for animations and multi-page images). + Animation m_animation; }; @@ -215,6 +218,8 @@ public: */ virtual bool writemulti(const std::vector& img_vec, const std::vector& params); + virtual bool writeanimation(const Animation& animation, const std::vector& params); + /** * @brief Get a description of the image encoder (e.g., the format it supports). * @return A string describing the encoder. diff --git a/modules/imgcodecs/src/grfmt_bmp.cpp b/modules/imgcodecs/src/grfmt_bmp.cpp index e69a93c78b..a8ca6a046a 100644 --- a/modules/imgcodecs/src/grfmt_bmp.cpp +++ b/modules/imgcodecs/src/grfmt_bmp.cpp @@ -208,7 +208,12 @@ bool BmpDecoder::readHeader() // in 32 bit case alpha channel is used - so require CV_8UC4 type m_type = iscolor ? ((m_bpp == 32 && m_rle_code != BMP_RGB) ? CV_8UC4 : CV_8UC3 ) : CV_8UC1; m_origin = m_height > 0 ? ORIGIN_BL : ORIGIN_TL; - m_height = std::abs(m_height); + if ( m_height == std::numeric_limits::min() ) { + // abs(std::numeric_limits::min()) is undefined behavior. + result = false; + } else { + m_height = std::abs(m_height); + } if( !result ) { diff --git a/modules/imgcodecs/src/grfmt_exr.cpp b/modules/imgcodecs/src/grfmt_exr.cpp index e93448e355..0ffd0d2618 100644 --- a/modules/imgcodecs/src/grfmt_exr.cpp +++ b/modules/imgcodecs/src/grfmt_exr.cpp @@ -235,6 +235,17 @@ bool ExrDecoder::readData( Mat& img ) ( (m_iscolor && !m_ischroma) || color) ? 3 : alphasupported ? 2 : 1 ); // number of channels to read may exceed channels in output img size_t xStride = floatsize * channelstoread; + // See https://github.com/opencv/opencv/issues/26705 + // If ALGO_HINT_ACCURATE is set, read BGR and swap to RGB. + // If ALGO_HINT_APPROX is set, read RGB directly. + bool doReadRGB = m_use_rgb; + bool doPostColorSwap = false; // After decoding, swap BGR to RGB + if(m_use_rgb && (getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE) ) + { + doReadRGB = false; + doPostColorSwap = true; + } + AutoBuffer copy_buffer; if( !justcopy ) @@ -373,7 +384,7 @@ bool ExrDecoder::readData( Mat& img ) if( m_iscolor ) { - if (m_use_rgb) + if (doReadRGB) { if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); @@ -397,7 +408,7 @@ bool ExrDecoder::readData( Mat& img ) if( chromatorgb ) { - if (m_use_rgb) + if (doReadRGB) ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep ); else ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); @@ -424,7 +435,7 @@ bool ExrDecoder::readData( Mat& img ) { if( chromatorgb ) { - if (m_use_rgb) + if (doReadRGB) ChromaToRGB( (float *)buffer, 1, defaultchannels, step ); else ChromaToBGR( (float *)buffer, 1, defaultchannels, step ); @@ -452,7 +463,7 @@ bool ExrDecoder::readData( Mat& img ) } if( color ) { - if (m_use_rgb) + if (doReadRGB) { if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) UpSampleY( data, defaultchannels, step / xstep, m_red->ySampling ); @@ -477,6 +488,11 @@ bool ExrDecoder::readData( Mat& img ) close(); + if(doPostColorSwap) + { + cvtColor( img, img, cv::COLOR_BGR2RGB ); + } + return result; } diff --git a/modules/imgcodecs/src/grfmt_gif.cpp b/modules/imgcodecs/src/grfmt_gif.cpp new file mode 100644 index 0000000000..05b27e4ddd --- /dev/null +++ b/modules/imgcodecs/src/grfmt_gif.cpp @@ -0,0 +1,1172 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level +// directory of this distribution and at http://opencv.org/license.html + +#include "precomp.hpp" +#include "grfmt_gif.hpp" +#include "opencv2/core/utils/logger.hpp" + +#ifdef HAVE_IMGCODEC_GIF +namespace cv +{ +////////////////////////////////////////////////////////////////////// +//// GIF Decoder //// +////////////////////////////////////////////////////////////////////// +GifDecoder::GifDecoder() { + m_signature = R"(GIF)"; + m_type = CV_8UC4; + bgColor = -1; + m_buf_supported = true; + globalColorTableSize = 0; + localColorTableSize = 0; + localColorTable.resize(3 * 256); // maximum size of a color table + lzwMinCodeSize = 0; + hasRead = false; + hasTransparentColor = false; + transparentColor = 0; + opMode = GRFMT_GIF_Nothing; + top = 0, left = 0, width = 0, height = 0; + depth = 8; + idx = 0; +} + +GifDecoder::~GifDecoder() { + close(); +} + +bool GifDecoder::readHeader() { + if (!m_buf.empty()) { + if (!m_strm.open(m_buf)) { + return false; + } + } else if (!m_strm.open(m_filename)) { + return false; + } + + String signature(6, ' '); + m_strm.getBytes((uchar*)signature.data(), 6); + CV_Assert(signature == R"(GIF87a)" || signature == R"(GIF89a)"); + + // #1: read logical screen descriptor + m_width = m_strm.getWord(); + m_height = m_strm.getWord(); + CV_Assert(m_width > 0 && m_height > 0); + + char flags = (char)m_strm.getByte(); + + // the background color -> index in the global color table, valid only if the global color table is present + bgColor = m_strm.getByte(); + m_strm.skip(1); // Skip the aspect ratio + + // #2: read global color table + depth = ((flags & 0x70) >> 4) + 1; + if (flags & 0x80) { + globalColorTableSize = 1 << ((flags & 0x07) + 1); + globalColorTable.resize(3 * globalColorTableSize); + for (int i = 0; i < 3 * globalColorTableSize; i++) { + globalColorTable[i] = (uchar)m_strm.getByte(); + } + } + + // get the frame count + bool success = getFrameCount_(); + + hasRead = false; + return success; +} + +bool GifDecoder::readData(Mat &img) { + if (hasRead) { + lastImage.copyTo(img); + return true; + } + + readExtensions(); + // Image separator + CV_Assert(!(m_strm.getByte()^0x2C)); + left = m_strm.getWord(); + top = m_strm.getWord(); + width = m_strm.getWord(); + height = m_strm.getWord(); + CV_Assert(width > 0 && height > 0 && left + width <= m_width && top + height <= m_height); + + imgCodeStream.resize(width * height); + Mat img_; + + switch (opMode) { + case GifOpMode::GRFMT_GIF_PreviousImage: + if (lastImage.empty()){ + img_ = Mat::zeros(m_height, m_width, CV_8UC4); + } else { + img_ = lastImage; + } + break; + case GifOpMode::GRFMT_GIF_Background: + // background color is valid iff global color table exists + CV_Assert(globalColorTableSize > 0); + if (hasTransparentColor && transparentColor == bgColor) { + img_ = Mat(m_height, m_width, CV_8UC4, + Scalar(globalColorTable[bgColor * 3 + 2], + globalColorTable[bgColor * 3 + 1], + globalColorTable[bgColor * 3], 0)); + } else { + img_ = Mat(m_height, m_width, CV_8UC4, + Scalar(globalColorTable[bgColor * 3 + 2], + globalColorTable[bgColor * 3 + 1], + globalColorTable[bgColor * 3], 255)); + } + break; + case GifOpMode::GRFMT_GIF_Nothing: + case GifOpMode::GRFMT_GIF_Cover: + // default value + img_ = Mat::zeros(m_height, m_width, CV_8UC4); + break; + default: + CV_Assert(false); + } + lastImage.release(); + + auto flags = (uchar)m_strm.getByte(); + if (flags & 0x80) { + // local color table + localColorTableSize = 1 << ((flags & 0x07) + 1); + for (int i = 0; i < 3 * localColorTableSize; i++) { + localColorTable[i] = (uchar)m_strm.getByte(); + } + } else if (globalColorTableSize) { + /* + * According to the GIF Specification at https://www.w3.org/Graphics/GIF/spec-gif89a.txt: + * "Both types of color tables are optional, making it possible for a Data Stream to contain + * numerous graphics without a color table at all." + * The specification recommended that the decoder save the last Global Color Table used + * until another Global Color Table is encountered, here we also save the last Local Color Table used + * in case of there is no such thing as "last Global Color Table used". Thus, we only refresh the + * Local Color Table when a Global Color Table or last Global Color Table used is present. + */ + localColorTableSize = 0; + } + + // lzw decompression to get the code stream + hasRead = lzwDecode(); + + // convert code stream into pixels on the image + if (hasRead) { + idx = 0; + if (!(flags & 0x40)) { + // no interlace, simply convert the code stream into pixels from top to down + code2pixel(img_, 0, 1); + } else { + // consider the interlace mode, the image will be rendered in four separate passes + code2pixel(img_, 0, 8); + code2pixel(img_, 4, 8); + code2pixel(img_, 2, 4); + code2pixel(img_, 1, 2); + } + } + + lastImage = img_; + if (!img.empty()) { + if (img.channels() == 3){ + if (m_use_rgb) { + cvtColor(img_, img, COLOR_BGRA2RGB); + } else { + cvtColor(img_, img, COLOR_BGRA2BGR); + } + } else { + if (m_use_rgb) { + cvtColor(img_, img, COLOR_BGRA2RGBA); + } else { + img_.copyTo(img); + } + } + } + + // release the memory + img_.release(); + + return hasRead; +} + +bool GifDecoder::nextPage() { + if (hasRead) { + hasRead = false; + // end of a gif file + if(!(m_strm.getByte() ^ 0x3B)) return false; + m_strm.setPos(m_strm.getPos() - 1); + return true; + } else { + bool success; + try { + Mat emptyImg; + success = readData(emptyImg); + emptyImg.release(); + } catch(...) { + return false; + } + return success; + } +} + +void GifDecoder::readExtensions() { + uchar len; + while (!(m_strm.getByte() ^ 0x21)) { + auto extensionType = (uchar)m_strm.getByte(); + + // read graphic control extension + // the scope of this extension is the next image or plain text extension + if (!(extensionType ^ 0xF9)) { + hasTransparentColor = false; + opMode = GifOpMode::GRFMT_GIF_Nothing;// default value + len = (uchar)m_strm.getByte(); + CV_Assert(len == 4); + auto flags = (uchar)m_strm.getByte(); + m_animation.durations.push_back(m_strm.getWord() * 10); // delay time + opMode = (GifOpMode)((flags & 0x1C) >> 2); + hasTransparentColor = flags & 0x01; + transparentColor = (uchar)m_strm.getByte(); + } + + // skip other kinds of extensions + len = (uchar)m_strm.getByte(); + while (len) { + m_strm.skip(len); + len = (uchar)m_strm.getByte(); + } + } + // roll back to the block identifier + m_strm.setPos(m_strm.getPos() - 1); +} + +void GifDecoder::code2pixel(Mat& img, int start, int k){ + for (int i = start; i < height; i += k) { + for (int j = 0; j < width; j++) { + uchar colorIdx = imgCodeStream[idx++]; + if (hasTransparentColor && colorIdx == transparentColor) { + if (opMode != GifOpMode::GRFMT_GIF_PreviousImage) { + if (colorIdx < localColorTableSize) { + img.at(top + i, left + j) = + Vec4b(localColorTable[colorIdx * 3 + 2], // B + localColorTable[colorIdx * 3 + 1], // G + localColorTable[colorIdx * 3], // R + 0); // A + } else if (colorIdx < globalColorTableSize) { + img.at(top + i, left + j) = + Vec4b(globalColorTable[colorIdx * 3 + 2], // B + globalColorTable[colorIdx * 3 + 1], // G + globalColorTable[colorIdx * 3], // R + 0); // A + } else { + img.at(top + i, left + j) = Vec4b(0, 0, 0, 0); + } + } + continue; + } + if (colorIdx < localColorTableSize) { + img.at(top + i, left + j) = + Vec4b(localColorTable[colorIdx * 3 + 2], // B + localColorTable[colorIdx * 3 + 1], // G + localColorTable[colorIdx * 3], // R + 255); // A + } else if (colorIdx < globalColorTableSize) { + img.at(top + i, left + j) = + Vec4b(globalColorTable[colorIdx * 3 + 2], // B + globalColorTable[colorIdx * 3 + 1], // G + globalColorTable[colorIdx * 3], // R + 255); // A + } else if (!(localColorTableSize || globalColorTableSize)) { + /* + * According to the GIF Specification at https://www.w3.org/Graphics/GIF/spec-gif89a.txt: + * "If no color table is available at all, the decoder is free to use a system color table + * or a table of its own. In that case, the decoder may use a color table with as many colors + * as its hardware is able to support; it is recommended that such a table have black and + * white as its first two entries, so that monochrome images can be rendered adequately." + */ + uchar intensity = colorIdx ^ 1 ? colorIdx : 255; + img.at(top + i, left + j) = + Vec4b(intensity, intensity, intensity, 255); + } else { + CV_Assert(false); + } + } + } +} + +bool GifDecoder::lzwDecode() { + // initialization + lzwMinCodeSize = m_strm.getByte(); + const int lzwMaxSize = (1 << 12); // 4096 is the maximum size of the LZW table (12 bits) + int lzwCodeSize = lzwMinCodeSize + 1; + int clearCode = 1 << lzwMinCodeSize; + int exitCode = clearCode + 1; + CV_Assert(lzwCodeSize > 2 && lzwCodeSize <= 12); + std::vector lzwExtraTable(lzwMaxSize + 1); + int colorTableSize = clearCode; + int lzwTableSize = exitCode; + + idx = 0; + int leftBits = 0; + uint32_t src = 0; + auto blockLen = (uchar)m_strm.getByte(); + while (blockLen) { + if (leftBits < lzwCodeSize) { + src |= m_strm.getByte() << leftBits; + blockLen --; + leftBits += 8; + } + + while (leftBits >= lzwCodeSize) { + // get the code + uint16_t code = src & ((1 << lzwCodeSize) - 1); + src >>= lzwCodeSize; + leftBits -= lzwCodeSize; + + // clear code + if (!(code ^ clearCode)) { + lzwExtraTable.clear(); + lzwExtraTable.resize(lzwMaxSize + 1); + // reset the code size, the same as that in the initialization part + lzwCodeSize = lzwMinCodeSize + 1; + lzwTableSize = exitCode; + continue; + } + // end of information + if (!(code ^ exitCode)) { + lzwExtraTable.clear(); + lzwCodeSize = lzwMinCodeSize + 1; + lzwTableSize = exitCode; + break; + } + + // check if the code stream is full + if (idx >= width * height) { + return idx == width * height && blockLen == 0 && !m_strm.getByte(); + } + + // output code + // 1. renew the lzw extra table + // * notice that if the lzw table size is full, + // * we should use the old table until a clear code is encountered + if (lzwTableSize < lzwMaxSize) { + if (code < colorTableSize) { + lzwExtraTable[lzwTableSize].suffix = (uchar)code; + lzwTableSize ++; + lzwExtraTable[lzwTableSize].prefix.clear(); + lzwExtraTable[lzwTableSize].prefix.push_back((uchar)code); + lzwExtraTable[lzwTableSize].length = 2; + } else if (code <= lzwTableSize) { + lzwExtraTable[lzwTableSize].suffix = lzwExtraTable[code].prefix[0]; + lzwTableSize ++; + lzwExtraTable[lzwTableSize].prefix = lzwExtraTable[code].prefix; + lzwExtraTable[lzwTableSize].prefix.push_back(lzwExtraTable[code].suffix); + lzwExtraTable[lzwTableSize].length = lzwExtraTable[code].length + 1; + } else { + return false; + } + } + + // 2. output to the code stream + if (code < colorTableSize) { + imgCodeStream[idx++] = (uchar)code; + } else { + for (int i = 0; i < lzwExtraTable[code].length - 1; i++) { + imgCodeStream[idx++] = lzwExtraTable[code].prefix[i]; + } + imgCodeStream[idx++] = lzwExtraTable[code].suffix; + } + + // check if the code size is full + if (lzwTableSize > lzwMaxSize) { + return false; + } + + // check if the bit length is full + if (lzwTableSize == (1 << lzwCodeSize)) { + lzwCodeSize < 12 ? lzwCodeSize++ : lzwCodeSize; + } + } + + // go to the next block if this block has been read out + if (!blockLen) { + blockLen = (uchar)m_strm.getByte(); + } + } + + return idx == width * height; +} + +ImageDecoder GifDecoder::newDecoder() const { + return makePtr(); +} + +void GifDecoder::close() { + while (!lastImage.empty()) lastImage.release(); + m_strm.close(); +} + +bool GifDecoder::getFrameCount_() { + m_frame_count = 0; + auto type = (uchar)m_strm.getByte(); + while (type != 0x3B) { + if (!(type ^ 0x21)) { + // skip all kinds of the extensions + int extension = m_strm.getByte(); + // Application Extension need to be handled for the loop count + if (extension == 0xFF) { + int len = m_strm.getByte(); + while (len) { + if (len == 3) { + if (m_strm.getByte() == 0x01) { + m_animation.loop_count = m_strm.getWord(); + } else { + // this branch should not be reached in normal cases + m_strm.skip(2); + CV_LOG_WARNING(NULL, "found Unknown Application Extension"); + } + } else { + m_strm.skip(len); + } + len = m_strm.getByte(); + } + } else { + // if it does not belong to any of the extension type mentioned in the GIF Specification + if (extension != 0xF9 && extension != 0xFE && extension != 0x01) { + CV_LOG_WARNING(NULL, "found Unknown Extension Type: " + std::to_string(extension)); + } + int len = m_strm.getByte(); + while (len) { + m_strm.skip(len); + len = m_strm.getByte(); + } + } + } else if (!(type ^ 0x2C)) { + // skip image data + m_frame_count ++; + // skip left, top, width, height + m_strm.skip(8); + int flags = m_strm.getByte(); + // skip local color table + if (flags & 0x80) { + m_strm.skip(3 * (1 << ((flags & 0x07) + 1))); + } + // skip lzw min code size + m_strm.skip(1); + int len = m_strm.getByte(); + while (len) { + m_strm.skip(len); + len = m_strm.getByte(); + } + } else { + CV_Assert(false); + } + type = (uchar)m_strm.getByte(); + } + // roll back to the block identifier + m_strm.setPos(0); + return skipHeader(); +} + +bool GifDecoder::skipHeader() { + String signature(6, ' '); + m_strm.getBytes((uchar *) signature.data(), 6); + // skip height and width + m_strm.skip(4); + char flags = (char) m_strm.getByte(); + // skip the background color and the aspect ratio + m_strm.skip(2); + // skip the global color table + if (flags & 0x80) { + m_strm.skip(3 * (1 << ((flags & 0x07) + 1))); + } + return signature == R"(GIF87a)" || signature == R"(GIF89a)"; +} + +} // namespace cv + +namespace cv +{ +////////////////////////////////////////////////////////////////////// +//// GIF Encoder //// +////////////////////////////////////////////////////////////////////// +static const char* fmtGifHeader = "GIF89a"; +GifEncoder::GifEncoder() { + m_description = "Graphics Interchange Format 89a(*.gif)"; + m_height = 0, m_width = 0; + width = 0, height = 0, top = 0, left = 0; + m_buf_supported = true; + opMode = GRFMT_GIF_Cover; + transparentColor = 0; // index of the transparent color, default 0. currently it is a constant number + transparentRGB = Vec3b(0, 0, 0); // the transparent color, default black + lzwMaxCodeSize = 12; // the maximum code size, default 12. currently it is a constant number + + // default value of the params + fast = true; + loopCount = 0; // infinite loops by default + criticalTransparency = 1; // critical transparency, default 1, range from 0 to 255, 0 means no transparency + frameDelay = 5; // 20fps by default, 10ms per unit + bitDepth = 8; // the number of bits per pixel, default 8, currently it is a constant number + lzwMinCodeSize = 8; // the minimum code size, default 8, this changes as the color number changes + colorNum = 256; // the number of colors in the color table, default 256 + dithering = 0; // the level dithering, default 0 + globalColorTableSize = 256, localColorTableSize = 0; +} + +GifEncoder::~GifEncoder() { + close(); +} + +bool GifEncoder::isFormatSupported(int depth) const { + return depth == CV_8U; +} + +bool GifEncoder::write(const Mat &img, const std::vector ¶ms) { + std::vector img_vec(1, img); + return writemulti(img_vec, params); +} + +bool GifEncoder::writeanimation(const Animation& animation, const std::vector& params) { + if (animation.frames.empty()) { + return false; + } + + if (m_buf) { + if (!strm.open(*m_buf)) { + return false; + } + } else if (!strm.open(m_filename)) { + return false; + } + + loopCount = animation.loop_count; + + // confirm the params + for (size_t i = 0; i < params.size(); i += 2) { + switch (params[i]) { + case IMWRITE_GIF_LOOP: + loopCount = std::min(std::max(params[i + 1], 0), 65535); // loop count is in 2 bytes + break; + case IMWRITE_GIF_SPEED: + frameDelay = 100 - std::min(std::max(params[i + 1] - 1, 0), 99); // from 10ms to 1000ms + break; + case IMWRITE_GIF_DITHER: + dithering = std::min(std::max(params[i + 1], -1), 3); + fast = false; + break; + case IMWRITE_GIF_TRANSPARENCY: + criticalTransparency = (uchar)std::min(std::max(params[i + 1], 0), 255); + break; + case IMWRITE_GIF_COLORTABLE: + localColorTableSize = std::min(std::max(params[i + 1], 0), 1); + break; + case IMWRITE_GIF_QUALITY: + switch (params[i + 1]) { + case IMWRITE_GIF_FAST_FLOYD_DITHER: + fast = true; + dithering = GRFMT_GIF_FloydSteinberg; + break; + case IMWRITE_GIF_FAST_NO_DITHER: + fast = true; + dithering = GRFMT_GIF_None; + break; + default: + lzwMinCodeSize = std::min(std::max(params[i + 1], 3), 8); + colorNum = 1 << lzwMinCodeSize; + globalColorTableSize = colorNum; + fast = false; + break; + } + break; // case IMWRITE_GIF_QUALITY + } + } + if (criticalTransparency) { + lzwMinCodeSize = std::min(8, lzwMinCodeSize + 1); + colorNum = 1 << lzwMinCodeSize; + globalColorTableSize = colorNum; + } + localColorTableSize = localColorTableSize ? colorNum : 0; + + std::vector img_vec_; + if (fast) { + const uchar transparent = 0x92; // 1001_0010: the middle of the color table + if (dithering == GRFMT_GIF_None) { + img_vec_ = animation.frames; + transparentColor = transparent; + } else { + localColorTableSize = 0; + int transRGB; + const int depth = 3 << 8 | 3 << 4 | 2; // r:g:b = 3:3:2 + for (auto &img: animation.frames) { + Mat img_(img.size(), img.type()); + transRGB = ditheringKernel(img, img_, depth, criticalTransparency); + if (transRGB >= 0) { + transparentRGB = Vec3b((transRGB >> 16) & 0xFF, (transRGB >> 8) & 0xFF, transRGB & 0xFF); + transparentColor = transparent; + } + img_vec_.push_back(img_); + } + if (transparentColor == 0) { + criticalTransparency = 0; + } + } + } else if (dithering != GRFMT_GIF_None) { + int depth = (int)floor(log2(colorNum) / 3) + dithering; + depth = depth << 8 | depth << 4 | depth; + for (auto &img : animation.frames) { + Mat img_(img.size(), img.type()); + ditheringKernel(img, img_, depth, criticalTransparency); + img_vec_.push_back(img_); + } + } else { + img_vec_ = animation.frames; + } + bool result = writeHeader(img_vec_); + if (!result) { + strm.close(); + return false; + } + + for (size_t i = 0; i < img_vec_.size(); i++) { + frameDelay = cvRound(animation.durations[i] / 10); + result = writeFrame(img_vec_[i]); + } + + strm.putByte(0x3B); // trailer + strm.close(); + return result; +} + +ImageEncoder GifEncoder::newEncoder() const { + return makePtr(); +} + +bool GifEncoder::writeFrame(const Mat &img) { + if (img.empty()) { + return false; + } + height = m_height, width = m_width; + + // graphic control extension + strm.putByte(0x21); // extension introducer + strm.putByte(0xF9); // graphic control label + strm.putByte(0x04); // block size, fixed number + // flag is a packed field, and the first 3 bits are reserved + uchar flag = opMode << 2; + if (criticalTransparency) + flag |= 1; + strm.putByte(flag); + strm.putWord(frameDelay); + strm.putByte(transparentColor); + strm.putByte(0x00); // end of the extension + + // image descriptor + strm.putByte(0x2C); // image separator + strm.putWord(left); + strm.putWord(top); + strm.putWord(width); + strm.putWord(height); + flag = localColorTableSize > 0 ? 0x80 : 0x00; + if (localColorTableSize > 0) { + std::vector img_vec(1, img); + getColorTable(img_vec, false); + } + flag |= lzwMinCodeSize - 1; + strm.putByte(flag); + if (localColorTableSize > 0) { + strm.putBytes(localColorTable.data(), localColorTableSize * 3); + } + + imgCodeStream.resize(width * height); + bool result = pixel2code(img); + if (result) result = lzwEncode(); + + return result; +} + +bool GifEncoder::lzwEncode() { + strm.putByte(lzwMinCodeSize); + int lzwCodeSize = lzwMinCodeSize + 1; + // add clear code to the head of the output stream + int bitLeft = lzwCodeSize; + size_t output = (size_t)1 << lzwMinCodeSize; + + lzwTable.resize((1 << 12) * 256); + // clear lzwTable + memset(lzwTable.data(), 0, (1 << 20) * sizeof(int16_t)); // 20 = 12 + 8 = 2^12(max lzw table size) * 256 + + // next code + auto idx = (int16_t)((1 << lzwMinCodeSize) + 2); + + int bufferLen = 0; + uchar buffer[256]; + + //initialize + int32_t prev = imgCodeStream[0]; + + for (int64_t i = 1; i < height * width; i++) { + // add the output code to the output buffer + while (bitLeft >= 8) { + buffer[bufferLen++] = (uchar)output; + output >>= 8; + bitLeft -= 8; + if(bufferLen == 255) { + strm.putByte(255); + strm.putBytes(buffer, 255); + bufferLen = 0; + } + } + + uchar c = imgCodeStream[i]; + // prev + currentCode(c) is not in the table + if(lzwTable[prev * 256 + c] == 0){ + output |= ((size_t)prev << bitLeft); + bitLeft += lzwCodeSize; + lzwTable[prev * 256 + c] = idx; + prev = c; + // check if the bit length is full + if(idx == (1 << lzwCodeSize)){ + lzwCodeSize ++; + } + idx ++; + // if the lzwTable is full, add clear code to the output + if(idx == (1 << lzwMaxCodeSize)){ + output |= (((size_t)1 << lzwMinCodeSize) << bitLeft); + bitLeft += lzwCodeSize; + memset(lzwTable.data(), 0, (1 << 20) * sizeof(int16_t)); // clear lzwTable + // next code + idx = (int16_t)((1 << lzwMinCodeSize) + 2); + lzwCodeSize = lzwMinCodeSize + 1; + } + } else{ + prev = lzwTable[prev * 256 + c]; + } + } + + // end of the code + output |= ((size_t)prev << bitLeft); + bitLeft += lzwCodeSize; + output |= ((((size_t)1 << lzwMinCodeSize) | 1) << bitLeft); + bitLeft += lzwCodeSize; + while (bitLeft >= 8) { + buffer[bufferLen++] = (uchar)output; + output >>= 8; + bitLeft -= 8; + if(bufferLen == 255) { + strm.putByte(255); + strm.putBytes(buffer, 255); + bufferLen = 0; + } + } + if (bitLeft > 0) { + buffer[bufferLen++] = (uchar)output; + } + if (bufferLen > 0){ + strm.putByte(bufferLen); + strm.putBytes(buffer, bufferLen); + } + // end of the block + strm.putByte(0); + + return true; +} + +bool GifEncoder::writeHeader(const std::vector& img_vec) { + strm.putBytes(fmtGifHeader, (int)strlen(fmtGifHeader)); + + if (img_vec[0].empty()) { + return false; + } + m_width = img_vec[0].cols, m_height = img_vec[0].rows; + if (m_width <= 0 || m_height <= 0 || m_width > 65535 || m_height > 65535) { + return false; + } + strm.putWord(m_width); + strm.putWord(m_height); + + // by default, set the global color table + uchar flags = (globalColorTableSize > 0) << 7; // global color table flag + getColorTable(img_vec, true); + flags |= (bitDepth - 1) << 4; // bit depth + flags |= (lzwMinCodeSize - 1); // global color table size + strm.putByte(flags); + strm.putByte(0); // background color, default value + strm.putByte(0); // aspect ratio, default value + if (globalColorTableSize > 0) { + strm.putBytes(globalColorTable.data(), globalColorTableSize * 3); + } + + + // add application extension to set the loop count + strm.putByte(0x21); // GIF extension code + strm.putByte(0xFF); // application extension table + strm.putByte(0x0B); // length of application block, in decimal is 11 + strm.putBytes(R"(NETSCAPE2.0)", 11); // application authentication code + strm.putByte(0x03); // length of application block, in decimal is 3 + strm.putByte(0x01); // identifier + strm.putWord(loopCount); + strm.putByte(0x00); // end of the extension + + return true; +} + +bool GifEncoder::pixel2code(const Mat &img) { + if(img.empty()) return false; + CV_Assert(img.rows == (top + height) && img.cols == (left + width)); + + if (fast) { + if (img.type() == CV_8UC3) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + uchar colorIdx = (img.at(i, j)[2] & 0xe0) | + ((img.at(i, j)[1] >> 3) & 0x1c) | + ((img.at(i, j)[0] >> 6) & 0x03); + if (criticalTransparency && colorIdx == transparentColor) { + imgCodeStream[i * width + j] = + transparentColor - 4; // 4 means the minimum color change of green channel + } else { + imgCodeStream[i * width + j] = colorIdx; + } + } + } + } else if (img.type() == CV_8UC4) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (img.at(i, j)[3] < criticalTransparency) { + imgCodeStream[i * width + j] = transparentColor; + continue; + } + uchar colorIdx = (img.at(i, j)[2] & 0xe0) | + ((img.at(i, j)[1] >> 3) & 0x1c) | + ((img.at(i, j)[0] >> 6) & 0x03); + if (criticalTransparency && colorIdx == transparentColor) { + imgCodeStream[i * width + j] = + transparentColor - 4; // 4 means the minimum color change of green channel + } else { + imgCodeStream[i * width + j] = colorIdx; + } + } + } + } else { + CV_Assert(false); + } + return true; + } + + // turn the image into the code stream and set the colorNum + CV_Assert(colorNum <= 256 && (colorNum <= localColorTableSize || colorNum <= globalColorTableSize)); + OctreeColorQuant quant = localColorTableSize > 0 ? quantL : quantG; + + if (img.type() == CV_8UC3) { + for(int i = 0; i < height; i++){ + for(int j = 0; j < width; j++){ + // set codeStream + imgCodeStream[i * width + j] = quant.getLeaf(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else if (img.type() == CV_8UC4) { + for(int i = 0; i < height; i++){ + for(int j = 0; j < width; j++){ + if (img.at(i, j)[3] < criticalTransparency) { + imgCodeStream[i * width + j] = transparentColor; + continue; + } + imgCodeStream[i * width + j] = quant.getLeaf(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else { + CV_Assert(false); + } + return true; +} + +void GifEncoder::getColorTable(const std::vector &img_vec, bool isGlobal) { + // generate the global/local color table (color quantification) + if (img_vec.empty()) return; + CV_Assert(isGlobal || img_vec.size() == 1); + if (fast) { + globalColorTable.resize(colorNum * 3); + for (int i = 0; i < 256; i++) { + globalColorTable[i * 3] = ((i >> 5) & 7) * 36; + globalColorTable[i * 3 + 1] = ((i >> 2) & 7) * 36; + globalColorTable[i * 3 + 2] = (i & 3) * 85; + } + globalColorTable[transparentColor * 3] = transparentRGB[0]; + globalColorTable[transparentColor * 3 + 1] = transparentRGB[1]; + globalColorTable[transparentColor * 3 + 2] = transparentRGB[2]; + return; + } + if (isGlobal) { + quantG = OctreeColorQuant(colorNum, bitDepth, criticalTransparency); + quantG.addMats(img_vec); + globalColorTable.resize(colorNum * 3); + quantG.getPalette(globalColorTable.data()); + } else { + quantL = OctreeColorQuant(colorNum, bitDepth, criticalTransparency); + quantL.addMats(img_vec); + localColorTable.resize(colorNum * 3); + quantL.getPalette(localColorTable.data()); + } +} + +int GifEncoder::ditheringKernel(const Mat &img, Mat &img_, int depth, uchar criticalTransparency) { + int transparentRGB = -1; + if (img.empty()) { + return -1; + } else if (img.type() == CV_8UC3){ + Mat error = Mat::zeros(img.rows + 2, img.cols + 2, CV_32FC3); + int constant_r = 255 / ((1 << ((depth >> 8) & 0xf)) - 1); + int constant_g = 255 / ((1 << ((depth >> 4) & 0xf)) - 1); + int constant_b = 255 / ((1 << ((depth) & 0xf)) - 1); + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + Vec3f old_pixel = (Vec3f)img.at(i, j) + error.at(i + 1, j + 1); + Vec3b new_pixel; + new_pixel[0] = (uchar)(std::lround(std::min(std::max(old_pixel[0], 0.0f), 255.0f) / (float)constant_b) * constant_b); + new_pixel[1] = (uchar)(std::lround(std::min(std::max(old_pixel[1], 0.0f), 255.0f) / (float)constant_g) * constant_g); + new_pixel[2] = (uchar)(std::lround(std::min(std::max(old_pixel[2], 0.0f), 255.0f) / (float)constant_r) * constant_r); + img_.at(i, j) = new_pixel; + Vec3f diff = old_pixel - (Vec3f)new_pixel; + error.at(i + 1, j + 2) += diff * 7 / 16; // (i, j + 1) + error.at(i + 2, j) += diff * 3 / 16; // (i + 1, j - 1) + error.at(i + 2, j + 1) += diff * 5 / 16; // (i + 1, j) + error.at(i + 2, j + 2) += diff / 16; // (i + 1, j + 1) + } + } + } else if (img.type() == CV_8UC4) { + Mat error = Mat::zeros(img.rows + 2, img.cols + 2, CV_32FC4); + int constant_r = 255 / ((1 << ((depth >> 8) & 0xf)) - 1); + int constant_g = 255 / ((1 << ((depth >> 4) & 0xf)) - 1); + int constant_b = 255 / ((1 << ((depth) & 0xf)) - 1); + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + // transparent color should not be dithered + if (img.at(i, j)[3] < criticalTransparency) { + transparentRGB = (img.at(i, j)[2] << 16) | + (img.at(i, j)[1] << 8) | + (img.at(i, j)[0]); + img_.at(i, j) = img.at(i, j); + continue; + } + Vec4f old_pixel = (Vec4f)img.at(i, j) + error.at(i + 1, j + 1); + Vec4b new_pixel; + new_pixel[0] = (uchar)(std::lround(std::min(std::max(old_pixel[0], 0.0f), 255.0f) / (float)constant_b) * constant_b); + new_pixel[1] = (uchar)(std::lround(std::min(std::max(old_pixel[1], 0.0f), 255.0f) / (float)constant_g) * constant_g); + new_pixel[2] = (uchar)(std::lround(std::min(std::max(old_pixel[2], 0.0f), 255.0f) / (float)constant_r) * constant_r); + new_pixel[3] = img.at(i, j)[3]; + img_.at(i, j) = new_pixel; + Vec4f diff = old_pixel - (Vec4f)new_pixel; + error.at(i + 1, j + 2) += diff * 7 / 16; // (i, j + 1) + error.at(i + 2, j) += diff * 3 / 16; // (i + 1, j - 1) + error.at(i + 2, j + 1) += diff * 5 / 16; // (i + 1, j) + error.at(i + 2, j + 2) += diff / 16; // (i + 1, j + 1) + } + } + } else { + CV_Assert(false); + } + return transparentRGB; +} + +void GifEncoder::close() { + if (strm.isOpened()) { + strm.close(); + } +} + + +////////////////////////////////////////////////////////////////////// +//// Color Quantization //// +////////////////////////////////////////////////////////////////////// +GifEncoder::OctreeColorQuant::OctreeNode::OctreeNode() { + this->isLeaf = false; + level = 0; + index = 0; + for (auto &i: children) { + i = nullptr; + } + leaf = 0, pixelCount = 0; + redSum = greenSum = blueSum = 0; +} + +GifEncoder::OctreeColorQuant::OctreeColorQuant(int maxColors, int bitLength, uchar criticalTransparency) { + m_maxColors = maxColors; + m_bitLength = bitLength; + m_leafCount = criticalTransparency ? 1 : 0; + m_criticalTransparency = criticalTransparency; + root = std::make_shared(); + r = g = b = 0; + for (int i = 0; i < bitLength; i++) { + m_nodeList[i] = std::vector>(); + } +} + +void GifEncoder::OctreeColorQuant::addMat(const Mat &img) { + if (img.empty()) { + return; + } else if (img.type() == CV_8UC3) { + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + addColor(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else if (img.type() == CV_8UC4) { + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + if (img.at(i, j)[3] < m_criticalTransparency) { + r = img.at(i, j)[2]; + g = img.at(i, j)[1]; + b = img.at(i, j)[0]; + continue; + } + addColor(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else { + CV_Assert(false); + } +} + +void GifEncoder::OctreeColorQuant::addMats(const std::vector &img_vec) { + for (const auto& img: img_vec) { + addMat(img); + } + if (m_maxColors < m_leafCount) { + reduceTree(); + } +} + +void GifEncoder::OctreeColorQuant::addColor(int red, int green, int blue) { + std::shared_ptr node = root; + for (int level = 0; level < m_bitLength; level++) { + node -> pixelCount++; + node -> redSum += red; + node -> greenSum += green; + node -> blueSum += blue; + if(node -> isLeaf){ + break; + } + int shift = m_bitLength - level; + int index = ((red >> shift) & 1) << 2 | ((green >> shift) & 1) << 1 | ((blue >> shift) & 1); + if (node->children[index] == nullptr) { + node->children[index] = std::make_shared(); + m_nodeList[level].push_back(node->children[index]); + } + node = node->children[index]; + if (level == m_bitLength - 1){ + node -> pixelCount++; + node -> redSum += red; + node -> greenSum += green; + node -> blueSum += blue; + } + } + if (!(node -> isLeaf)) { + m_leafCount++; + node -> isLeaf = true; + } +} + +// return the relative index of the leaf node +uchar GifEncoder::OctreeColorQuant::getLeaf(uchar red, uchar green, uchar blue) { + std::shared_ptr node = root; + for (int level = 0; level <= m_bitLength; level++) { + if (node->isLeaf) { + break; + } + int shift = m_bitLength - level; + int index = ((red >> shift) & 1) << 2 | ((green >> shift) & 1) << 1 | ((blue >> shift) & 1); + if (node->children[index] == nullptr) { + CV_Assert(false); + } + node = node->children[index]; + } + return node->index; +} + +// get the palette +int GifEncoder::OctreeColorQuant::getPalette(uchar* colorTable) { + CV_Assert(colorTable != nullptr); + uchar index = 0; + if (m_criticalTransparency) { + colorTable[index * 3] = r; + colorTable[index * 3 + 1] = g; + colorTable[index * 3 + 2] = b; + index++; + } + for (int i = 0; i < m_bitLength; i++) { + for (const auto& node : m_nodeList[i]) { + if (node -> isLeaf) { + colorTable[index * 3] = (uchar)(node -> redSum / node -> pixelCount); + colorTable[index * 3 + 1] = (uchar)(node -> greenSum / node -> pixelCount); + colorTable[index * 3 + 2] = (uchar)(node -> blueSum / node -> pixelCount); + node -> index = index++; + } + if (index == m_leafCount) { + break; + } + } + } + return m_leafCount; +} + +void GifEncoder::OctreeColorQuant::reduceTree() { + // reduce to max color + int level = 0; + for (int i = 0; i < m_bitLength; i++) { + auto size = (int32_t)m_nodeList[i].size() + 1; + if (m_maxColors < size) { + level = i - 1; + break; + } + } + for (const auto& node : m_nodeList[level + 1]) { + recurseReduce(node); + } + + while(m_maxColors < m_leafCount) { + int minPixelCount = INT_MAX; + std::shared_ptr minNode = nullptr; + for (const auto& node : m_nodeList[level]) { + if (node->pixelCount < minPixelCount && !(node->isLeaf)) { + minPixelCount = node->pixelCount; + minNode = node; + } + } + CV_Assert(minNode != nullptr); + recurseReduce(minNode); + } +} + +void GifEncoder::OctreeColorQuant::recurseReduce(const std::shared_ptr& node) { + // reduce all the children of the node + if (node == nullptr || node->isLeaf) { + return; + } + std::vector> stack; + stack.push_back(node); + + while (!stack.empty()) { + std::shared_ptr child = stack.back(); + stack.pop_back(); + if (child->isLeaf) { + m_leafCount--; + child->isLeaf = false; + } else { + for (int i = 0; i < m_bitLength; i++) { + if (child->children[i] != nullptr) { + stack.push_back(child->children[i]); + } + } + } + } + m_leafCount++; + node -> isLeaf = true; +} + +} // namespace cv2 +#endif diff --git a/modules/imgcodecs/src/grfmt_gif.hpp b/modules/imgcodecs/src/grfmt_gif.hpp new file mode 100644 index 0000000000..8f520745ba --- /dev/null +++ b/modules/imgcodecs/src/grfmt_gif.hpp @@ -0,0 +1,181 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level +// directory of this distribution and at http://opencv.org/license.html + +#ifndef OPENCV_GRFMT_GIF_HPP +#define OPENCV_GRFMT_GIF_HPP +#ifdef HAVE_IMGCODEC_GIF + +#include "grfmt_base.hpp" + +namespace cv +{ + +enum GifOpMode +{ + GRFMT_GIF_Nothing = 0, + GRFMT_GIF_PreviousImage = 1, + GRFMT_GIF_Background = 2, + GRFMT_GIF_Cover = 3 +}; + +////////////////////////////////////////////////////////////////////// +//// GIF Decoder //// +////////////////////////////////////////////////////////////////////// + +class GifDecoder CV_FINAL : public BaseImageDecoder +{ +public: + GifDecoder(); + ~GifDecoder() CV_OVERRIDE; + + bool readHeader() CV_OVERRIDE; + bool readData(Mat& img) CV_OVERRIDE; + bool nextPage() CV_OVERRIDE; + void close(); + + ImageDecoder newDecoder() const CV_OVERRIDE; + +protected: + RLByteStream m_strm; + + int bgColor; + int depth; + int idx; + + GifOpMode opMode; + bool hasTransparentColor; + uchar transparentColor; + int top, left, width, height; + + bool hasRead; + std::vector globalColorTable; + std::vector localColorTable; + + int lzwMinCodeSize; + int globalColorTableSize; + int localColorTableSize; + + Mat lastImage; + std::vector imgCodeStream; + + struct lzwNodeD + { + int length; + uchar suffix; + std::vector prefix; + }; + + void readExtensions(); + void code2pixel(Mat& img, int start, int k); + bool lzwDecode(); + bool getFrameCount_(); + bool skipHeader(); +}; + + + +////////////////////////////////////////////////////////////////////// +//// GIF Encoder //// +////////////////////////////////////////////////////////////////////// +class GifEncoder CV_FINAL : public BaseImageEncoder { +public: + GifEncoder(); + ~GifEncoder() CV_OVERRIDE; + + bool isFormatSupported(int depth) const CV_OVERRIDE; + + bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; + bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; + + ImageEncoder newEncoder() const CV_OVERRIDE; + +private: +/** Color Quantization **/ + class OctreeColorQuant + { + struct OctreeNode + { + bool isLeaf; + std::shared_ptr children[8]{}; + int level; + uchar index; + int leaf; + int pixelCount; + size_t redSum, greenSum, blueSum; + + OctreeNode(); + }; + + std::shared_ptr root; + std::vector> m_nodeList[8]; + int32_t m_bitLength; + int32_t m_maxColors; + int32_t m_leafCount; + uchar m_criticalTransparency; + uchar r, g, b; // color under transparent color + + public: + explicit OctreeColorQuant(int maxColors = 256, int bitLength = 8, uchar criticalTransparency = 1); + + int getPalette(uchar* colorTable); + uchar getLeaf(uchar red, uchar green, uchar blue); + + void addMat(const Mat& img); + void addMats(const std::vector& img_vec); + void addColor(int red, int green, int blue); + void reduceTree(); + void recurseReduce(const std::shared_ptr& node); + }; + + enum GifDithering // normal dithering level is -1 to 2 + { + GRFMT_GIF_None = 3, + GRFMT_GIF_FloydSteinberg = 4 + }; + + WLByteStream strm; + int m_width, m_height; + + int globalColorTableSize; + int localColorTableSize; + + uchar opMode; + uchar criticalTransparency; + uchar transparentColor; + Vec3b transparentRGB; + int top, left, width, height; + + OctreeColorQuant quantG; + OctreeColorQuant quantL; + + std::vector lzwTable; + std::vector imgCodeStream; + + std::vector globalColorTable; + std::vector localColorTable; + + // params + int loopCount; + int frameDelay; + int colorNum; + int bitDepth; + int dithering; + int lzwMinCodeSize, lzwMaxCodeSize; + bool fast; + + bool writeFrames(const std::vector& img_vec, const std::vector& params); + bool writeHeader(const std::vector& img_vec); + bool writeFrame(const Mat& img); + bool pixel2code(const Mat& img); + void getColorTable(const std::vector& img_vec, bool isGlobal); + static int ditheringKernel(const Mat &img, Mat &img_, int depth, uchar transparency); + bool lzwEncode(); + void close(); +}; + + +} // namespace cv + +#endif // HAVE_IMGCODEC_GIF +#endif //OPENCV_GRFMT_GIF_HPP diff --git a/modules/imgcodecs/src/grfmt_jpegxl.cpp b/modules/imgcodecs/src/grfmt_jpegxl.cpp new file mode 100644 index 0000000000..fc89dd2c6e --- /dev/null +++ b/modules/imgcodecs/src/grfmt_jpegxl.cpp @@ -0,0 +1,420 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#include "precomp.hpp" +#include "grfmt_jpegxl.hpp" + +#ifdef HAVE_JPEGXL + +#include +#include +#include + +namespace cv +{ + +/////////////////////// JpegXLDecoder /////////////////// + +JpegXLDecoder::JpegXLDecoder() : m_f(nullptr, &fclose) +{ + m_signature = "\xFF\x0A"; + m_decoder = nullptr; + m_buf_supported = false; + m_type = m_convert = -1; + m_status = JXL_DEC_NEED_MORE_INPUT; +} + +JpegXLDecoder::~JpegXLDecoder() +{ + close(); +} + +void JpegXLDecoder::close() +{ + if (m_decoder) + m_decoder.release(); + if (m_f) + m_f.release(); + m_read_buffer = {}; + m_width = m_height = 0; + m_type = m_convert = -1; + m_status = JXL_DEC_NEED_MORE_INPUT; +} + +// see https://github.com/libjxl/libjxl/blob/v0.10.0/doc/format_overview.md +size_t JpegXLDecoder::signatureLength() const +{ + return 12; // For an ISOBMFF-based container +} + +bool JpegXLDecoder::checkSignature( const String& signature ) const +{ + // A "naked" codestream. + if ( + ( signature.size() >= 2 ) && + ( memcmp( signature.c_str(), "\xFF\x0A", 2 ) == 0 ) + ) + { + return true; + } + + // An ISOBMFF-based container. + // 0x0000_000C_4A58_4C20_0D0A_870A. + if ( + ( signature.size() >= 12 ) && + ( memcmp( signature.c_str(), "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12 ) == 0 ) + ) + { + return true; + } + + return false; +} + +ImageDecoder JpegXLDecoder::newDecoder() const +{ + return makePtr(); +} + +bool JpegXLDecoder::read(Mat* pimg) +{ + // Open file + if (!m_f) { + m_f.reset(fopen(m_filename.c_str(), "rb")); + if (!m_f) + return false; + } + + // Initialize decoder + if (!m_decoder) { + m_decoder = JxlDecoderMake(nullptr); + if (!m_decoder) + return false; + // Subscribe to the basic info event + JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); + if (status != JXL_DEC_SUCCESS) + return false; + } + + // Set up parallel m_parallel_runner + if (!m_parallel_runner) { + m_parallel_runner = JxlThreadParallelRunnerMake(nullptr, cv::getNumThreads()); + if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(m_decoder.get(), + JxlThreadParallelRunner, + m_parallel_runner.get())) { + return false; + } + } + + // Create buffer for reading + const size_t read_buffer_size = 16384; // 16KB chunks + if (m_read_buffer.capacity() < read_buffer_size) + m_read_buffer.resize(read_buffer_size); + + // Create image if needed + if (m_type != -1 && pimg) { + pimg->create(m_height, m_width, m_type); + if (!pimg->isContinuous()) + return false; + if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(m_decoder.get(), + &m_format, + pimg->ptr(), + pimg->total() * pimg->elemSize())) { + return false; + } + } + + // Start decoding loop + do { + // Check if we need more input + if (m_status == JXL_DEC_NEED_MORE_INPUT) { + size_t remaining = JxlDecoderReleaseInput(m_decoder.get()); + // Move any remaining bytes to the beginning + if (remaining > 0) + memmove(m_read_buffer.data(), m_read_buffer.data() + m_read_buffer.size() - remaining, remaining); + // Read more data from file + size_t bytes_read = fread(m_read_buffer.data() + remaining, + 1, m_read_buffer.size() - remaining, m_f.get()); + if (bytes_read == 0) { + if (ferror(m_f.get())) { + CV_LOG_WARNING(NULL, "Error reading input file"); + return false; + } + // If we reached EOF but decoder needs more input, file is truncated + if (m_status == JXL_DEC_NEED_MORE_INPUT) { + CV_LOG_WARNING(NULL, "Truncated JXL file"); + return false; + } + } + + // Set input buffer + if (JXL_DEC_SUCCESS != JxlDecoderSetInput(m_decoder.get(), + m_read_buffer.data(), + bytes_read + remaining)) { + return false; + } + } + + // Get the next decoder status + m_status = JxlDecoderProcessInput(m_decoder.get()); + + // Handle different decoder states + switch (m_status) { + case JXL_DEC_BASIC_INFO: { + if (m_type != -1) + return false; + JxlBasicInfo info; + if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(m_decoder.get(), &info)) + return false; + + // total channels (Color + Alpha) + const uint32_t ncn = info.num_color_channels + info.num_extra_channels; + + m_width = info.xsize; + m_height = info.ysize; + m_format = { + ncn, + JXL_TYPE_UINT8, // (temporary) + JXL_LITTLE_ENDIAN, // endianness + 0 // align stride to bytes + }; + if (!m_use_rgb) { + switch (ncn) { + case 3: + m_convert = cv::COLOR_RGB2BGR; + break; + case 4: + m_convert = cv::COLOR_RGBA2BGRA; + break; + default: + m_convert = -1; + } + } + if (info.exponent_bits_per_sample > 0) { + m_format.data_type = JXL_TYPE_FLOAT; + m_type = CV_MAKETYPE( CV_32F, ncn ); + } else { + switch (info.bits_per_sample) { + case 8: + m_format.data_type = JXL_TYPE_UINT8; + m_type = CV_MAKETYPE( CV_8U, ncn ); + break; + case 16: + m_format.data_type = JXL_TYPE_UINT16; + m_type = CV_MAKETYPE( CV_16U, ncn ); + break; + default: + return false; + } + } + if (!pimg) + return true; + break; + } + case JXL_DEC_FULL_IMAGE: { + // Image is ready + if (m_convert != -1) + cv::cvtColor(*pimg, *pimg, m_convert); + break; + } + case JXL_DEC_ERROR: { + close(); + return false; + } + default: + break; + } + } while (m_status != JXL_DEC_SUCCESS); + + return true; +} + +bool JpegXLDecoder::readHeader() +{ + close(); + return read(nullptr); +} + +bool JpegXLDecoder::readData(Mat& img) +{ + if (!m_decoder || m_width == 0 || m_height == 0) + return false; + return read(&img); +} + +/////////////////////// JpegXLEncoder /////////////////// + +JpegXLEncoder::JpegXLEncoder() +{ + m_description = "JPEG XL files (*.jxl)"; + m_buf_supported = true; +} + +JpegXLEncoder::~JpegXLEncoder() +{ +} + +ImageEncoder JpegXLEncoder::newEncoder() const +{ + return makePtr(); +} + +bool JpegXLEncoder::isFormatSupported( int depth ) const +{ + return depth == CV_8U || depth == CV_16U || depth == CV_32F; +} + +bool JpegXLEncoder::write(const Mat& img, const std::vector& params) +{ + m_last_error.clear(); + + JxlEncoderPtr encoder = JxlEncoderMake(nullptr); + if (!encoder) + return false; + + JxlThreadParallelRunnerPtr runner = JxlThreadParallelRunnerMake( + /*memory_manager=*/nullptr, cv::getNumThreads()); + if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(encoder.get(), JxlThreadParallelRunner, runner.get())) + return false; + + CV_CheckDepth(img.depth(), + ( img.depth() == CV_8U || img.depth() == CV_16U || img.depth() == CV_32F ), + "JPEG XL encoder only supports CV_8U, CV_16U, CV_32F"); + CV_CheckChannels(img.channels(), + ( img.channels() == 1 || img.channels() == 3 || img.channels() == 4) , + "JPEG XL encoder only supports 1, 3, 4 channels"); + + WLByteStream strm; + if( m_buf ) { + if( !strm.open( *m_buf ) ) + return false; + } + else if( !strm.open( m_filename )) { + return false; + } + + JxlBasicInfo info; + JxlEncoderInitBasicInfo(&info); + info.xsize = img.cols; + info.ysize = img.rows; + info.uses_original_profile = JXL_FALSE; + + if( img.channels() == 4 ) + { + info.num_color_channels = 3; + info.num_extra_channels = 1; + + info.bits_per_sample = + info.alpha_bits = 8 * static_cast(img.elemSize1()); + + info.exponent_bits_per_sample = + info.alpha_exponent_bits = img.depth() == CV_32F ? 8 : 0; + }else{ + info.num_color_channels = img.channels(); + info.bits_per_sample = 8 * static_cast(img.elemSize1()); + info.exponent_bits_per_sample = img.depth() == CV_32F ? 8 : 0; + } + + if (JxlEncoderSetBasicInfo(encoder.get(), &info) != JXL_ENC_SUCCESS) + return false; + + JxlDataType type = JXL_TYPE_UINT8; + if (img.depth() == CV_32F) + type = JXL_TYPE_FLOAT; + else if (img.depth() == CV_16U) + type = JXL_TYPE_UINT16; + JxlPixelFormat format = {(uint32_t)img.channels(), type, JXL_NATIVE_ENDIAN, 0}; + JxlColorEncoding color_encoding = {}; + JXL_BOOL is_gray(format.num_channels < 3 ? JXL_TRUE : JXL_FALSE); + JxlColorEncodingSetToSRGB(&color_encoding, is_gray); + if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(encoder.get(), &color_encoding)) + return false; + + Mat image; + switch ( img.channels() ) { + case 3: + cv::cvtColor(img, image, cv::COLOR_BGR2RGB); + break; + case 4: + cv::cvtColor(img, image, cv::COLOR_BGRA2RGBA); + break; + case 1: + default: + if(img.isContinuous()) { + image = img; + } else { + image = img.clone(); // reconstruction as continuous image. + } + break; + } + if (!image.isContinuous()) + return false; + + JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr); + // set frame settings from params if available + for( size_t i = 0; i < params.size(); i += 2 ) + { + if( params[i] == IMWRITE_JPEGXL_QUALITY ) + { +#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 + int quality = params[i+1]; + quality = MIN(MAX(quality, 0), 100); + const float distance = JxlEncoderDistanceFromQuality(static_cast(quality)); + JxlEncoderSetFrameDistance(frame_settings, distance); + if (distance == 0) + JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); +#else + CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later"); +#endif + } + if( params[i] == IMWRITE_JPEGXL_DISTANCE ) + { + int distance = params[i+1]; + distance = MIN(MAX(distance, 0), 25); + JxlEncoderSetFrameDistance(frame_settings, distance); + if (distance == 0) + JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); + } + if( params[i] == IMWRITE_JPEGXL_EFFORT ) + { + int effort = params[i+1]; + effort = MIN(MAX(effort, 1), 10); + JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort); + } + if( params[i] == IMWRITE_JPEGXL_DECODING_SPEED ) + { + int speed = params[i+1]; + speed = MIN(MAX(speed, 0), 4); + JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, speed); + } + } + if (JXL_ENC_SUCCESS != + JxlEncoderAddImageFrame(frame_settings, &format, + static_cast(image.ptr()), + image.total() * image.elemSize())) { + return false; + } + JxlEncoderCloseInput(encoder.get()); + + const size_t buffer_size = 16384; // 16KB chunks + + std::vector compressed(buffer_size); + JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; + while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { + uint8_t* next_out = compressed.data(); + size_t avail_out = buffer_size; + process_result = JxlEncoderProcessOutput(encoder.get(), &next_out, &avail_out); + if (JXL_ENC_ERROR == process_result) + return false; + const size_t write_size = buffer_size - avail_out; + if ( strm.putBytes(compressed.data(), write_size) == false ) + return false; + } + return true; +} + +} + +#endif + +/* End of file. */ diff --git a/modules/imgcodecs/src/grfmt_jpegxl.hpp b/modules/imgcodecs/src/grfmt_jpegxl.hpp new file mode 100644 index 0000000000..87e4bfcba5 --- /dev/null +++ b/modules/imgcodecs/src/grfmt_jpegxl.hpp @@ -0,0 +1,68 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#ifndef _GRFMT_JPEGXL_H_ +#define _GRFMT_JPEGXL_H_ + +#ifdef HAVE_JPEGXL + +#include "grfmt_base.hpp" +#include +#include +#include +#include + +// Jpeg XL codec + +namespace cv +{ + +/** +* @brief JpegXL codec using libjxl library +*/ + +class JpegXLDecoder CV_FINAL : public BaseImageDecoder +{ +public: + + JpegXLDecoder(); + virtual ~JpegXLDecoder(); + + bool readData( Mat& img ) CV_OVERRIDE; + bool readHeader() CV_OVERRIDE; + void close(); + size_t signatureLength() const CV_OVERRIDE; + bool checkSignature( const String& signature ) const CV_OVERRIDE; + + ImageDecoder newDecoder() const CV_OVERRIDE; + +protected: + std::unique_ptr m_f; + JxlDecoderPtr m_decoder; + JxlThreadParallelRunnerPtr m_parallel_runner; + JxlPixelFormat m_format; + int m_convert; + std::vector m_read_buffer; + JxlDecoderStatus m_status; + +private: + bool read(Mat* pimg); +}; + + +class JpegXLEncoder CV_FINAL : public BaseImageEncoder +{ +public: + JpegXLEncoder(); + virtual ~JpegXLEncoder(); + + bool isFormatSupported( int depth ) const CV_OVERRIDE; + bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + ImageEncoder newEncoder() const CV_OVERRIDE; +}; + +} + +#endif + +#endif/*_GRFMT_JPEGXL_H_*/ diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 726c8b90b7..1ecc01f17f 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -51,6 +51,52 @@ and png2bmp sample from libpng distribution (Copyright (C) 1999-2001 MIYASAKA Masaru) \****************************************************************************************/ +/****************************************************************************\ + * + * this file includes some modified part of apngasm and APNG Optimizer 1.4 + * both have zlib license. + * + ****************************************************************************/ + + + /* apngasm + * + * The next generation of apngasm, the APNG Assembler. + * The apngasm CLI tool and library can assemble and disassemble APNG image files. + * + * https://github.com/apngasm/apngasm + + + * APNG Optimizer 1.4 + * + * Makes APNG files smaller. + * + * http://sourceforge.net/projects/apng/files + * + * Copyright (c) 2011-2015 Max Stepin + * maxst at users.sourceforge.net + * + * zlib license + * ------------ + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + #ifndef _LFS64_LARGEFILE # define _LFS64_LARGEFILE 0 #endif @@ -58,14 +104,13 @@ # define _FILE_OFFSET_BITS 0 #endif -#include -#include - #include "grfmt_png.hpp" +#include #if defined _MSC_VER && _MSC_VER >= 1200 // interaction between '_setjmp' and C++ object destruction is non-portable #pragma warning( disable: 4611 ) + #pragma warning( disable: 4244 ) #endif // the following defines are a hack to avoid multiple problems with frame pointer handling and setjmp @@ -76,24 +121,88 @@ namespace cv { -/////////////////////// PngDecoder /////////////////// +const uint32_t id_IHDR = 0x52444849; // PNG header +const uint32_t id_acTL = 0x4C546361; // Animation control chunk +const uint32_t id_fcTL = 0x4C546366; // Frame control chunk +const uint32_t id_IDAT = 0x54414449; // first frame and/or default image +const uint32_t id_fdAT = 0x54416466; // Frame data chunk +const uint32_t id_PLTE = 0x45544C50; +const uint32_t id_bKGD = 0x44474B62; +const uint32_t id_tRNS = 0x534E5274; +const uint32_t id_IEND = 0x444E4549; // end/footer chunk + +APNGFrame::APNGFrame() +{ + _pixels = NULL; + _width = 0; + _height = 0; + _colorType = 0; + _paletteSize = 0; + _transparencySize = 0; + _delayNum = 1; + _delayDen = 1000; +} + +APNGFrame::~APNGFrame() {} + +bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen) +{ + _delayNum = delayNum; + _delayDen = delayDen; + + if (!src.empty()) + { + png_uint_32 rowbytes = src.depth() == CV_16U ? src.cols * src.channels() * 2 : src.cols * src.channels(); + _width = src.cols; + _height = src.rows; + _colorType = src.channels() == 1 ? PNG_COLOR_TYPE_GRAY : src.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + _pixels = src.data; + _rows.resize(_height); + + for (unsigned int i = 0; i < _height; ++i) + _rows[i] = _pixels + i * rowbytes; + return true; + } + return false; +} + +void APNGFrame::setWidth(unsigned int width) { _width = width; } +void APNGFrame::setHeight(unsigned int height) { _height = height;} +void APNGFrame::setColorType(unsigned char colorType) { _colorType = colorType; } +void APNGFrame::setPalette(const rgb* palette) { std::copy(palette, palette + 256, _palette); } +void APNGFrame::setTransparency(const unsigned char* transparency) { std::copy(transparency, transparency + 256, _transparency); } +void APNGFrame::setPaletteSize(int paletteSize) { _paletteSize = paletteSize; } +void APNGFrame::setTransparencySize(int transparencySize) { _transparencySize = transparencySize; } +void APNGFrame::setDelayNum(unsigned int delayNum) { _delayNum = delayNum; } +void APNGFrame::setDelayDen(unsigned int delayDen) { _delayDen = delayDen; } +void APNGFrame::setPixels(unsigned char* pixels) { _pixels = pixels; } PngDecoder::PngDecoder() { m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa"; m_color_type = 0; - m_png_ptr = 0; - m_info_ptr = m_end_info = 0; m_f = 0; m_buf_supported = true; m_buf_pos = 0; m_bit_depth = 0; + m_frame_no = 0; + w0 = 0; + h0 = 0; + x0 = 0; + y0 = 0; + delay_num = 0; + delay_den = 0; + dop = 0; + bop = 0; } - PngDecoder::~PngDecoder() { - close(); + if( m_f ) + { + fclose( m_f ); + m_f = nullptr; + } } ImageDecoder PngDecoder::newDecoder() const @@ -101,26 +210,7 @@ ImageDecoder PngDecoder::newDecoder() const return makePtr(); } -void PngDecoder::close() -{ - if( m_f ) - { - fclose( m_f ); - m_f = 0; - } - - if( m_png_ptr ) - { - png_structp png_ptr = (png_structp)m_png_ptr; - png_infop info_ptr = (png_infop)m_info_ptr; - png_infop end_info = (png_infop)m_end_info; - png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ); - m_png_ptr = m_info_ptr = m_end_info = 0; - } -} - - -void PngDecoder::readDataFromBuf( void* _png_ptr, uchar* dst, size_t size ) +void PngDecoder::readDataFromBuf( void* _png_ptr, unsigned char* dst, size_t size ) { png_structp png_ptr = (png_structp)_png_ptr; PngDecoder* decoder = (PngDecoder*)(png_get_io_ptr(png_ptr)); @@ -138,94 +228,306 @@ void PngDecoder::readDataFromBuf( void* _png_ptr, uchar* dst, size_t size ) bool PngDecoder::readHeader() { volatile bool result = false; - close(); - png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ); + PngPtrs png_ptrs; + png_structp png_ptr = png_ptrs.getPng(); + png_infop info_ptr = png_ptrs.getInfo(); + png_infop end_info = png_ptrs.getEndInfo(); - if( png_ptr ) + if( png_ptr && info_ptr && end_info ) { - png_infop info_ptr = png_create_info_struct( png_ptr ); - png_infop end_info = png_create_info_struct( png_ptr ); - - m_png_ptr = png_ptr; - m_info_ptr = info_ptr; - m_end_info = end_info; m_buf_pos = 0; - - if( info_ptr && end_info ) + if( setjmp( png_jmpbuf( png_ptr ) ) == 0 ) { - if( setjmp( png_jmpbuf( png_ptr ) ) == 0 ) + unsigned char sig[8]; + uint32_t id = 0; + Chunk chunk; + + if( !m_buf.empty() ) + png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf ); + else { - if( !m_buf.empty() ) - png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf ); - else + m_f = fopen(m_filename.c_str(), "rb"); + if (!m_f) { - m_f = fopen( m_filename.c_str(), "rb" ); - if( m_f ) - png_init_io( png_ptr, m_f ); + return false; + } + png_init_io(png_ptr, m_f); + } + + // Read PNG header: 137 80 78 71 13 10 26 10 + if (!read_from_io(&sig, 8)) + return false; + + id = read_chunk(m_chunkIHDR); + // 8=HDR+size, 13=size of IHDR chunk, 4=CRC + // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR + if (!(id == id_IHDR && m_chunkIHDR.p.size() == 8 + 13 + 4)) + { + return false; + } + + while (true) + { + m_is_fcTL_loaded = false; + id = read_chunk(chunk); + + if (!id || (m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total())) + { + return false; } - if( !m_buf.empty() || m_f ) + if (id == id_IDAT) { - png_uint_32 wdth, hght; - int bit_depth, color_type, num_trans=0; - png_bytep trans; - png_color_16p trans_values; - - png_read_info( png_ptr, info_ptr ); - - png_get_IHDR( png_ptr, info_ptr, &wdth, &hght, - &bit_depth, &color_type, 0, 0, 0 ); - - m_width = (int)wdth; - m_height = (int)hght; - m_color_type = color_type; - m_bit_depth = bit_depth; - - if( bit_depth <= 8 || bit_depth == 16 ) - { - switch(color_type) - { - case PNG_COLOR_TYPE_RGB: - case PNG_COLOR_TYPE_PALETTE: - png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); - if( num_trans > 0 ) - m_type = CV_8UC4; - else - m_type = CV_8UC3; - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - case PNG_COLOR_TYPE_RGB_ALPHA: - m_type = CV_8UC4; - break; - default: - m_type = CV_8UC1; - } - if( bit_depth == 16 ) - m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); - result = true; - } + if (m_f) + fseek(m_f, 0, SEEK_SET); + else + m_buf_pos = 0; + break; } + + if (id == id_acTL) + { + // 8=HDR+size, 8=size of acTL chunk, 4=CRC + // https://wiki.mozilla.org/APNG_Specification#%60acTL%60:_The_Animation_Control_Chunk + if (chunk.p.size() != 8 + 8 + 4) + return false; + m_animation.loop_count = png_get_uint_32(&chunk.p[12]); + + m_frame_count = png_get_uint_32(&chunk.p[8]); + if (m_frame_count == 0) + return false; + } + + if (id == id_fcTL) + { + // 8=HDR+size, 26=size of fcTL chunk, 4=CRC + // https://wiki.mozilla.org/APNG_Specification#%60fcTL%60:_The_Frame_Control_Chunk + if (chunk.p.size() != 8 + 26 + 4) + return false; + m_is_fcTL_loaded = true; + w0 = png_get_uint_32(&chunk.p[12]); + h0 = png_get_uint_32(&chunk.p[16]); + x0 = png_get_uint_32(&chunk.p[20]); + y0 = png_get_uint_32(&chunk.p[24]); + delay_num = png_get_uint_16(&chunk.p[28]); + delay_den = png_get_uint_16(&chunk.p[30]); + dop = chunk.p[32]; + bop = chunk.p[33]; + } + + if (id == id_bKGD) + { + // 8=HDR+size, ??=size of bKGD chunk, 4=CRC + // The spec is actually more complex: http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.bKGD + // TODO: we only check that 4 bytes can be read from &chunk.p[8]. Fix. + if (chunk.p.size() < 8 + 4) + return false; + int bgcolor = png_get_uint_32(&chunk.p[8]); + m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF; + m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF; + m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF; + m_animation.bgcolor[0] = bgcolor & 0xFF; + } + + if (id == id_PLTE || id == id_tRNS) + m_chunksInfo.push_back(chunk); + } + + png_uint_32 wdth, hght; + int bit_depth, color_type, num_trans=0; + png_bytep trans; + png_color_16p trans_values; + + // Free chunk in case png_read_info uses longjmp. + chunk.p.clear(); + chunk.p.shrink_to_fit(); + + png_read_info( png_ptr, info_ptr ); + png_get_IHDR(png_ptr, info_ptr, &wdth, &hght, + &bit_depth, &color_type, 0, 0, 0); + + m_width = (int)wdth; + m_height = (int)hght; + m_color_type = color_type; + m_bit_depth = bit_depth; + + if (bit_depth <= 8 || bit_depth == 16) + { + switch (color_type) + { + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_PALETTE: + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); + if (num_trans > 0) + m_type = CV_8UC4; + else + m_type = CV_8UC3; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + case PNG_COLOR_TYPE_RGB_ALPHA: + m_type = CV_8UC4; + break; + default: + m_type = CV_8UC1; + } + if (bit_depth == 16) + m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); + result = true; } } } + if(result) + { + m_png_ptrs = std::move(png_ptrs); + } + return result; } - bool PngDecoder::readData( Mat& img ) { + if (m_frame_count > 1) + { + Mat mat_cur = Mat::zeros(img.rows, img.cols, m_type); + uint32_t id = 0; + uint32_t j = 0; + uint32_t imagesize = m_width * m_height * mat_cur.channels(); + m_is_IDAT_loaded = false; + + if (m_frame_no == 0) + { + m_mat_raw = Mat(img.rows, img.cols, m_type); + m_mat_next = Mat(img.rows, img.cols, m_type); + frameRaw.setMat(m_mat_raw); + frameNext.setMat(m_mat_next); + if (m_f) + fseek(m_f, -8, SEEK_CUR); + else + m_buf_pos -= 8; + } + else + m_mat_next.copyTo(mat_cur); + + frameCur.setMat(mat_cur); + + processing_start((void*)&frameRaw, mat_cur); + png_structp png_ptr = m_png_ptrs.getPng(); + png_infop info_ptr = m_png_ptrs.getInfo(); + + while (true) + { + Chunk chunk; + id = read_chunk(chunk); + if (!id) + return false; + + if (id == id_fcTL && m_is_IDAT_loaded) + { + if (!m_is_fcTL_loaded) + { + m_is_fcTL_loaded = true; + w0 = m_width; + h0 = m_height; + } + + if (processing_finish()) + { + if (dop == 2) + memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); + + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); + if (!delay_den) + delay_den = 100; + m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den)); + + if (mat_cur.channels() == img.channels()) + mat_cur.copyTo(img); + else if (img.channels() == 1) + cvtColor(mat_cur, img, COLOR_BGRA2GRAY); + else if (img.channels() == 3) + cvtColor(mat_cur, img, COLOR_BGRA2BGR); + + if (dop != 2) + { + memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); + if (dop == 1) + for (j = 0; j < h0; j++) + memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels()); + } + } + else + { + return false; + } + + w0 = png_get_uint_32(&chunk.p[12]); + h0 = png_get_uint_32(&chunk.p[16]); + x0 = png_get_uint_32(&chunk.p[20]); + y0 = png_get_uint_32(&chunk.p[24]); + delay_num = png_get_uint_16(&chunk.p[28]); + delay_den = png_get_uint_16(&chunk.p[30]); + dop = chunk.p[32]; + bop = chunk.p[33]; + + if (int(x0 + w0) > img.cols || int(y0 + h0) > img.rows || dop > 2 || bop > 1) + { + return false; + } + + memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8); + return true; + } + else if (id == id_IDAT) + { + m_is_IDAT_loaded = true; + png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size()); + } + else if (id == id_fdAT && m_is_fcTL_loaded) + { + m_is_IDAT_loaded = true; + png_save_uint_32(&chunk.p[4], static_cast(chunk.p.size() - 16)); + memcpy(&chunk.p[8], "IDAT", 4); + png_process_data(png_ptr, info_ptr, &chunk.p[4], chunk.p.size() - 4); + } + else if (id == id_IEND) + { + if (processing_finish()) + { + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); + if (!delay_den) + delay_den = 100; + m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den)); + + if (mat_cur.channels() == img.channels()) + mat_cur.copyTo(img); + else if (img.channels() == 1) + cvtColor(mat_cur, img, COLOR_BGRA2GRAY); + else if (img.channels() == 3) + cvtColor(mat_cur, img, COLOR_BGRA2BGR); + } + else + return false; + + return true; + } + else + png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size()); + } + return false; + } + volatile bool result = false; - AutoBuffer _buffer(m_height); - uchar** buffer = _buffer.data(); + AutoBuffer _buffer(m_height); + unsigned char** buffer = _buffer.data(); bool color = img.channels() > 1; - png_structp png_ptr = (png_structp)m_png_ptr; - png_infop info_ptr = (png_infop)m_info_ptr; - png_infop end_info = (png_infop)m_end_info; + png_structp png_ptr = m_png_ptrs.getPng(); + png_infop info_ptr = m_png_ptrs.getInfo(); + png_infop end_info = m_png_ptrs.getEndInfo(); - if( m_png_ptr && m_info_ptr && m_end_info && m_width && m_height ) + if( png_ptr && info_ptr && end_info && m_width && m_height ) { if( setjmp( png_jmpbuf ( png_ptr ) ) == 0 ) { @@ -300,22 +602,221 @@ bool PngDecoder::readData( Mat& img ) return result; } +bool PngDecoder::nextPage() { + return ++m_frame_no < (int)m_frame_count; +} + +void PngDecoder::compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img) +{ + int channels = img.channels(); + if (img.depth() == CV_16U) + cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) { + for (int j = range.start; j < range.end; j++) { + uint16_t* sp = reinterpret_cast(rows_src[j]); + uint16_t* dp = reinterpret_cast(rows_dst[j + y]) + x * channels; + + if (_bop == 0) { + // Overwrite mode: copy source row directly to destination + memcpy(dp, sp, w * channels * sizeof(uint16_t)); + } + else { + // Blending mode + for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { + if (sp[3] == 65535) { // Fully opaque in 16-bit (max value) + memcpy(dp, sp, channels * sizeof(uint16_t)); + } + else if (sp[3] != 0) { // Partially transparent + if (dp[3] != 0) { // Both source and destination have alpha + uint32_t u = sp[3] * 65535; // 16-bit max + uint32_t v = (65535 - sp[3]) * dp[3]; + uint32_t al = u + v; + dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red + dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green + dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue + dp[3] = static_cast(al / 65535); // Alpha + } + else { + // If destination alpha is 0, copy source pixel + memcpy(dp, sp, channels * sizeof(uint16_t)); + } + } + } + } + } + }); + else + cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) { + for (int j = range.start; j < range.end; j++) { + unsigned char* sp = rows_src[j]; + unsigned char* dp = rows_dst[j + y] + x * channels; + + if (_bop == 0) { + // Overwrite mode: copy source row directly to destination + memcpy(dp, sp, w * channels); + } + else { + // Blending mode + for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { + if (sp[3] == 255) { + // Fully opaque: copy source pixel directly + memcpy(dp, sp, channels); + } + else if (sp[3] != 0) { + // Alpha blending + if (dp[3] != 0) { + int u = sp[3] * 255; + int v = (255 - sp[3]) * dp[3]; + int al = u + v; + dp[0] = (sp[0] * u + dp[0] * v) / al; // Red + dp[1] = (sp[1] * u + dp[1] * v) / al; // Green + dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue + dp[3] = al / 255; // Alpha + } + else { + // If destination alpha is 0, copy source pixel + memcpy(dp, sp, channels); + } + } + } + } + } + }); +} + +bool PngDecoder::read_from_io(void* buffer, size_t num_bytes) +{ + if (m_f) + return fread(buffer, 1, num_bytes, m_f) == num_bytes; + + if (m_buf_pos + num_bytes > m_buf.cols * m_buf.rows * m_buf.elemSize()) { + CV_LOG_WARNING(NULL, "PNG input buffer is incomplete"); + return false; + } + + memcpy( buffer, m_buf.ptr() + m_buf_pos, num_bytes ); + m_buf_pos += num_bytes; + return true; +} + +uint32_t PngDecoder::read_chunk(Chunk& chunk) +{ + unsigned char len[4]; + if (read_from_io(&len, 4)) + { + const size_t size = static_cast(png_get_uint_32(len)) + 12; + if (size > PNG_USER_CHUNK_MALLOC_MAX) + { + CV_LOG_WARNING(NULL, "chunk data is too large"); + return 0; + } + chunk.p.resize(size); + memcpy(chunk.p.data(), len, 4); + if (read_from_io(&chunk.p[4], chunk.p.size() - 4)) + return *(uint32_t*)(&chunk.p[4]); + } + return 0; +} + +bool PngDecoder::processing_start(void* frame_ptr, const Mat& img) +{ + static uint8_t header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + + PngPtrs png_ptrs; + png_structp png_ptr = png_ptrs.getPng(); + png_infop info_ptr = png_ptrs.getInfo(); + + if (!png_ptr || !info_ptr) { + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + return false; + } + + m_png_ptrs = std::move(png_ptrs); + png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + png_set_progressive_read_fn(png_ptr, frame_ptr, (png_progressive_info_ptr)info_fn, row_fn, NULL); + + if (img.channels() < 4) + png_set_strip_alpha(png_ptr); + else + png_set_tRNS_to_alpha(png_ptr); + + png_process_data(png_ptr, info_ptr, header, 8); + png_process_data(png_ptr, info_ptr, m_chunkIHDR.p.data(), m_chunkIHDR.p.size()); + + if ((m_color_type & PNG_COLOR_MASK_COLOR) && img.channels() > 1 && !m_use_rgb) + png_set_bgr(png_ptr); // convert RGB to BGR + else if (img.channels() > 1) + png_set_gray_to_rgb(png_ptr); // Gray->RGB + else + png_set_rgb_to_gray(png_ptr, 1, 0.299, 0.587); // RGB->Gray + + for (size_t i = 0; i < m_chunksInfo.size(); i++) + png_process_data(png_ptr, info_ptr, m_chunksInfo[i].p.data(), m_chunksInfo[i].p.size()); + + return true; +} + +bool PngDecoder::processing_finish() +{ + static uint8_t footer[12] = { 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 }; + + png_structp png_ptr = m_png_ptrs.getPng(); + png_infop info_ptr = m_png_ptrs.getInfo(); + + if (!png_ptr) { + m_png_ptrs.clear(); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + m_png_ptrs.clear(); + return false; + } + + png_process_data(png_ptr, info_ptr, footer, 12); + m_png_ptrs.clear(); + + return true; +} + +void PngDecoder::info_fn(png_structp png_ptr, png_infop info_ptr) +{ + png_set_expand(png_ptr); + (void)png_set_interlace_handling(png_ptr); + png_read_update_info(png_ptr, info_ptr); +} + +void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) +{ + CV_UNUSED(pass); + APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr); + png_progressive_combine_row(png_ptr, frame->getRows()[row_num], new_row); +} /////////////////////// PngEncoder /////////////////// - PngEncoder::PngEncoder() { m_description = "Portable Network Graphics files (*.png)"; m_buf_supported = true; + op_zstream1.zalloc = NULL; + op_zstream2.zalloc = NULL; + next_seq_num = 0; + trnssize = 0; + palsize = 0; + memset(palette, 0, sizeof(palette)); + memset(trns, 0, sizeof(trns)); + memset(op, 0, sizeof(op)); } - PngEncoder::~PngEncoder() { } - bool PngEncoder::isFormatSupported( int depth ) const { return depth == CV_8U || depth == CV_16U; @@ -326,8 +827,7 @@ ImageEncoder PngEncoder::newEncoder() const return makePtr(); } - -void PngEncoder::writeDataToBuf(void* _png_ptr, uchar* src, size_t size) +void PngEncoder::writeDataToBuf(void* _png_ptr, unsigned char* src, size_t size) { if( size == 0 ) return; @@ -339,7 +839,6 @@ void PngEncoder::writeDataToBuf(void* _png_ptr, uchar* src, size_t size) memcpy( &(*encoder->m_buf)[cursz], src, size ); } - void PngEncoder::flushBuf(void*) { } @@ -449,6 +948,752 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) return result; } +size_t PngEncoder::write_to_io(void const* _Buffer, size_t _ElementSize, size_t _ElementCount, FILE * _Stream) +{ + if (_Stream) + return fwrite(_Buffer, _ElementSize, _ElementCount, _Stream); + + size_t cursz = m_buf->size(); + m_buf->resize(cursz + _ElementCount); + memcpy( &(*m_buf)[cursz], _Buffer, _ElementCount ); + return _ElementCount; +} + +void PngEncoder::writeChunk(FILE* f, const char* name, unsigned char* data, uint32_t length) +{ + unsigned char buf[4]; + uint32_t crc = crc32(0, Z_NULL, 0); + + png_save_uint_32(buf, length); + write_to_io(buf, 1, 4, f); + write_to_io(name, 1, 4, f); + crc = crc32(crc, (const Bytef*)name, 4); + + if (memcmp(name, "fdAT", 4) == 0) + { + png_save_uint_32(buf, next_seq_num++); + write_to_io(buf, 1, 4, f); + crc = crc32(crc, buf, 4); + length -= 4; + } + + if (data != NULL && length > 0) + { + write_to_io(data, 1, length, f); + crc = crc32(crc, data, length); + } + + png_save_uint_32(buf, crc); + write_to_io(buf, 1, 4, f); +} + +void PngEncoder::writeIDATs(FILE* f, int frame, unsigned char* data, uint32_t length, uint32_t idat_size) +{ + uint32_t z_cmf = data[0]; + if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) + { + if (length >= 2) + { + uint32_t z_cinfo = z_cmf >> 4; + uint32_t half_z_window_size = 1 << (z_cinfo + 7); + while (idat_size <= half_z_window_size && half_z_window_size >= 256) + { + z_cinfo--; + half_z_window_size >>= 1; + } + z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); + if (data[0] != (unsigned char)z_cmf) + { + data[0] = (unsigned char)z_cmf; + data[1] &= 0xe0; + data[1] += (unsigned char)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f); + } + } + } + + while (length > 0) + { + uint32_t ds = length; + if (ds > 32768) + ds = 32768; + + if (frame == 0) + writeChunk(f, "IDAT", data, ds); + else + writeChunk(f, "fdAT", data, ds + 4); + + data += ds; + length -= ds; + } +} + +void PngEncoder::processRect(unsigned char* row, int rowbytes, int bpp, int stride, int h, unsigned char* rows) +{ + int i, j, v; + int a, b, c, pa, pb, pc, p; + unsigned char* prev = NULL; + unsigned char* dp = rows; + unsigned char* out; + + for (j = 0; j < h; j++) + { + uint32_t sum = 0; + unsigned char* best_row = row_buf.data(); + uint32_t mins = ((uint32_t)(-1)) >> 1; + + out = row_buf.data() + 1; + for (i = 0; i < rowbytes; i++) + { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + mins = sum; + + sum = 0; + out = sub_row.data() + 1; + for (i = 0; i < bpp; i++) + { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) + { + v = out[i] = row[i] - row[i - bpp]; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + mins = sum; + best_row = sub_row.data(); + } + + if (prev) + { + sum = 0; + out = up_row.data() + 1; + for (i = 0; i < rowbytes; i++) + { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + mins = sum; + best_row = up_row.data(); + } + + sum = 0; + out = avg_row.data() + 1; + for (i = 0; i < bpp; i++) + { + v = out[i] = row[i] - prev[i] / 2; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) + { + v = out[i] = row[i] - (prev[i] + row[i - bpp]) / 2; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + mins = sum; + best_row = avg_row.data(); + } + + sum = 0; + out = paeth_row.data() + 1; + for (i = 0; i < bpp; i++) + { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) + { + a = row[i - bpp]; + b = prev[i]; + c = prev[i - bpp]; + p = b - c; + pc = a - c; + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b + : c; + v = out[i] = row[i] - p; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + best_row = paeth_row.data(); + } + } + + if (rows == NULL) + { + // deflate_rect_op() + op_zstream1.next_in = row_buf.data(); + op_zstream1.avail_in = rowbytes + 1; + deflate(&op_zstream1, Z_NO_FLUSH); + + op_zstream2.next_in = best_row; + op_zstream2.avail_in = rowbytes + 1; + deflate(&op_zstream2, Z_NO_FLUSH); + } + else + { + // deflate_rect_fin() + memcpy(dp, best_row, rowbytes + 1); + dp += rowbytes + 1; + } + + prev = row; + row += stride; + } +} + +void PngEncoder::deflateRectOp(unsigned char* pdata, int x, int y, int w, int h, int bpp, int stride, int zbuf_size, int n) +{ + unsigned char* row = pdata + y * stride + x * bpp; + int rowbytes = w * bpp; + + op_zstream1.data_type = Z_BINARY; + op_zstream1.next_out = op_zbuf1.data(); + op_zstream1.avail_out = zbuf_size; + + op_zstream2.data_type = Z_BINARY; + op_zstream2.next_out = op_zbuf2.data(); + op_zstream2.avail_out = zbuf_size; + + processRect(row, rowbytes, bpp, stride, h, NULL); + + deflate(&op_zstream1, Z_FINISH); + deflate(&op_zstream2, Z_FINISH); + op[n].p = pdata; + + if (op_zstream1.total_out < op_zstream2.total_out) + { + op[n].size = op_zstream1.total_out; + op[n].filters = 0; + } + else + { + op[n].size = op_zstream2.total_out; + op[n].filters = 1; + } + op[n].x = x; + op[n].y = y; + op[n].w = w; + op[n].h = h; + op[n].valid = 1; + deflateReset(&op_zstream1); + deflateReset(&op_zstream2); +} + +bool PngEncoder::getRect(uint32_t w, uint32_t h, unsigned char* pimage1, unsigned char* pimage2, unsigned char* ptemp, uint32_t bpp, uint32_t stride, int zbuf_size, uint32_t has_tcolor, uint32_t tcolor, int n) +{ + uint32_t i, j, x0, y0, w0, h0; + uint32_t x_min = w - 1; + uint32_t y_min = h - 1; + uint32_t x_max = 0; + uint32_t y_max = 0; + uint32_t diffnum = 0; + uint32_t over_is_possible = 1; + + if (!has_tcolor) + over_is_possible = 0; + + if (bpp == 1) + { + unsigned char* pa = pimage1; + unsigned char* pb = pimage2; + unsigned char* pc = ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + unsigned char c = *pb++; + if (*pa++ != c) + { + diffnum++; + if (has_tcolor && c == tcolor) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c = tcolor; + + *pc++ = c; + } + } + else if (bpp == 2) + { + unsigned short* pa = (unsigned short*)pimage1; + unsigned short* pb = (unsigned short*)pimage2; + unsigned short* pc = (unsigned short*)ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + uint32_t c1 = *pa++; + uint32_t c2 = *pb++; + if ((c1 != c2) && ((c1 >> 8) || (c2 >> 8))) + { + diffnum++; + if ((c2 >> 8) != 0xFF) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c2 = 0; + + *pc++ = c2; + } + } + else if (bpp == 3) + { + unsigned char* pa = pimage1; + unsigned char* pb = pimage2; + unsigned char* pc = ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + uint32_t c1 = (pa[2] << 16) + (pa[1] << 8) + pa[0]; + uint32_t c2 = (pb[2] << 16) + (pb[1] << 8) + pb[0]; + if (c1 != c2) + { + diffnum++; + if (has_tcolor && c2 == tcolor) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c2 = tcolor; + + memcpy(pc, &c2, 3); + pa += 3; + pb += 3; + pc += 3; + } + } + else if (bpp == 4) + { + uint32_t* pa = (uint32_t*)pimage1; + uint32_t* pb = (uint32_t*)pimage2; + uint32_t* pc = (uint32_t*)ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + uint32_t c1 = *pa++; + uint32_t c2 = *pb++; + if ((c1 != c2) && ((c1 >> 24) || (c2 >> 24))) + { + diffnum++; + if ((c2 >> 24) != 0xFF) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c2 = 0; + + *pc++ = c2; + } + } + + if (diffnum == 0) + { + return false; + } + else + { + x0 = x_min; + y0 = y_min; + w0 = x_max - x_min + 1; + h0 = y_max - y_min + 1; + } + + if (n < 3) + { + deflateRectOp(pimage2, x0, y0, w0, h0, bpp, stride, zbuf_size, n * 2); + + if (over_is_possible) + deflateRectOp(ptemp, x0, y0, w0, h0, bpp, stride, zbuf_size, n * 2 + 1); + } + + return true; +} + +void PngEncoder::deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, int stride, unsigned char* rows, int zbuf_size, int n) +{ + unsigned char* row = op[n].p + op[n].y * stride + op[n].x * bpp; + int rowbytes = op[n].w * bpp; + + if (op[n].filters == 0) + { + unsigned char* dp = rows; + for (int j = 0; j < op[n].h; j++) + { + *dp++ = 0; + memcpy(dp, row, rowbytes); + dp += rowbytes; + row += stride; + } + } + else + processRect(row, rowbytes, bpp, stride, op[n].h, rows); + + z_stream fin_zstream; + fin_zstream.data_type = Z_BINARY; + fin_zstream.zalloc = Z_NULL; + fin_zstream.zfree = Z_NULL; + fin_zstream.opaque = Z_NULL; + deflateInit2(&fin_zstream, Z_BEST_COMPRESSION, 8, 15, 8, op[n].filters ? Z_FILTERED : Z_DEFAULT_STRATEGY); + + fin_zstream.next_out = zbuf; + fin_zstream.avail_out = zbuf_size; + fin_zstream.next_in = rows; + fin_zstream.avail_in = op[n].h * (rowbytes + 1); + deflate(&fin_zstream, Z_FINISH); + *zsize = fin_zstream.total_out; + deflateEnd(&fin_zstream); +} + +bool PngEncoder::writeanimation(const Animation& animation, const std::vector& params) +{ + int compression_level = 6; + int compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy + bool isBilevel = false; + + for (size_t i = 0; i < params.size(); i += 2) + { + if (params[i] == IMWRITE_PNG_COMPRESSION) + { + compression_strategy = IMWRITE_PNG_STRATEGY_DEFAULT; // Default strategy + compression_level = params[i + 1]; + compression_level = MIN(MAX(compression_level, 0), Z_BEST_COMPRESSION); + } + if (params[i] == IMWRITE_PNG_STRATEGY) + { + compression_strategy = params[i + 1]; + compression_strategy = MIN(MAX(compression_strategy, 0), Z_FIXED); + } + if (params[i] == IMWRITE_PNG_BILEVEL) + { + isBilevel = params[i + 1] != 0; + } + } + + CV_UNUSED(isBilevel); + uint32_t first =0; + uint32_t loops= animation.loop_count; + uint32_t coltype= animation.frames[0].channels() == 1 ? PNG_COLOR_TYPE_GRAY : animation.frames[0].channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + + FILE* m_f = NULL; + uint32_t i, j, k; + uint32_t x0, y0, w0, h0, dop, bop; + uint32_t idat_size, zbuf_size, zsize; + unsigned char header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + uint32_t num_frames = (int)animation.frames.size(); + uint32_t width = animation.frames[0].cols; + uint32_t height = animation.frames[0].rows; + uint32_t bpp = (coltype == 6) ? 4 : (coltype == 2) ? 3 + : (coltype == 4) ? 2 + : 1; + uint32_t has_tcolor = (coltype >= 4 || (coltype <= 2 && trnssize)) ? 1 : 0; + uint32_t tcolor = 0; + uint32_t rowbytes = width * bpp; + uint32_t imagesize = rowbytes * height; + + AutoBuffer temp(imagesize); + AutoBuffer over1(imagesize); + AutoBuffer over2(imagesize); + AutoBuffer over3(imagesize); + AutoBuffer rest(imagesize); + AutoBuffer rows((rowbytes + 1) * height); + + std::vector frames; + std::vector tmpframes; + + for (i = 0; i < (uint32_t)animation.frames.size(); i++) + { + APNGFrame apngFrame; + tmpframes.push_back(animation.frames[i].clone()); + // TO DO optimize BGR RGB conversations + if (animation.frames[i].channels() == 4) + cvtColor(animation.frames[i], tmpframes[i], COLOR_BGRA2RGBA); + if (animation.frames[i].channels() == 3) + cvtColor(animation.frames[i], tmpframes[i], COLOR_BGR2RGB); + + if (tmpframes[i].depth() != CV_8U) + tmpframes[i].convertTo(tmpframes[i], CV_8U, 1.0 / 255); + apngFrame.setMat(tmpframes[i], animation.durations[i]); + + if (i > 0 && !getRect(width, height, frames.back().getPixels(), apngFrame.getPixels(), over1.data(), bpp, rowbytes, 0, 0, 0, 3)) + { + frames.back().setDelayNum(frames.back().getDelayNum() + apngFrame.getDelayNum()); + num_frames--; + } + else + frames.push_back(apngFrame); + } + + if (trnssize) + { + if (coltype == 0) + tcolor = trns[1]; + else if (coltype == 2) + tcolor = (((trns[5] << 8) + trns[3]) << 8) + trns[1]; + else if (coltype == 3) + { + for (i = 0; i < trnssize; i++) + if (trns[i] == 0) + { + has_tcolor = 1; + tcolor = i; + break; + } + } + } + + if (m_buf || (m_f = fopen(m_filename.c_str(), "wb")) != 0) + { + unsigned char buf_IHDR[13]; + unsigned char buf_acTL[8]; + unsigned char buf_fcTL[26]; + + png_save_uint_32(buf_IHDR, width); + png_save_uint_32(buf_IHDR + 4, height); + buf_IHDR[8] = 8; + buf_IHDR[9] = coltype; + buf_IHDR[10] = 0; + buf_IHDR[11] = 0; + buf_IHDR[12] = 0; + + png_save_uint_32(buf_acTL, num_frames - first); + png_save_uint_32(buf_acTL + 4, loops); + + write_to_io(header, 1, 8, m_f); + + writeChunk(m_f, "IHDR", buf_IHDR, 13); + + if (num_frames > 1) + writeChunk(m_f, "acTL", buf_acTL, 8); + else + first = 0; + + if (palsize > 0) + writeChunk(m_f, "PLTE", (unsigned char*)(&palette), palsize * 3); + + if ((animation.bgcolor != Scalar()) && (animation.frames.size() > 1)) + { + uint64_t bgvalue = (static_cast(animation.bgcolor[0]) & 0xFF) << 24 | + (static_cast(animation.bgcolor[1]) & 0xFF) << 16 | + (static_cast(animation.bgcolor[2]) & 0xFF) << 8 | + (static_cast(animation.bgcolor[3]) & 0xFF); + writeChunk(m_f, "bKGD", (unsigned char*)(&bgvalue), 6); //the bKGD chunk must precede the first IDAT chunk, and must follow the PLTE chunk. + } + + if (trnssize > 0) + writeChunk(m_f, "tRNS", trns, trnssize); + + op_zstream1.data_type = Z_BINARY; + op_zstream1.zalloc = Z_NULL; + op_zstream1.zfree = Z_NULL; + op_zstream1.opaque = Z_NULL; + deflateInit2(&op_zstream1, compression_level, 8, 15, 8, compression_strategy); + + op_zstream2.data_type = Z_BINARY; + op_zstream2.zalloc = Z_NULL; + op_zstream2.zfree = Z_NULL; + op_zstream2.opaque = Z_NULL; + deflateInit2(&op_zstream2, compression_level, 8, 15, 8, Z_FILTERED); + + idat_size = (rowbytes + 1) * height; + zbuf_size = idat_size + ((idat_size + 7) >> 3) + ((idat_size + 63) >> 6) + 11; + + AutoBuffer zbuf(zbuf_size); + op_zbuf1.allocate(zbuf_size); + op_zbuf2.allocate(zbuf_size); + row_buf.allocate(rowbytes + 1); + sub_row.allocate(rowbytes + 1); + up_row.allocate(rowbytes + 1); + avg_row.allocate(rowbytes + 1); + paeth_row.allocate(rowbytes + 1); + + row_buf[0] = 0; + sub_row[0] = 1; + up_row[0] = 2; + avg_row[0] = 3; + paeth_row[0] = 4; + + x0 = 0; + y0 = 0; + w0 = width; + h0 = height; + bop = 0; + next_seq_num = 0; + + for (j = 0; j < 6; j++) + op[j].valid = 0; + deflateRectOp(frames[0].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); + deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); + + if (first) + { + writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size); + for (j = 0; j < 6; j++) + op[j].valid = 0; + deflateRectOp(frames[1].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); + deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); + } + + for (i = first; i < num_frames - 1; i++) + { + uint32_t op_min; + int op_best; + + for (j = 0; j < 6; j++) + op[j].valid = 0; + + /* dispose = none */ + getRect(width, height, frames[i].getPixels(), frames[i + 1].getPixels(), over1.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 0); + + /* dispose = background */ + if (has_tcolor) + { + memcpy(temp.data(), frames[i].getPixels(), imagesize); + if (coltype == 2) + for (j = 0; j < h0; j++) + for (k = 0; k < w0; k++) + memcpy(temp.data() + ((j + y0) * width + (k + x0)) * 3, &tcolor, 3); + else + for (j = 0; j < h0; j++) + memset(temp.data() + ((j + y0) * width + x0) * bpp, tcolor, w0 * bpp); + + getRect(width, height, temp.data(), frames[i + 1].getPixels(), over2.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 1); + } + + /* dispose = previous */ + if (i > first) + getRect(width, height, rest.data(), frames[i + 1].getPixels(), over3.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 2); + + op_min = op[0].size; + op_best = 0; + for (j = 1; j < 6; j++) + if (op[j].valid) + { + if (op[j].size < op_min) + { + op_min = op[j].size; + op_best = j; + } + } + + dop = op_best >> 1; + + png_save_uint_32(buf_fcTL, next_seq_num++); + png_save_uint_32(buf_fcTL + 4, w0); + png_save_uint_32(buf_fcTL + 8, h0); + png_save_uint_32(buf_fcTL + 12, x0); + png_save_uint_32(buf_fcTL + 16, y0); + png_save_uint_16(buf_fcTL + 20, frames[i].getDelayNum()); + png_save_uint_16(buf_fcTL + 22, frames[i].getDelayDen()); + buf_fcTL[24] = dop; + buf_fcTL[25] = bop; + writeChunk(m_f, "fcTL", buf_fcTL, 26); + + writeIDATs(m_f, i, zbuf.data(), zsize, idat_size); + + /* process apng dispose - begin */ + if (dop != 2) + memcpy(rest.data(), frames[i].getPixels(), imagesize); + + if (dop == 1) + { + if (coltype == 2) + for (j = 0; j < h0; j++) + for (k = 0; k < w0; k++) + memcpy(rest.data() + ((j + y0) * width + (k + x0)) * 3, &tcolor, 3); + else + for (j = 0; j < h0; j++) + memset(rest.data() + ((j + y0) * width + x0) * bpp, tcolor, w0 * bpp); + } + /* process apng dispose - end */ + + x0 = op[op_best].x; + y0 = op[op_best].y; + w0 = op[op_best].w; + h0 = op[op_best].h; + bop = op_best & 1; + + deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, op_best); + } + + if (num_frames > 1) + { + png_save_uint_32(buf_fcTL, next_seq_num++); + png_save_uint_32(buf_fcTL + 4, w0); + png_save_uint_32(buf_fcTL + 8, h0); + png_save_uint_32(buf_fcTL + 12, x0); + png_save_uint_32(buf_fcTL + 16, y0); + png_save_uint_16(buf_fcTL + 20, frames[i].getDelayNum()); + png_save_uint_16(buf_fcTL + 22, frames[i].getDelayDen()); + buf_fcTL[24] = 0; + buf_fcTL[25] = bop; + writeChunk(m_f, "fcTL", buf_fcTL, 26); + } + + writeIDATs(m_f, num_frames - 1, zbuf.data(), zsize, idat_size); + + writeChunk(m_f, "IEND", 0, 0); + + if (m_f) + fclose(m_f); + + deflateEnd(&op_zstream1); + deflateEnd(&op_zstream2); + } + + return true; +} + } #endif diff --git a/modules/imgcodecs/src/grfmt_png.hpp b/modules/imgcodecs/src/grfmt_png.hpp index 3d8d1a764a..dec2cd0b61 100644 --- a/modules/imgcodecs/src/grfmt_png.hpp +++ b/modules/imgcodecs/src/grfmt_png.hpp @@ -47,34 +47,161 @@ #include "grfmt_base.hpp" #include "bitstrm.hpp" +#include +#include +#include namespace cv { +struct Chunk { std::vector p; }; +struct OP { unsigned char* p; uint32_t size; int x, y, w, h, valid, filters; }; + +typedef struct { + unsigned char r, g, b; +} rgb; + +class APNGFrame { +public: + + APNGFrame(); + + // Destructor + ~APNGFrame(); + + bool setMat(const cv::Mat& src, unsigned delayNum = 1, unsigned delayDen = 1000); + + // Getters and Setters + unsigned char* getPixels() const { return _pixels; } + void setPixels(unsigned char* pixels); + + unsigned int getWidth() const { return _width; } + void setWidth(unsigned int width); + + unsigned int getHeight() const { return _height; } + void setHeight(unsigned int height); + + unsigned char getColorType() const { return _colorType; } + void setColorType(unsigned char colorType); + + rgb* getPalette() { return _palette; } + void setPalette(const rgb* palette); + + unsigned char* getTransparency() { return _transparency; } + void setTransparency(const unsigned char* transparency); + + int getPaletteSize() const { return _paletteSize; } + void setPaletteSize(int paletteSize); + + int getTransparencySize() const { return _transparencySize; } + void setTransparencySize(int transparencySize); + + unsigned int getDelayNum() const { return _delayNum; } + void setDelayNum(unsigned int delayNum); + + unsigned int getDelayDen() const { return _delayDen; } + void setDelayDen(unsigned int delayDen); + + std::vector& getRows() { return _rows; } + +private: + unsigned char* _pixels; + unsigned int _width; + unsigned int _height; + unsigned char _colorType; + rgb _palette[256]; + unsigned char _transparency[256]; + int _paletteSize; + int _transparencySize; + unsigned int _delayNum; + unsigned int _delayDen; + std::vector _rows; +}; + class PngDecoder CV_FINAL : public BaseImageDecoder { public: - PngDecoder(); virtual ~PngDecoder(); bool readData( Mat& img ) CV_OVERRIDE; bool readHeader() CV_OVERRIDE; - void close(); + bool nextPage() CV_OVERRIDE; ImageDecoder newDecoder() const CV_OVERRIDE; protected: - static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size); + static void info_fn(png_structp png_ptr, png_infop info_ptr); + static void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass); + bool processing_start(void* frame_ptr, const Mat& img); + bool processing_finish(); + void compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img); + bool read_from_io(void* buffer, size_t num_bytes); + uint32_t read_chunk(Chunk& chunk); + struct PngPtrs { + public: + PngPtrs() { + png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ); + if (png_ptr) { + info_ptr = png_create_info_struct( png_ptr ); + end_info = png_create_info_struct( png_ptr ); + } else { + info_ptr = end_info = nullptr; + } + } + ~PngPtrs() { + clear(); + } + PngPtrs& operator=(PngPtrs&& other) { + clear(); + png_ptr = other.png_ptr; + info_ptr = other.info_ptr; + end_info = other.end_info; + other.png_ptr = nullptr; + other.info_ptr = other.end_info = nullptr; + return *this; + } + void clear() { + if (png_ptr) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + png_ptr = nullptr; + info_ptr = end_info = nullptr; + } + } + png_structp getPng() const { return png_ptr; } + png_infop getInfo() const { return info_ptr; } + png_infop getEndInfo() const { return end_info; } + private: + png_structp png_ptr; // pointer to decompression structure + png_infop info_ptr; // pointer to image information structure + png_infop end_info; // pointer to one more image information structure + }; + + PngPtrs m_png_ptrs; int m_bit_depth; - void* m_png_ptr; // pointer to decompression structure - void* m_info_ptr; // pointer to image information structure - void* m_end_info; // pointer to one more image information structure FILE* m_f; int m_color_type; + Chunk m_chunkIHDR; + int m_frame_no; size_t m_buf_pos; + std::vector m_chunksInfo; + APNGFrame frameRaw; + APNGFrame frameNext; + APNGFrame frameCur; + Mat m_mat_raw; + Mat m_mat_next; + uint32_t w0; + uint32_t h0; + uint32_t x0; + uint32_t y0; + uint32_t delay_num; + uint32_t delay_den; + uint32_t dop; + uint32_t bop; + bool m_is_fcTL_loaded; + bool m_is_IDAT_loaded; }; @@ -84,14 +211,39 @@ public: PngEncoder(); virtual ~PngEncoder(); - bool isFormatSupported( int depth ) const CV_OVERRIDE; - bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + bool isFormatSupported( int depth ) const CV_OVERRIDE; + bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + bool writeanimation(const Animation& animinfo, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; protected: - static void writeDataToBuf(void* png_ptr, uchar* src, size_t size); + static void writeDataToBuf(void* png_ptr, unsigned char* src, size_t size); static void flushBuf(void* png_ptr); + size_t write_to_io(void const* _Buffer, size_t _ElementSize, size_t _ElementCount, FILE* _Stream); + +private: + void writeChunk(FILE* f, const char* name, unsigned char* data, uint32_t length); + void writeIDATs(FILE* f, int frame, unsigned char* data, uint32_t length, uint32_t idat_size); + void processRect(unsigned char* row, int rowbytes, int bpp, int stride, int h, unsigned char* rows); + void deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, int stride, unsigned char* rows, int zbuf_size, int n); + void deflateRectOp(unsigned char* pdata, int x, int y, int w, int h, int bpp, int stride, int zbuf_size, int n); + bool getRect(uint32_t w, uint32_t h, unsigned char* pimage1, unsigned char* pimage2, unsigned char* ptemp, uint32_t bpp, uint32_t stride, int zbuf_size, uint32_t has_tcolor, uint32_t tcolor, int n); + + AutoBuffer op_zbuf1; + AutoBuffer op_zbuf2; + AutoBuffer row_buf; + AutoBuffer sub_row; + AutoBuffer up_row; + AutoBuffer avg_row; + AutoBuffer paeth_row; + z_stream op_zstream1; + z_stream op_zstream2; + OP op[6]; + rgb palette[256]; + unsigned char trns[256]; + uint32_t palsize, trnssize; + uint32_t next_seq_num; }; } diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index ca54effe86..2d55995789 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -44,17 +44,15 @@ #include "precomp.hpp" -#include -#include - #include #include - #include "grfmt_webp.hpp" - -#include "opencv2/imgproc.hpp" - +#include #include +#include +#include +#include +#include namespace cv { @@ -67,12 +65,18 @@ static const size_t WEBP_HEADER_SIZE = 32; WebPDecoder::WebPDecoder() { m_buf_supported = true; - channels = 0; fs_size = 0; + m_has_animation = false; + m_previous_timestamp = 0; } WebPDecoder::~WebPDecoder() {} +void WebPDecoder::UniquePtrDeleter::operator()(WebPAnimDecoder* decoder) const +{ + WebPAnimDecoderDelete(decoder); +} + size_t WebPDecoder::signatureLength() const { return WEBP_HEADER_SIZE; @@ -102,6 +106,11 @@ ImageDecoder WebPDecoder::newDecoder() const bool WebPDecoder::readHeader() { + if (m_has_animation) + { + return true; + } + uint8_t header[WEBP_HEADER_SIZE] = { 0 }; if (m_buf.empty()) { @@ -124,28 +133,49 @@ bool WebPDecoder::readHeader() } WebPBitstreamFeatures features; - if (VP8_STATUS_OK == WebPGetFeatures(header, sizeof(header), &features)) + if (VP8_STATUS_OK < WebPGetFeatures(header, sizeof(header), &features)) return false; + + m_has_animation = features.has_animation == 1; + + if (m_has_animation) { - CV_CheckEQ(features.has_animation, 0, "WebP backend does not support animated webp images"); - - m_width = features.width; - m_height = features.height; - - if (features.has_alpha) + if (m_buf.empty()) { - m_type = CV_8UC4; - channels = 4; - } - else - { - m_type = CV_8UC3; - channels = 3; + fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error"); + data.create(1, validateToInt(fs_size), CV_8UC1); + fs.read((char*)data.ptr(), fs_size); + CV_Assert(fs && "Can't read file data"); + fs.close(); } - return true; + CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1); + + WebPData webp_data; + webp_data.bytes = (const uint8_t*)data.ptr(); + webp_data.size = data.total(); + + WebPAnimDecoderOptions dec_options; + WebPAnimDecoderOptionsInit(&dec_options); + + dec_options.color_mode = m_use_rgb ? MODE_RGBA : MODE_BGRA; + anim_decoder.reset(WebPAnimDecoderNew(&webp_data, &dec_options)); + CV_Assert(anim_decoder.get() && "Error parsing image"); + + WebPAnimInfo anim_info; + WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info); + m_animation.loop_count = anim_info.loop_count; + + m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF; + m_animation.bgcolor[1] = (anim_info.bgcolor >> 16) & 0xFF; + m_animation.bgcolor[2] = (anim_info.bgcolor >> 8) & 0xFF; + m_animation.bgcolor[3] = anim_info.bgcolor & 0xFF; + m_frame_count = anim_info.frame_count; } + m_width = features.width; + m_height = features.height; + m_type = features.has_alpha ? CV_8UC4 : CV_8UC3; - return false; + return true; } bool WebPDecoder::readData(Mat &img) @@ -155,7 +185,7 @@ bool WebPDecoder::readData(Mat &img) CV_CheckEQ(img.cols, m_width, ""); CV_CheckEQ(img.rows, m_height, ""); - if (m_buf.empty()) + if (data.empty()) { fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error"); data.create(1, validateToInt(fs_size), CV_8UC1); @@ -165,70 +195,96 @@ bool WebPDecoder::readData(Mat &img) } CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1); + Mat read_img; + CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, ""); + if (img.type() != m_type || img.cols != m_width || img.rows != m_height) { - Mat read_img; - CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, ""); - if (img.type() != m_type) + read_img.create(m_height, m_width, m_type); + } + else + { + read_img = img; // copy header + } + + uchar* out_data = read_img.ptr(); + size_t out_data_size = read_img.dataend - out_data; + + uchar* res_ptr = NULL; + + if (m_has_animation) + { + uint8_t* buf; + int timestamp; + + WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp); + Mat tmp(Size(m_width, m_height), CV_8UC4, buf); + + if (img.type() == CV_8UC1) { - read_img.create(m_height, m_width, m_type); + cvtColor(tmp, img, COLOR_BGR2GRAY); } else + if (img.type() == CV_8UC3) { - read_img = img; // copy header - } - - uchar* out_data = read_img.ptr(); - size_t out_data_size = read_img.dataend - out_data; - - uchar *res_ptr = NULL; - if (channels == 3) - { - CV_CheckTypeEQ(read_img.type(), CV_8UC3, ""); - if (m_use_rgb) - res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - else - res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - } - else if (channels == 4) - { - CV_CheckTypeEQ(read_img.type(), CV_8UC4, ""); - if (m_use_rgb) - res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - else - res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - } - - if (res_ptr != out_data) - return false; - - if (read_img.data == img.data && img.type() == m_type) - { - // nothing - } - else if (img.type() == CV_8UC1) - { - cvtColor(read_img, img, COLOR_BGR2GRAY); - } - else if (img.type() == CV_8UC3 && m_type == CV_8UC4) - { - cvtColor(read_img, img, COLOR_BGRA2BGR); - } - else if (img.type() == CV_8UC4 && m_type == CV_8UC3) - { - cvtColor(read_img, img, COLOR_BGR2BGRA); + cvtColor(tmp, img, COLOR_BGRA2BGR); } else - { - CV_Error(Error::StsInternal, ""); - } + tmp.copyTo(img); + + m_animation.durations.push_back(timestamp - m_previous_timestamp); + m_previous_timestamp = timestamp; + return true; + } + + if (m_type == CV_8UC3) + { + CV_CheckTypeEQ(read_img.type(), CV_8UC3, ""); + if (m_use_rgb) + res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + else + res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + } + else if (m_type == CV_8UC4) + { + CV_CheckTypeEQ(read_img.type(), CV_8UC4, ""); + if (m_use_rgb) + res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + else + res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + } + + if (res_ptr != out_data) + return false; + + if (read_img.data == img.data && img.type() == m_type) + { + // nothing + } + else if (img.type() == CV_8UC1) + { + cvtColor(read_img, img, COLOR_BGR2GRAY); + } + else if (img.type() == CV_8UC3 && m_type == CV_8UC4) + { + cvtColor(read_img, img, COLOR_BGRA2BGR); + } + else + { + CV_Error(Error::StsInternal, ""); } return true; } +bool WebPDecoder::nextPage() +{ + // Prepare the next page, if any. + return WebPAnimDecoderHasMoreFrames(anim_decoder.get()) > 0; +} + WebPEncoder::WebPEncoder() { m_description = "WebP files (*.webp)"; @@ -312,23 +368,138 @@ bool WebPEncoder::write(const Mat& img, const std::vector& params) #endif CV_Assert(size > 0); - + size_t bytes_written = 0; if (m_buf) { m_buf->resize(size); memcpy(&(*m_buf)[0], out, size); + bytes_written = size; } else { FILE *fd = fopen(m_filename.c_str(), "wb"); if (fd != NULL) { - fwrite(out, size, sizeof(uint8_t), fd); + bytes_written = fwrite(out, sizeof(uint8_t), size, fd); + if (size != bytes_written) + { + CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, size)); + } fclose(fd); fd = NULL; } } - return size > 0; + return (size > 0) && (bytes_written == size); +} + +bool WebPEncoder::writeanimation(const Animation& animation, const std::vector& params) +{ + CV_CheckDepthEQ(animation.frames[0].depth(), CV_8U, "WebP codec supports only 8-bit unsigned images"); + int ok = 0; + int timestamp = 0; + const int width = animation.frames[0].cols, height = animation.frames[0].rows; + + WebPAnimEncoderOptions anim_config; + WebPConfig config; + WebPPicture pic; + WebPData webp_data; + + WebPDataInit(&webp_data); + if (!WebPAnimEncoderOptionsInit(&anim_config) || + !WebPConfigInit(&config) || + !WebPPictureInit(&pic)) { + CV_LOG_ERROR(NULL, "Library version mismatch!\n"); + WebPDataClear(&webp_data); + return false; + } + + int bgvalue = (static_cast(animation.bgcolor[0]) & 0xFF) << 24 | + (static_cast(animation.bgcolor[1]) & 0xFF) << 16 | + (static_cast(animation.bgcolor[2]) & 0xFF) << 8 | + (static_cast(animation.bgcolor[3]) & 0xFF); + + anim_config.anim_params.bgcolor = bgvalue; + anim_config.anim_params.loop_count = animation.loop_count; + + if (params.size() > 1) + { + if (params[0] == IMWRITE_WEBP_QUALITY) + { + config.lossless = 0; + config.quality = static_cast(params[1]); + if (config.quality < 1.0f) + { + config.quality = 1.0f; + } + if (config.quality >= 100.0f) + { + config.lossless = 1; + } + } + anim_config.minimize_size = 0; + } + + std::unique_ptr anim_encoder( + WebPAnimEncoderNew(width, height, &anim_config), WebPAnimEncoderDelete); + + pic.width = width; + pic.height = height; + pic.use_argb = 1; + pic.argb_stride = width; + WebPEncode(&config, &pic); + + bool is_input_rgba = animation.frames[0].channels() == 4; + Size canvas_size = Size(animation.frames[0].cols,animation.frames[0].rows); + + for (size_t i = 0; i < animation.frames.size(); i++) + { + Mat argb; + CV_Assert(canvas_size == Size(animation.frames[i].cols,animation.frames[i].rows)); + if (is_input_rgba) + pic.argb = (uint32_t*)animation.frames[i].data; + else + { + cvtColor(animation.frames[i], argb, COLOR_BGR2BGRA); + pic.argb = (uint32_t*)argb.data; + } + ok = WebPAnimEncoderAdd(anim_encoder.get(), &pic, timestamp, &config); + timestamp += animation.durations[i]; + } + + // add a last fake frame to signal the last duration + ok = ok & WebPAnimEncoderAdd(anim_encoder.get(), NULL, timestamp, NULL); + ok = ok & WebPAnimEncoderAssemble(anim_encoder.get(), &webp_data); + + size_t bytes_written = 0; + if (ok) + { + if (m_buf) + { + m_buf->resize(webp_data.size); + memcpy(&(*m_buf)[0], webp_data.bytes, webp_data.size); + bytes_written = webp_data.size; + } + else + { + FILE* fd = fopen(m_filename.c_str(), "wb"); + if (fd != NULL) + { + bytes_written = fwrite(webp_data.bytes, sizeof(uint8_t), webp_data.size, fd); + if (webp_data.size != bytes_written) + { + CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, webp_data.size)); + } + fclose(fd); fd = NULL; + } + } + } + + bool status = (ok > 0) && (webp_data.size == bytes_written); + + // free resources + WebPDataClear(&webp_data); + + return status; } } diff --git a/modules/imgcodecs/src/grfmt_webp.hpp b/modules/imgcodecs/src/grfmt_webp.hpp index 6d833e0db6..f9ceb5e524 100644 --- a/modules/imgcodecs/src/grfmt_webp.hpp +++ b/modules/imgcodecs/src/grfmt_webp.hpp @@ -49,6 +49,8 @@ #include +struct WebPAnimDecoder; + namespace cv { @@ -61,6 +63,7 @@ public: bool readData( Mat& img ) CV_OVERRIDE; bool readHeader() CV_OVERRIDE; + bool nextPage() CV_OVERRIDE; size_t signatureLength() const CV_OVERRIDE; bool checkSignature( const String& signature) const CV_OVERRIDE; @@ -68,10 +71,16 @@ public: ImageDecoder newDecoder() const CV_OVERRIDE; protected: + struct UniquePtrDeleter { + void operator()(WebPAnimDecoder* decoder) const; + }; + std::ifstream fs; size_t fs_size; Mat data; - int channels; + std::unique_ptr anim_decoder; + bool m_has_animation; + int m_previous_timestamp; }; class WebPEncoder CV_FINAL : public BaseImageEncoder @@ -81,6 +90,7 @@ public: ~WebPEncoder() CV_OVERRIDE; bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; + bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; }; diff --git a/modules/imgcodecs/src/grfmts.hpp b/modules/imgcodecs/src/grfmts.hpp index 46b79ff96c..9fcc27740c 100644 --- a/modules/imgcodecs/src/grfmts.hpp +++ b/modules/imgcodecs/src/grfmts.hpp @@ -45,8 +45,10 @@ #include "grfmt_base.hpp" #include "grfmt_avif.hpp" #include "grfmt_bmp.hpp" +#include "grfmt_gif.hpp" #include "grfmt_sunras.hpp" #include "grfmt_jpeg.hpp" +#include "grfmt_jpegxl.hpp" #include "grfmt_pxm.hpp" #include "grfmt_pfm.hpp" #include "grfmt_tiff.hpp" diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index d84c1332c8..6b110303b8 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -151,14 +151,18 @@ struct ImageCodecInitializer */ ImageCodecInitializer() { -#ifdef HAVE_AVIF - decoders.push_back(makePtr()); - encoders.push_back(makePtr()); -#endif /// BMP Support decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); + #ifdef HAVE_IMGCODEC_GIF + decoders.push_back( makePtr() ); + encoders.push_back( makePtr() ); + #endif + #ifdef HAVE_AVIF + decoders.push_back(makePtr()); + encoders.push_back(makePtr()); + #endif #ifdef HAVE_IMGCODEC_HDR decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); @@ -206,6 +210,10 @@ struct ImageCodecInitializer decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); #endif + #ifdef HAVE_JPEGXL + decoders.push_back( makePtr() ); + encoders.push_back( makePtr() ); + #endif #ifdef HAVE_OPENJPEG decoders.push_back( makePtr() ); decoders.push_back( makePtr() ); @@ -685,6 +693,117 @@ bool imreadmulti(const String& filename, std::vector& mats, int start, int return imreadmulti_(filename, flags, mats, start, count); } +static bool +imreadanimation_(const String& filename, int flags, int start, int count, Animation& animation) +{ + bool success = false; + if (start < 0) { + start = 0; + } + if (count < 0) { + count = INT16_MAX; + } + + /// Search for the relevant decoder to handle the imagery + ImageDecoder decoder; + decoder = findDecoder(filename); + + /// if no decoder was found, return false. + if (!decoder) { + CV_LOG_WARNING(NULL, "Decoder for " << filename << " not found!\n"); + return false; + } + + /// set the filename in the driver + decoder->setSource(filename); + // read the header to make sure it succeeds + try + { + // read the header to make sure it succeeds + if (!decoder->readHeader()) + return false; + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read header: " << e.what()); + return false; + } + catch (...) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read header: unknown exception"); + return false; + } + + int current = 0; + int frame_count = (int)decoder->getFrameCount(); + count = count + start > frame_count ? frame_count - start : count; + + uint64 pixels = (uint64)decoder->width() * (uint64)decoder->height() * (uint64)(count + 4); + if (pixels > CV_IO_MAX_IMAGE_PIXELS) { + CV_LOG_WARNING(NULL, "\nyou are trying to read " << pixels << + " bytes that exceed CV_IO_MAX_IMAGE_PIXELS.\n"); + return false; + } + + while (current < start + count) + { + // grab the decoded type + const int type = calcType(decoder->type(), flags); + + // established the required input image size + Size size = validateInputImageSize(Size(decoder->width(), decoder->height())); + + // read the image data + Mat mat(size.height, size.width, type); + success = false; + try + { + if (decoder->readData(mat)) + success = true; + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read data: " << e.what()); + } + catch (...) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read data: unknown exception"); + } + if (!success) + break; + + // optionally rotate the data if EXIF' orientation flag says so + if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) + { + ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + } + + if (current >= start) + { + int duration = decoder->animation().durations.size() > 0 ? decoder->animation().durations.back() : 1000; + animation.durations.push_back(duration); + animation.frames.push_back(mat); + } + + if (!decoder->nextPage()) + { + break; + } + ++current; + } + animation.bgcolor = decoder->animation().bgcolor; + animation.loop_count = decoder->animation().loop_count; + + return success; +} + +bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start, int count) +{ + CV_TRACE_FUNCTION(); + + return imreadanimation_(filename, IMREAD_UNCHANGED, start, count, animation); +} + static size_t imcount_(const String& filename, int flags) { @@ -801,6 +920,55 @@ bool imwrite( const String& filename, InputArray _img, return imwrite_(filename, img_vec, params, false); } +static bool imwriteanimation_(const String& filename, const Animation& animation, const std::vector& params) +{ + ImageEncoder encoder = findEncoder(filename); + if (!encoder) + CV_Error(Error::StsError, "could not find a writer for the specified extension"); + + encoder->setDestination(filename); + + bool code = false; + try + { + code = encoder->writeanimation(animation, params); + + if (!code) + { + FILE* f = fopen(filename.c_str(), "wb"); + if (!f) + { + if (errno == EACCES) + { + CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't open file for writing: permission denied"); + } + } + else + { + fclose(f); + remove(filename.c_str()); + } + } + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't write data: " << e.what()); + } + catch (...) + { + CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't write data: unknown exception"); + } + + return code; +} + +bool imwriteanimation(const String& filename, const Animation& animation, const std::vector& params) +{ + CV_Assert(!animation.frames.empty()); + CV_Assert(animation.frames.size() == animation.durations.size()); + return imwriteanimation_(filename, animation, params); +} + static bool imdecode_( const Mat& buf, int flags, Mat& mat ) { @@ -1427,6 +1595,13 @@ ImageCollection::iterator ImageCollection::iterator::operator++(int) { return tmp; } +Animation::Animation(int loopCount, Scalar bgColor) + : loop_count(loopCount), bgcolor(bgColor) +{ + if (loopCount < 0 || loopCount > 0xffff) + this->loop_count = 0; // loop_count should be non-negative +} + } /* End of file. */ diff --git a/modules/imgcodecs/src/utils.hpp b/modules/imgcodecs/src/utils.hpp index 2a9451422e..95deebde31 100644 --- a/modules/imgcodecs/src/utils.hpp +++ b/modules/imgcodecs/src/utils.hpp @@ -137,7 +137,11 @@ uchar* FillGrayRow1( uchar* data, uchar* indices, int len, uchar* palette ); CV_INLINE bool isBigEndian( void ) { - return (((const int*)"\0\x1\x2\x3\x4\x5\x6\x7")[0] & 255) != 0; +#ifdef WORDS_BIGENDIAN + return true; +#else + return false; +#endif } } // namespace diff --git a/modules/imgcodecs/test/test_animation.cpp b/modules/imgcodecs/test/test_animation.cpp new file mode 100644 index 0000000000..e8c42cbcc0 --- /dev/null +++ b/modules/imgcodecs/test/test_animation.cpp @@ -0,0 +1,536 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + +static void readFileBytes(const std::string& fname, std::vector& buf) +{ + FILE * wfile = fopen(fname.c_str(), "rb"); + if (wfile != NULL) + { + fseek(wfile, 0, SEEK_END); + size_t wfile_size = ftell(wfile); + fseek(wfile, 0, SEEK_SET); + + buf.resize(wfile_size); + + size_t data_size = fread(&buf[0], 1, wfile_size, wfile); + + if(wfile) + { + fclose(wfile); + } + + EXPECT_EQ(data_size, wfile_size); + } +} + +static bool fillFrames(Animation& animation, bool hasAlpha, int n = 14) +{ + // Set the path to the test image directory and filename for loading. + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "pngsuite/tp1n3p08.png"; + + EXPECT_TRUE(imreadanimation(filename, animation)); + EXPECT_EQ(1000, animation.durations.back()); + + if (!hasAlpha) + cvtColor(animation.frames[0], animation.frames[0], COLOR_BGRA2BGR); + + animation.loop_count = 0xffff; // 0xffff is the maximum value to set. + + // Add the first frame with a duration value of 400 milliseconds. + int duration = 80; + animation.durations[0] = duration * 5; + Mat image = animation.frames[0].clone(); + putText(animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + + // Define a region of interest (ROI) + Rect roi(2, 16, 26, 16); + + // Modify the ROI in n iterations to simulate slight changes in animation frames. + for (int i = 1; i < n; i++) + { + roi.x++; + roi.width -= 2; + RNG rng = theRNG(); + for (int x = roi.x; x < roi.x + roi.width; x++) + for (int y = roi.y; y < roi.y + roi.height; y++) + { + if (hasAlpha) + { + Vec4b& pixel = image.at(y, x); + if (pixel[3] > 0) + { + if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(2, 5); + if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(2, 5); + if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(2, 5); + pixel[3] -= (uchar)rng.uniform(2, 5); + } + } + else + { + Vec3b& pixel = image.at(y, x); + if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(2, 5); + if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(2, 5); + if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(2, 5); + } + } + + // Update the duration and add the modified frame to the animation. + duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly). + animation.frames.push_back(image.clone()); + putText(animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + animation.durations.push_back(duration); + } + + // Add two identical frames with the same duration. + if (animation.frames.size() > 1 && animation.frames.size() < 20) + { + animation.durations.push_back(++duration); + animation.frames.push_back(animation.frames.back()); + animation.durations.push_back(++duration); + animation.frames.push_back(animation.frames.back()); + } + + return true; +} + +#ifdef HAVE_IMGCODEC_GIF + +TEST(Imgcodecs_Gif, imwriteanimation_rgba) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + s_animation.bgcolor = Scalar(0, 0, 0, 0); // TO DO not implemented yet. + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".gif"); + + // Write the animation to a .webp file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + size_t expected_frame_count = s_animation.frames.size(); + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + // Check that the background color and loop count match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order + EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(cvRound(s_animation.durations[i] / 10), cvRound(l_animation.durations[i] / 10)); + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[16], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[17], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[18], NORM_INF)); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_UNCHANGED); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); + + std::vector buf; + readFileBytes(output, buf); + vector webp_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count, webp_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +#endif // HAVE_IMGCODEC_GIF + +#ifdef HAVE_WEBP + +TEST(Imgcodecs_WebP, imwriteanimation_rgba) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose. + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".webp"); + + // Write the animation to a .webp file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + // Since the last frames are identical, WebP optimizes by storing only one of them, + // and the duration value for the last frame is handled by libwebp. + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + // Check that the background color and loop count match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order + EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF)); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_UNCHANGED); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); + + std::vector buf; + readFileBytes(output, buf); + vector webp_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count, webp_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imwriteanimation_rgb) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".webp"); + + // Write the animation to a .webp file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + // Since the last frames are identical, WebP optimizes by storing only one of them, + // and the duration value for the last frame is handled by libwebp. + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0); + EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0); + EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_COLOR); + EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0); + + std::vector buf; + readFileBytes(output, buf); + + vector webp_frames; + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count,webp_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imwritemulti_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + string output = cv::tempfile(".webp"); + ASSERT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + ASSERT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED)); + EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size()); + EXPECT_EQ(4, s_animation.frames[0].channels()); + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imwritemulti_rgb) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + string output = cv::tempfile(".webp"); + ASSERT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + ASSERT_TRUE(imreadmulti(output, read_frames)); + EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size()); + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imencode_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true, 3)); + + std::vector buf; + vector apng_frames; + + // Test encoding and decoding the images in memory (without saving to disk). + EXPECT_TRUE(imencode(".webp", s_animation.frames, buf)); + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames)); + EXPECT_EQ(s_animation.frames.size() - 2, apng_frames.size()); +} + +#endif // HAVE_WEBP + +#ifdef HAVE_PNG + +TEST(Imgcodecs_APNG, imwriteanimation_rgba) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + { + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF)); + } + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF)); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_UNCHANGED); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); + + std::vector buf; + readFileBytes(output, buf); + vector apng_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames)); + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + apng_frames.clear(); + // Test saving the animation frames as individual still images. + EXPECT_TRUE(imwrite(output, s_animation.frames)); + + // Read back the still images into a vector of Mats. + EXPECT_TRUE(imreadmulti(output, apng_frames)); + + // Expect all frames written as multi-page image + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imwriteanimation_rgba16u) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + for (size_t i = 0; i < s_animation.frames.size(); i++) + { + s_animation.frames[i].convertTo(s_animation.frames[i], CV_16U, 255); + } + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + std::vector buf; + readFileBytes(output, buf); + vector apng_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames)); + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + apng_frames.clear(); + // Test saving the animation frames as individual still images. + EXPECT_TRUE(imwrite(output, s_animation.frames)); + + // Read back the still images into a vector of Mats. + EXPECT_TRUE(imreadmulti(output, apng_frames)); + + // Expect all frames written as multi-page image + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imwriteanimation_rgb) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + EXPECT_EQ(l_animation.frames.size(), s_animation.frames.size() - 2); + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + { + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF)); + } + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imwritemulti_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + string output = cv::tempfile(".png"); + EXPECT_EQ(true, imwrite(output, s_animation.frames)); + vector read_frames; + EXPECT_EQ(true, imreadmulti(output, read_frames, IMREAD_UNCHANGED)); + EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2); + EXPECT_EQ(imcount(output), read_frames.size()); + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imwritemulti_rgb) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + string output = cv::tempfile(".png"); + ASSERT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + ASSERT_TRUE(imreadmulti(output, read_frames)); + EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2); + EXPECT_EQ(0, remove(output.c_str())); + + for (size_t i = 0; i < read_frames.size(); i++) + { + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF)); + } +} + +TEST(Imgcodecs_APNG, imwritemulti_gray) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + for (size_t i = 0; i < s_animation.frames.size(); i++) + { + cvtColor(s_animation.frames[i], s_animation.frames[i], COLOR_BGR2GRAY); + } + + string output = cv::tempfile(".png"); + EXPECT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + EXPECT_TRUE(imreadmulti(output, read_frames)); + EXPECT_EQ(1, read_frames[0].channels()); + read_frames.clear(); + EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED)); + EXPECT_EQ(1, read_frames[0].channels()); + read_frames.clear(); + EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_COLOR)); + EXPECT_EQ(3, read_frames[0].channels()); + read_frames.clear(); + EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_GRAYSCALE)); + EXPECT_EQ(0, remove(output.c_str())); + + for (size_t i = 0; i < read_frames.size(); i++) + { + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF)); + } +} + +TEST(Imgcodecs_APNG, imwriteanimation_bgcolor) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true, 2)); + s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose. + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + // Check that the background color match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); + EXPECT_EQ(0, remove(output.c_str())); + + EXPECT_TRUE(fillFrames(s_animation, true, 2)); + s_animation.bgcolor = Scalar(); + + output = cv::tempfile(".png"); + EXPECT_TRUE(imwriteanimation(output, s_animation)); + EXPECT_TRUE(imreadanimation(output, l_animation)); + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); + + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imencode_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true, 3)); + + std::vector buf; + vector read_frames; + // Test encoding and decoding the images in memory (without saving to disk). + EXPECT_TRUE(imencode(".png", s_animation.frames, buf)); + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, read_frames)); + EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2); +} + +#endif // HAVE_PNG + +}} // namespace diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index 62cd471e23..d1a9e720a9 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -2,6 +2,9 @@ // It is subject to the license terms in the LICENSE file found in the top-level // directory of this distribution and at http://opencv.org/license.html +#include +#include + #include "test_precomp.hpp" namespace opencv_test { namespace { @@ -110,7 +113,7 @@ TEST_P(Exif, exif_orientation) } } -const string exif_files[] = +const std::vector exif_files { #ifdef HAVE_JPEG "readwrite/testExifOrientation_1.jpg", diff --git a/modules/imgcodecs/test/test_exr.impl.hpp b/modules/imgcodecs/test/test_exr.impl.hpp index 32984ff731..6b4ac0b8d1 100644 --- a/modules/imgcodecs/test/test_exr.impl.hpp +++ b/modules/imgcodecs/test/test_exr.impl.hpp @@ -35,7 +35,8 @@ TEST(Imgcodecs_EXR, readWrite_32FC1) ASSERT_TRUE(cv::imwrite(filenameOutput, img)); // Check generated file size to ensure that it's compressed with proper options - ASSERT_EQ(396u, getFileSize(filenameOutput)); + ASSERT_LE(396u, getFileSize(filenameOutput)); // OpenEXR 2 + ASSERT_LE( getFileSize(filenameOutput), 440u); // OpenEXR 3.2+ const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); ASSERT_EQ(img2.type(), img.type()); ASSERT_EQ(img2.size(), img.size()); @@ -199,7 +200,11 @@ TEST(Imgcodecs_EXR, read_YC_changeDepth) cvtColor(img_rgb, img_rgb, COLOR_RGB2BGR); - EXPECT_TRUE(cvtest::norm(img, img_rgb, NORM_INF) == 0); + // See https://github.com/opencv/opencv/issues/26705 + // If ALGO_HINT_ACCURATE is set, norm should be 0. + // If ALGO_HINT_APPROX is set, norm should be 1(or 0). + EXPECT_LE(cvtest::norm(img, img_rgb, NORM_INF), + (cv::getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE)?0:1); // Cannot test writing, EXR encoder doesn't support 8U depth } diff --git a/modules/imgcodecs/test/test_gif.cpp b/modules/imgcodecs/test/test_gif.cpp new file mode 100644 index 0000000000..b9c49c0bdb --- /dev/null +++ b/modules/imgcodecs/test/test_gif.cpp @@ -0,0 +1,357 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html +#include "test_precomp.hpp" + +#ifdef HAVE_IMGCODEC_GIF + +namespace opencv_test { namespace { + +const string gifsuite_files_multi[]={ + "basi3p01", + "basi3p02", + "basi3p04", + "basn3p01", + "basn3p02", + "basn3p04", + "ccwn3p08", + "ch1n3p04", + "cs3n3p08", + "cs5n3p08", + "cs8n3p08", + "g03n3p04", + "g04n3p04", + "g05n3p04", + "g07n3p04", + "g10n3p04", + "g25n3p04", + "s32i3p04", + "s32n3p04", + "tp0n3p08", +}; + +const string gifsuite_files_read_single[] = { + "basi3p01", + "basi3p02", + "basi3p04", + "basn3p01", + "basn3p02", + "basn3p04", + "ccwn3p08", + "cdfn2c08", + "cdhn2c08", + "cdsn2c08", + "cdun2c08", + "ch1n3p04", + "cs3n3p08", + "cs5n2c08", + "cs5n3p08", + "cs8n2c08", + "cs8n3p08", + "exif2c08", + "g03n2c08", + "g03n3p04", + "g04n2c08", + "g04n3p04", + "g05n2c08", + "g05n3p04", + "g07n2c08", + "g07n3p04", + "g10n2c08", + "g10n3p04" +}; + +const string gifsuite_files_read_write_suite[]={ + "g25n2c08", + "g25n3p04", + "s01i3p01", + "s01n3p01", + "s02i3p01", + "s02n3p01", + "s03i3p01", + "s03n3p01", + "s04i3p01", + "s04n3p01", + "s05i3p02", + "s05n3p02", + "s06i3p02", + "s06n3p02", + "s07i3p02", + "s07n3p02", + "s08i3p02", + "s08n3p02", + "s09i3p02", + "s09n3p02", + "s32i3p04", + "s32n3p04", + "s33i3p04", + "s33n3p04", + "s34i3p04", + "s34n3p04", + "s35i3p04", + "s35n3p04", + "s36i3p04", + "s36n3p04", + "s37i3p04", + "s37n3p04", + "s38i3p04", + "s38n3p04", + "s39i3p04", + "s39n3p04", + "s40i3p04", + "s40n3p04", + "tp0n3p08", +}; + +const std::pair gifsuite_files_bgra[]={ + make_pair("gif_bgra1",53287), + make_pair("gif_bgra2",52651), + make_pair("gif_bgra3",54809), + make_pair("gif_bgra4",57562), + make_pair("gif_bgra5",56733), + make_pair("gif_bgra6",52110), +}; + +TEST(Imgcodecs_Gif, read_gif_multi) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "gifsuite/gif_multi.gif"; + vector img_vec_8UC4; + ASSERT_NO_THROW(cv::imreadmulti(filename, img_vec_8UC4,0,20,IMREAD_UNCHANGED)); + EXPECT_EQ(img_vec_8UC4.size(), imcount(filename)); + vector img_vec_8UC3; + for(const auto & i : img_vec_8UC4){ + cv::Mat img_tmp; + cvtColor(i,img_tmp,COLOR_BGRA2BGR); + img_vec_8UC3.push_back(img_tmp); + } + const long unsigned int expected_size=20; + EXPECT_EQ(img_vec_8UC3.size(),expected_size); + for(long unsigned int i=0;i Imgcodecs_Gif_GifSuite_SingleFrame; + +TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame, read_gif_single) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "gifsuite/" + GetParam() + ".gif"; + const string png_filename=root + "pngsuite/" + GetParam() + ".png"; + const long unsigned int expected_size = 1; + + EXPECT_EQ(expected_size, imcount(filename)); + cv::Mat img_8UC4; + ASSERT_NO_THROW(img_8UC4 = cv::imread(filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_8UC4.empty()); + cv::Mat img_8UC3; + ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3, COLOR_BGRA2BGR)); + cv::Mat img_png; + ASSERT_NO_THROW(img_png = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_png.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame, + testing::ValuesIn(gifsuite_files_read_single)); + +TEST(Imgcodecs_Gif, read_gif_big){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = root + "gifsuite/gif_big.gif"; + const string png_filename = root + "gifsuite/gif_big.png"; + cv::Mat img_8UC4; + ASSERT_NO_THROW(img_8UC4 = imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_8UC4.empty()); + cv::Mat img_8UC3; + const int expected_col=1303; + const int expected_row=1391; + EXPECT_EQ(expected_col, img_8UC4.cols); + EXPECT_EQ(expected_row, img_8UC4.rows); + ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3,COLOR_BGRA2BGR)); + EXPECT_EQ(expected_col, img_8UC3.cols); + EXPECT_EQ(expected_row, img_8UC3.rows); + cv::Mat img_png; + ASSERT_NO_THROW(img_png=imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_png.empty()); + cv::Mat img_png_8UC3; + ASSERT_NO_THROW(cvtColor(img_png,img_png_8UC3, COLOR_BGRA2BGR)); + EXPECT_EQ(img_8UC3.size, img_png_8UC3.size); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png_8UC3); +} + +typedef testing::TestWithParam> Imgcodecs_Gif_GifSuite_SingleFrame_BGRA; + +TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame_BGRA, read_gif_single_bgra){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = root + "gifsuite/" + GetParam().first + ".gif"; + const string png_filename = root + "gifsuite/" + GetParam().first + ".png"; + cv::Mat gif_img; + cv::Mat png_img; + ASSERT_NO_THROW(gif_img = cv::imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(gif_img.empty()); + ASSERT_NO_THROW(png_img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(png_img.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img, png_img); + int transparent_count = 0; + for(int i=0; i(i,j); + if((int)(pixel1[3]) == 0){ + transparent_count++; + } + } + } + EXPECT_EQ(transparent_count,GetParam().second); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame_BGRA , + testing::ValuesIn(gifsuite_files_bgra)); + +TEST(Imgcodecs_Gif,read_gif_multi_bgra){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = root + "gifsuite/gif_multi_bgra.gif"; + vector img_vec; + ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec, IMREAD_UNCHANGED)); + EXPECT_EQ(imcount(gif_filename), img_vec.size()); + const int fixed_transparent_count = 53211; + for(auto & frame_count : img_vec){ + int transparent_count=0; + for(int i=0; i(i,j); + if((int)(pixel1[3]) == 0){ + transparent_count++; + } + } + } + EXPECT_EQ(fixed_transparent_count,transparent_count); + } +} + +TEST(Imgcodecs_Gif, read_gif_special){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename1 = root + "gifsuite/special1.gif"; + const string png_filename1 = root + "gifsuite/special1.png"; + const string gif_filename2 = root + "gifsuite/special2.gif"; + const string png_filename2 = root + "gifsuite/special2.png"; + cv::Mat gif_img1; + ASSERT_NO_THROW(gif_img1 = cv::imread(gif_filename1,IMREAD_UNCHANGED)); + ASSERT_FALSE(gif_img1.empty()); + cv::Mat png_img1; + ASSERT_NO_THROW(png_img1 = cv::imread(png_filename1,IMREAD_UNCHANGED)); + ASSERT_FALSE(png_img1.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img1, png_img1); + cv::Mat gif_img2; + ASSERT_NO_THROW(gif_img2 = cv::imread(gif_filename2,IMREAD_UNCHANGED)); + ASSERT_FALSE(gif_img2.empty()); + cv::Mat png_img2; + ASSERT_NO_THROW(png_img2 = cv::imread(png_filename2,IMREAD_UNCHANGED)); + ASSERT_FALSE(png_img2.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img2, png_img2); +} + +TEST(Imgcodecs_Gif,write_gif_flags){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string png_filename = root + "gifsuite/special1.png"; + vector buff; + const int expected_rows=611; + const int expected_cols=293; + Mat img_gt = Mat::ones(expected_rows, expected_cols, CV_8UC1); + vector param; + param.push_back(IMWRITE_GIF_QUALITY); + param.push_back(7); + param.push_back(IMWRITE_GIF_DITHER); + param.push_back(2); + EXPECT_NO_THROW(imencode(".png", img_gt, buff, param)); + Mat img; + EXPECT_NO_THROW(img = imdecode(buff, IMREAD_ANYDEPTH)); // hang + EXPECT_FALSE(img.empty()); + EXPECT_EQ(img.cols, expected_cols); + EXPECT_EQ(img.rows, expected_rows); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt); +} + +TEST(Imgcodecs_Gif, write_gif_big) { + const string root = cvtest::TS::ptr()->get_data_path(); + const string png_filename = root + "gifsuite/gif_big.png"; + const string gif_filename = cv::tempfile(".png"); + cv::Mat img; + ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img.empty()); + EXPECT_EQ(1303, img.cols); + EXPECT_EQ(1391, img.rows); + ASSERT_NO_THROW(imwrite(gif_filename, img)); + cv::Mat img_gif; + ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_gif.empty()); + EXPECT_EQ(1303, img_gif.cols); + EXPECT_EQ(1391, img_gif.rows); + EXPECT_EQ(0, remove(gif_filename.c_str())); +} + +typedef testing::TestWithParam Imgcodecs_Gif_GifSuite_Read_Write_Suite; + +TEST_P(Imgcodecs_Gif_GifSuite_Read_Write_Suite ,read_gif_single) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string png_filename = root + "pngsuite/"+GetParam()+".png"; + const string gif_filename = cv::tempfile(".gif"); + cv::Mat img; + ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img.empty()); + vector param; + param.push_back(IMWRITE_GIF_QUALITY); + param.push_back(8); + param.push_back(IMWRITE_GIF_DITHER); + param.push_back(3); + ASSERT_NO_THROW(imwrite(gif_filename, img, param)); + cv::Mat img_gif; + ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_gif.empty()); + cv::Mat img_8UC3; + ASSERT_NO_THROW(cv::cvtColor(img_gif, img_8UC3, COLOR_BGRA2BGR)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img, img_8UC3); + EXPECT_EQ(0, remove(gif_filename.c_str())); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_Read_Write_Suite , + testing::ValuesIn(gifsuite_files_read_write_suite)); + +TEST(Imgcodecs_Gif, write_gif_multi) { + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = cv::tempfile(".gif"); + vector img_vec; + for (long unsigned int i = 0; i < 20; i++) { + const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png"; + cv::Mat img; + ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img.empty()); + img_vec.push_back(img); + } + vector param; + param.push_back(IMWRITE_GIF_QUALITY); + param.push_back(8); + param.push_back(IMWRITE_GIF_DITHER); + param.push_back(3); + ASSERT_NO_THROW(cv::imwritemulti(gif_filename, img_vec, param)); + vector img_vec_gif; + ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec_gif)); + EXPECT_EQ(img_vec.size(), img_vec_gif.size()); + for (long unsigned int i = 0; i < img_vec.size(); i++) { + cv::Mat img_8UC3; + ASSERT_NO_THROW(cv::cvtColor(img_vec_gif[i], img_8UC3, COLOR_BGRA2BGR)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img_vec[i], img_8UC3); + } + EXPECT_EQ(0, remove(gif_filename.c_str())); +} + +}//opencv_test +}//namespace + +#endif diff --git a/modules/imgcodecs/test/test_jpegxl.cpp b/modules/imgcodecs/test/test_jpegxl.cpp new file mode 100644 index 0000000000..6c5aad181f --- /dev/null +++ b/modules/imgcodecs/test/test_jpegxl.cpp @@ -0,0 +1,186 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level +// directory of this distribution and at http://opencv.org/license.html +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + +#ifdef HAVE_JPEGXL + +typedef tuple MatType_and_Distance; +typedef testing::TestWithParam Imgcodecs_JpegXL_MatType; + +TEST_P(Imgcodecs_JpegXL_MatType, write_read) +{ + const int matType = get<0>(GetParam()); + const int distanceParam = get<1>(GetParam()); + + cv::Scalar col; + // Jpeg XL is lossy compression. + // There may be small differences in decoding results by environments. + double th; + + switch( CV_MAT_DEPTH(matType) ) + { + case CV_16U: + col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 ); + th = 656; // = 65535 / 100; + break; + case CV_32F: + col = cv::Scalar(0.486, 0.298, 0.165, 0.75); + th = 1.0 / 100.0; + break; + default: + case CV_8U: + col = cv::Scalar(124, 76, 42, 192); + th = 3; // = 255 / 100 (1%); + break; + } + + // If increasing distanceParam, threshold should be increased. + th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + + bool ret = false; + string tmp_fname = cv::tempfile(".jxl"); + Mat img_org(320, 480, matType, col); + vector param; + param.push_back(IMWRITE_JPEGXL_DISTANCE); + param.push_back(distanceParam); + EXPECT_NO_THROW(ret = imwrite(tmp_fname, img_org, param)); + EXPECT_TRUE(ret); + Mat img_decoded; + EXPECT_NO_THROW(img_decoded = imread(tmp_fname, IMREAD_UNCHANGED)); + EXPECT_FALSE(img_decoded.empty()); + + EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th); + + EXPECT_EQ(0, remove(tmp_fname.c_str())); +} + +TEST_P(Imgcodecs_JpegXL_MatType, encode_decode) +{ + const int matType = get<0>(GetParam()); + const int distanceParam = get<1>(GetParam()); + + cv::Scalar col; + // Jpeg XL is lossy compression. + // There may be small differences in decoding results by environments. + double th; + + // If alpha=0, libjxl modify color channels(BGR). So do not set it. + switch( CV_MAT_DEPTH(matType) ) + { + case CV_16U: + col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 ); + th = 656; // = 65535 / 100; + break; + case CV_32F: + col = cv::Scalar(0.486, 0.298, 0.165, 0.75); + th = 1.0 / 100.0; + break; + default: + case CV_8U: + col = cv::Scalar(124, 76, 42, 192); + th = 3; // = 255 / 100 (1%); + break; + } + + // If increasing distanceParam, threshold should be increased. + th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + + bool ret = false; + vector buff; + Mat img_org(320, 480, matType, col); + vector param; + param.push_back(IMWRITE_JPEGXL_DISTANCE); + param.push_back(distanceParam); + EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param)); + EXPECT_TRUE(ret); + Mat img_decoded; + EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED)); + EXPECT_FALSE(img_decoded.empty()); + + EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th); +} + +INSTANTIATE_TEST_CASE_P( + /**/, + Imgcodecs_JpegXL_MatType, + testing::Combine( + testing::Values( + CV_8UC1, CV_8UC3, CV_8UC4, + CV_16UC1, CV_16UC3, CV_16UC4, + CV_32FC1, CV_32FC3, CV_32FC4 + ), + testing::Values( // Distance + 0, // Lossless + 1, // Default + 3, // Recomended Lossy Max + 25 // Specification Max + ) +) ); + + +typedef tuple Effort_and_Decoding_speed; +typedef testing::TestWithParam Imgcodecs_JpegXL_Effort_DecodingSpeed; + +TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode) +{ + const int effort = get<0>(GetParam()); + const int speed = get<1>(GetParam()); + + cv::Scalar col = cv::Scalar(124,76,42); + // Jpeg XL is lossy compression. + // There may be small differences in decoding results by environments. + double th = 3; // = 255 / 100 (1%); + + bool ret = false; + vector buff; + Mat img_org(320, 480, CV_8UC3, col); + vector param; + param.push_back(IMWRITE_JPEGXL_EFFORT); + param.push_back(effort); + param.push_back(IMWRITE_JPEGXL_DECODING_SPEED); + param.push_back(speed); + EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param)); + EXPECT_TRUE(ret); + Mat img_decoded; + EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED)); + EXPECT_FALSE(img_decoded.empty()); + + EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th); +} + +INSTANTIATE_TEST_CASE_P( + /**/, + Imgcodecs_JpegXL_Effort_DecodingSpeed, + testing::Combine( + testing::Values( // Effort + 1, // fastest + 7, // default + 9 // slowest + ), + testing::Values( // Decoding Speed + 0, // default, slowest, and best quality/density + 2, + 4 // fastest, at the cost of some qulity/density + ) +) ); + +TEST(Imgcodecs_JpegXL, encode_from_uncontinued_image) +{ + cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10)); + cv::Mat roi = src(cv::Rect(10,20,30,50)); + EXPECT_FALSE(roi.isContinuous()); // uncontinued image + + vector buff; + vector param; + bool ret = false; + EXPECT_NO_THROW(ret = cv::imencode(".jxl", roi, buff, param)); + EXPECT_TRUE(ret); +} + +#endif // HAVE_JPEGXL + +} // namespace +} // namespace opencv_test diff --git a/modules/imgcodecs/test/test_read_write.cpp b/modules/imgcodecs/test/test_read_write.cpp index 7dfd02c67c..1e171d9e8d 100644 --- a/modules/imgcodecs/test/test_read_write.cpp +++ b/modules/imgcodecs/test/test_read_write.cpp @@ -157,6 +157,9 @@ const string exts[] = { #ifdef HAVE_JPEG "jpg", #endif +#ifdef HAVE_JPEGXL + "jxl", +#endif #if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \ || defined(HAVE_OPENJPEG) "jp2", @@ -238,6 +241,8 @@ TEST_P(Imgcodecs_Image, read_write_BGR) double psnrThreshold = 100; if (ext == "jpg") psnrThreshold = 32; + if (ext == "jxl") + psnrThreshold = 30; #if defined(HAVE_JASPER) if (ext == "jp2") psnrThreshold = 95; @@ -268,6 +273,8 @@ TEST_P(Imgcodecs_Image, read_write_GRAYSCALE) double psnrThreshold = 100; if (ext == "jpg") psnrThreshold = 40; + if (ext == "jxl") + psnrThreshold = 40; #if defined(HAVE_JASPER) if (ext == "jp2") psnrThreshold = 70; diff --git a/modules/imgcodecs/test/test_webp.cpp b/modules/imgcodecs/test/test_webp.cpp index 3647714039..6414e60469 100644 --- a/modules/imgcodecs/test/test_webp.cpp +++ b/modules/imgcodecs/test/test_webp.cpp @@ -1,29 +1,16 @@ // This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory -// of this distribution and at http://opencv.org/license.html +// of this distribution and at http://opencv.org/license.html. + #include "test_precomp.hpp" namespace opencv_test { namespace { #ifdef HAVE_WEBP -TEST(Imgcodecs_WebP, encode_decode_lossless_webp) +static void readFileBytes(const std::string& fname, std::vector& buf) { - const string root = cvtest::TS::ptr()->get_data_path(); - string filename = root + "../cv/shared/lena.png"; - cv::Mat img = cv::imread(filename); - ASSERT_FALSE(img.empty()); - - string output = cv::tempfile(".webp"); - EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless - - cv::Mat img_webp = cv::imread(output); - - std::vector buf; - - FILE * wfile = NULL; - - wfile = fopen(output.c_str(), "rb"); + FILE * wfile = fopen(fname.c_str(), "rb"); if (wfile != NULL) { fseek(wfile, 0, SEEK_END); @@ -39,12 +26,24 @@ TEST(Imgcodecs_WebP, encode_decode_lossless_webp) fclose(wfile); } - if (data_size != wfile_size) - { - EXPECT_TRUE(false); - } + EXPECT_EQ(data_size, wfile_size); } +} +TEST(Imgcodecs_WebP, encode_decode_lossless_webp) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + string filename = root + "../cv/shared/lena.png"; + cv::Mat img = cv::imread(filename); + ASSERT_FALSE(img.empty()); + + string output = cv::tempfile(".webp"); + EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless + + cv::Mat img_webp = cv::imread(output); + + std::vector buf; + readFileBytes(output, buf); EXPECT_EQ(0, remove(output.c_str())); cv::Mat decode = cv::imdecode(buf, IMREAD_COLOR); diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 50872670e6..7384c5b3e4 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -2195,11 +2195,13 @@ Must fall between 0 and max_theta. @param max_theta For standard and multi-scale Hough transform, an upper bound for the angle. Must fall between min_theta and CV_PI. The actual maximum angle in the accumulator may be slightly less than max_theta, depending on the parameters min_theta and theta. +@param use_edgeval True if you want to use weighted Hough transform. */ CV_EXPORTS_W void HoughLines( InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn = 0, double stn = 0, - double min_theta = 0, double max_theta = CV_PI ); + double min_theta = 0, double max_theta = CV_PI, + bool use_edgeval = false ); /** @brief Finds line segments in a binary image using the probabilistic Hough transform. @@ -4398,7 +4400,7 @@ CV_EXPORTS_W RotatedRect fitEllipseAMS( InputArray points ); The function calculates the ellipse that fits a set of 2D points. It returns the rotated rectangle in which the ellipse is inscribed. - The Direct least square (Direct) method by @cite Fitzgibbon1999 is used. + The Direct least square (Direct) method by @cite oy1998NumericallySD is used. For an ellipse, this basis set is \f$ \chi= \left(x^2, x y, y^2, x, y, 1\right) \f$, which is a set of six free coefficients \f$ A^T=\left\{A_{\text{xx}},A_{\text{xy}},A_{\text{yy}},A_x,A_y,A_0\right\} \f$. diff --git a/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp b/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp index d4b0f3fbb9..1d1db5e3a0 100644 --- a/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp +++ b/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp @@ -3,6 +3,7 @@ #include "opencv2/core/cvdef.h" #include "opencv2/core/cvstd.hpp" +#include "opencv2/core/utility.hpp" #include "opencv2/core/hal/interface.h" namespace cv { namespace hal { @@ -158,12 +159,14 @@ CV_EXPORTS void cvtGraytoBGR5x5(const uchar * src_data, size_t src_step, CV_EXPORTS void cvtBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int depth, int scn, bool swapBlue, bool isCbCr); + int depth, int scn, bool swapBlue, bool isCbCr, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int depth, int dcn, bool swapBlue, bool isCbCr); + int depth, int dcn, bool swapBlue, bool isCbCr, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtBGRtoXYZ(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, @@ -198,28 +201,33 @@ CV_EXPORTS void cvtLabtoBGR(const uchar * src_data, size_t src_step, CV_EXPORTS void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); //! Separate Y and UV planes CV_EXPORTS void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar * uv_data, size_t uv_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int scn, bool swapBlue, int uIdx); + int scn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); //! Separate Y and UV planes CV_EXPORTS void cvtBGRtoTwoPlaneYUV(const uchar * src_data, size_t src_step, @@ -230,12 +238,14 @@ CV_EXPORTS void cvtBGRtoTwoPlaneYUV(const uchar * src_data, size_t src_step, CV_EXPORTS void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int dcn, bool swapBlue, int uIdx, int ycn); + int dcn, bool swapBlue, int uIdx, int ycn, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtOnePlaneBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int scn, bool swapBlue, int uIdx, int ycn); + int scn, bool swapBlue, int uIdx, int ycn, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtRGBAtoMultipliedRGBA(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, diff --git a/modules/imgproc/include/opencv2/imgproc/segmentation.hpp b/modules/imgproc/include/opencv2/imgproc/segmentation.hpp index c40d5011ee..b2df4fcaca 100644 --- a/modules/imgproc/include/opencv2/imgproc/segmentation.hpp +++ b/modules/imgproc/include/opencv2/imgproc/segmentation.hpp @@ -121,7 +121,7 @@ public: * * @param targetPt The target point * @param[out] contour The list of pixels which contains optimal path between the source and the target points of the image. Type is CV_32SC2 (compatible with `std::vector`) - * @param backward Flag to indicate reverse order of retrived pixels (use "true" value to fetch points from the target to the source point) + * @param backward Flag to indicate reverse order of retrieved pixels (use "true" value to fetch points from the target to the source point) */ CV_WRAP void getContour(const Point& targetPt, OutputArray contour, bool backward = false) const; diff --git a/modules/imgproc/misc/js/gen_dict.json b/modules/imgproc/misc/js/gen_dict.json index 7b8c959fdd..a26808e38b 100644 --- a/modules/imgproc/misc/js/gen_dict.json +++ b/modules/imgproc/misc/js/gen_dict.json @@ -2,82 +2,105 @@ "whitelist": { "": [ - "Canny", - "GaussianBlur", - "Laplacian", - "HoughLines", - "HoughLinesP", - "HoughCircles", - "Scharr", - "Sobel", "adaptiveThreshold", + "applyColorMap", "approxPolyDP", + "approxPolyN", "arcLength", + "arrowedLine", "bilateralFilter", + "blendLinear", "blur", "boundingRect", "boxFilter", "calcBackProject", "calcHist", + "Canny", "circle", + "clipLine", "compareHist", "connectedComponents", "connectedComponentsWithStats", "contourArea", + "convertMaps", "convexHull", "convexityDefects", "cornerHarris", "cornerMinEigenVal", "createCLAHE", + "createHanningWindow", "createLineSegmentDetector", "cvtColor", "demosaicing", "dilate", "distanceTransform", "distanceTransformWithLabels", + "divSpectrums", "drawContours", + "drawMarker", "ellipse", "ellipse2Poly", "equalizeHist", "erode", + "fillConvexPoly", + "fillPoly", "filter2D", "findContours", + "findContoursLinkRuns", "fitEllipse", + "fitEllipseAMS", + "fitEllipseDirect", "fitLine", "floodFill", + "GaussianBlur", "getAffineTransform", + "getFontScaleFromHeight", "getPerspectiveTransform", + "getRectSubPix", "getRotationMatrix2D", "getStructuringElement", "goodFeaturesToTrack", "grabCut", + "HoughCircles", + "HoughLines", + "HoughLinesP", + "HuMoments", "integral", "integral2", + "intersectConvexConvex", + "invertAffineTransform", "isContourConvex", + "Laplacian", "line", "matchShapes", "matchTemplate", "medianBlur", "minAreaRect", "minEnclosingCircle", + "minEnclosingTriangle", "moments", "morphologyEx", "pointPolygonTest", + "polylines", + "preCornerDetect", "putText", "pyrDown", "pyrUp", "rectangle", "remap", "resize", + "rotatedRectangleIntersection", + "Scharr", "sepFilter2D", + "Sobel", + "spatialGradient", + "sqrBoxFilter", + "stackBlur", "threshold", "warpAffine", "warpPerspective", "warpPolar", - "watershed", - "fillPoly", - "fillConvexPoly", - "polylines" + "watershed" ], "CLAHE": ["apply", "collectGarbage", "getClipLimit", "getTilesGridSize", "setClipLimit", "setTilesGridSize"], "segmentation_IntelligentScissorsMB": [ diff --git a/modules/imgproc/perf/perf_blur.cpp b/modules/imgproc/perf/perf_blur.cpp index d1f5a6b1ca..a0904156a0 100644 --- a/modules/imgproc/perf/perf_blur.cpp +++ b/modules/imgproc/perf/perf_blur.cpp @@ -45,6 +45,10 @@ typedef perf::TestBaseWithParam Size_MatType_BorderTy typedef tuple Size_ksize_BorderType_t; typedef perf::TestBaseWithParam Size_ksize_BorderType; +typedef tuple Size_MatType_BorderType_ksize_t; +typedef perf::TestBaseWithParam Size_MatType_BorderType_ksize; + + PERF_TEST_P(Size_MatType_BorderType3x3, gaussianBlur3x3, testing::Combine( testing::Values(szODD, szQVGA, szVGA, sz720p), @@ -114,24 +118,27 @@ PERF_TEST_P(Size_MatType_BorderType, blur16x16, SANITY_CHECK(dst, eps); } -PERF_TEST_P(Size_MatType_BorderType3x3, box3x3, +PERF_TEST_P(Size_MatType_BorderType_ksize, box, testing::Combine( testing::Values(szODD, szQVGA, szVGA, sz720p), testing::Values(CV_8UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_32FC3), - BorderType3x3::all() + BorderType::all(), + testing::Values(3, 5) ) ) { - Size size = get<0>(GetParam()); - int type = get<1>(GetParam()); - BorderType3x3 btype = get<2>(GetParam()); + auto p = GetParam(); + Size size = get<0>(p); + int type = get<1>(p); + BorderType btype = get<2>(p); + int ksize = get<3>(p); Mat src(size, type); Mat dst(size, type); declare.in(src, WARMUP_RNG).out(dst); - TEST_CYCLE() boxFilter(src, dst, -1, Size(3,3), Point(-1,-1), false, btype); + TEST_CYCLE() boxFilter(src, dst, -1, Size(ksize, ksize), Point(-1,-1), false, btype); SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE); } @@ -158,17 +165,20 @@ PERF_TEST_P(Size_ksize_BorderType, box_CV8U_CV16U, SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE); } -PERF_TEST_P(Size_MatType_BorderType3x3, box3x3_inplace, +PERF_TEST_P(Size_MatType_BorderType_ksize, box_inplace, testing::Combine( testing::Values(szODD, szQVGA, szVGA, sz720p), testing::Values(CV_8UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_32FC3), - BorderType3x3::all() + BorderType::all(), + testing::Values(3, 5) ) ) { - Size size = get<0>(GetParam()); - int type = get<1>(GetParam()); - BorderType3x3 btype = get<2>(GetParam()); + auto p = GetParam(); + Size size = get<0>(p); + int type = get<1>(p); + BorderType btype = get<2>(p); + int ksize = get<3>(p); Mat src(size, type); Mat dst(size, type); @@ -179,7 +189,7 @@ PERF_TEST_P(Size_MatType_BorderType3x3, box3x3_inplace, { src.copyTo(dst); startTimer(); - boxFilter(dst, dst, -1, Size(3,3), Point(-1,-1), false, btype); + boxFilter(dst, dst, -1, Size(ksize, ksize), Point(-1,-1), false, btype); stopTimer(); } diff --git a/modules/imgproc/src/color_lab.cpp b/modules/imgproc/src/color_lab.cpp index 812480e7fb..eb18f5a17c 100644 --- a/modules/imgproc/src/color_lab.cpp +++ b/modules/imgproc/src/color_lab.cpp @@ -1982,7 +1982,7 @@ struct RGB2Lab_f clipv(bvec0); clipv(bvec1); #undef clipv /* int iR = R*LAB_BASE, iG = G*LAB_BASE, iB = B*LAB_BASE, iL, ia, ib; */ - v_float32 basef = vx_setall_f32(LAB_BASE); + v_float32 basef = vx_setall_f32(static_cast(LAB_BASE)); rvec0 = v_mul(rvec0, basef), gvec0 = v_mul(gvec0, basef), bvec0 = v_mul(bvec0, basef); rvec1 = v_mul(rvec1, basef), gvec1 = v_mul(gvec1, basef), bvec1 = v_mul(bvec1, basef); @@ -2013,14 +2013,14 @@ struct RGB2Lab_f b_vec0 = v_cvt_f32(i_bvec0); b_vec1 = v_cvt_f32(i_bvec1); /* dst[i] = L*100.0f */ - v_float32 v100dBase = vx_setall_f32(100.0f/LAB_BASE); + v_float32 v100dBase = vx_setall_f32(100.0f / static_cast(LAB_BASE)); l_vec0 = v_mul(l_vec0, v100dBase); l_vec1 = v_mul(l_vec1, v100dBase); /* dst[i + 1] = a*256.0f - 128.0f; dst[i + 2] = b*256.0f - 128.0f; */ - v_float32 v256dBase = vx_setall_f32(256.0f/LAB_BASE), vm128 = vx_setall_f32(-128.f); + v_float32 v256dBase = vx_setall_f32(256.0f / static_cast(LAB_BASE)), vm128 = vx_setall_f32(-128.f); a_vec0 = v_fma(a_vec0, v256dBase, vm128); a_vec1 = v_fma(a_vec1, v256dBase, vm128); b_vec0 = v_fma(b_vec0, v256dBase, vm128); @@ -2038,10 +2038,10 @@ struct RGB2Lab_f float G = clip(src[1]); float B = clip(src[bIdx^2]); - int iR = cvRound(R*LAB_BASE), iG = cvRound(G*LAB_BASE), iB = cvRound(B*LAB_BASE); + int iR = cvRound(R*static_cast(LAB_BASE)), iG = cvRound(G*static_cast(LAB_BASE)), iB = cvRound(B*static_cast(LAB_BASE)); int iL, ia, ib; trilinearInterpolate(iR, iG, iB, LABLUVLUTs16.RGB2LabLUT_s16, iL, ia, ib); - float L = iL*1.0f/LAB_BASE, a = ia*1.0f/LAB_BASE, b = ib*1.0f/LAB_BASE; + float L = iL*1.0f/static_cast(LAB_BASE), a = ia*1.0f/static_cast(LAB_BASE), b = ib*1.0f/static_cast(LAB_BASE); dst[i] = L*100.0f; dst[i + 1] = a*256.0f - 128.0f; diff --git a/modules/imgproc/src/color_yuv.dispatch.cpp b/modules/imgproc/src/color_yuv.dispatch.cpp index 89e933affa..2a869d5b4a 100644 --- a/modules/imgproc/src/color_yuv.dispatch.cpp +++ b/modules/imgproc/src/color_yuv.dispatch.cpp @@ -18,7 +18,7 @@ namespace cv { namespace hal { // 8u, 16u, 32f -static void cvtBGRtoYUV(const uchar * src_data, size_t src_step, +void cvtBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int depth, int scn, bool swapBlue, bool isCbCr, AlgorithmHint hint) @@ -71,7 +71,7 @@ static void cvtBGRtoYUV(const uchar * src_data, size_t src_step, CV_CPU_DISPATCH_MODES_ALL); } -static void cvtYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int depth, int dcn, bool swapBlue, bool isCbCr, AlgorithmHint hint) @@ -128,7 +128,7 @@ static void cvtYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:0, two planes: Y, UV interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar * uv_data, size_t uv_step, +void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar * uv_data, size_t uv_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -151,7 +151,7 @@ static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar // 4:2:0, two planes in one array: Y, UV interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -173,7 +173,7 @@ static void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:0, two planes: Y, UV interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, size_t src_step, +void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -186,7 +186,7 @@ static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, siz // 4:2:0, three planes in one array: Y, U, V // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -207,7 +207,7 @@ static void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:0, three planes in one array: Y, U, V // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, +void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int scn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -225,10 +225,27 @@ static void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, CV_CPU_DISPATCH_MODES_ALL); } +// 4:2:0, two planes: Y, UV interleaved +// Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 +// 20-bit fixed-point arithmetics +void cvtBGRtoTwoPlaneYUV(const uchar * src_data, size_t src_step, + uchar * y_data, uchar * uv_data, size_t dst_step, + int width, int height, + int scn, bool swapBlue, int uIdx) +{ + CV_INSTRUMENT_REGION(); + + CALL_HAL(cvtBGRtoTwoPlaneYUV, cv_hal_cvtBGRtoTwoPlaneYUV, + src_data, src_step, y_data, dst_step, uv_data, dst_step, width, height, scn, swapBlue, uIdx); + + CV_CPU_DISPATCH(cvtBGRtoTwoPlaneYUV, (src_data, src_step, y_data, uv_data, dst_step, width, height, scn, swapBlue, uIdx), + CV_CPU_DISPATCH_MODES_ALL); +} + // 4:2:2 interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int dcn, bool swapBlue, int uIdx, int ycn, AlgorithmHint hint) @@ -249,7 +266,7 @@ static void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:2 interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 14-bit fixed-point arithmetics is used -static void cvtOnePlaneBGRtoYUV(const uchar * src_data, size_t src_step, +void cvtOnePlaneBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int scn, bool swapBlue, int uIdx, int ycn, AlgorithmHint hint) diff --git a/modules/imgproc/src/contours_new.cpp b/modules/imgproc/src/contours_new.cpp index ca5a0a5cda..47dfc585b1 100644 --- a/modules/imgproc/src/contours_new.cpp +++ b/modules/imgproc/src/contours_new.cpp @@ -368,10 +368,6 @@ CNode& ContourScanner_::makeContour(schar& nbd_, const bool is_hole, const int x const Point start_pt(x - (is_hole ? 1 : 0), y); CNode& res = tree.newElem(); - if (isChain) - res.body.codes.reserve(200); - else - res.body.pts.reserve(200); res.body.isHole = is_hole; res.body.isChain = isChain; res.body.origin = start_pt + offset; diff --git a/modules/imgproc/src/drawing.cpp b/modules/imgproc/src/drawing.cpp index ea10da3bb6..5e2906a171 100644 --- a/modules/imgproc/src/drawing.cpp +++ b/modules/imgproc/src/drawing.cpp @@ -1027,8 +1027,8 @@ EllipseEx( Mat& img, Point2l center, Size2l axes, for (unsigned int i = 0; i < _v.size(); ++i) { Point2l pt; - pt.x = (int64)cvRound(_v[i].x / XY_ONE) << XY_SHIFT; - pt.y = (int64)cvRound(_v[i].y / XY_ONE) << XY_SHIFT; + pt.x = (int64)cvRound(_v[i].x / static_cast(XY_ONE)) << XY_SHIFT; + pt.y = (int64)cvRound(_v[i].y / static_cast(XY_ONE)) << XY_SHIFT; pt.x += cvRound(_v[i].x - pt.x); pt.y += cvRound(_v[i].y - pt.y); if (pt != prevPt) { @@ -1645,7 +1645,7 @@ static void ThickLine( Mat& img, Point2l p0, Point2l p1, const void* color, int thickness, int line_type, int flags, int shift ) { - static const double INV_XY_ONE = 1./XY_ONE; + static const double INV_XY_ONE = 1./static_cast(XY_ONE); p0.x <<= XY_SHIFT - shift; p0.y <<= XY_SHIFT - shift; @@ -1981,8 +1981,8 @@ void ellipse(InputOutputArray _img, const RotatedRect& box, const Scalar& color, int _angle = cvRound(box.angle); Point2l center(cvRound(box.center.x), cvRound(box.center.y)); - center.x = (center.x << XY_SHIFT) + cvRound((box.center.x - center.x)*XY_ONE); - center.y = (center.y << XY_SHIFT) + cvRound((box.center.y - center.y)*XY_ONE); + center.x = (center.x << XY_SHIFT) + cvRound((box.center.x - center.x)*static_cast(XY_ONE)); + center.y = (center.y << XY_SHIFT) + cvRound((box.center.y - center.y)*static_cast(XY_ONE)); Size2l axes(cvRound(box.size.width), cvRound(box.size.height)); axes.width = (axes.width << (XY_SHIFT - 1)) + cvRound((box.size.width - axes.width)*(XY_ONE>>1)); @@ -2153,21 +2153,70 @@ void cv::drawContours( InputOutputArray _image, InputArrayOfArrays _contours, CV_Assert(ncontours <= (size_t)std::numeric_limits::max()); if (lineType == cv::LINE_AA && _image.depth() != CV_8U) lineType = 8; - Mat image = _image.getMat(), hierarchy = _hierarchy.getMat(); + Mat image = _image.getMat(); + Mat_ hierarchy = _hierarchy.getMat(); - if (thickness >= 0) // contour lines + int i = 0, end = (int)ncontours; + if (contourIdx >= 0) { - double color_buf[4] {}; - scalarToRawData(color, color_buf, _image.type(), 0 ); - int i = 0, end = (int)ncontours; - if (contourIdx >= 0) + i = contourIdx; + end = i + 1; + } + std::vector indexesToFill; + if (hierarchy.empty() || maxLevel == 0) + { + indexesToFill.resize(end - i); + std::iota(indexesToFill.begin(), indexesToFill.end(), i); + } + else + { + std::stack indexes; + for (; i != end; ++i) { - i = contourIdx; - end = i + 1; + // either all from the top level or a single contour + if (hierarchy(i)[3] < 0 || contourIdx >= 0) + indexes.push(i); } - for (; i < end; ++i) + while (!indexes.empty()) + { + // get current element + const int cur = indexes.top(); + indexes.pop(); + + // check current element depth + int curLevel = -1; + int par = cur; + while (par >= 0) + { + par = hierarchy(par)[3]; // parent + ++curLevel; + } + if (curLevel <= maxLevel) + { + indexesToFill.push_back(cur); + } + + int next = hierarchy(cur)[2]; // first child + while (next > 0) + { + indexes.push(next); + next = hierarchy(next)[0]; // next sibling + } + } + } + std::vector contoursToFill; + contoursToFill.reserve(indexesToFill.size()); + for (const int& idx : indexesToFill) + contoursToFill.emplace_back(_contours.getMat(idx)); + + if (thickness < 0) + fillPoly(image, contoursToFill, color, lineType, 0, offset); + else + { + double color_buf[4]{}; + scalarToRawData(color, color_buf, _image.type(), 0); + for (const Mat& cnt : contoursToFill) { - Mat cnt = _contours.getMat(i); if (cnt.empty()) continue; const int npoints = cnt.checkVector(2, CV_32S); @@ -2181,59 +2230,4 @@ void cv::drawContours( InputOutputArray _image, InputArrayOfArrays _contours, } } } - else // filled polygons - { - int i = 0, end = (int)ncontours; - if (contourIdx >= 0) - { - i = contourIdx; - end = i + 1; - } - std::vector indexesToFill; - if (hierarchy.empty() || maxLevel == 0) - { - for (; i != end; ++i) - indexesToFill.push_back(i); - } - else - { - std::stack indexes; - for (; i != end; ++i) - { - // either all from the top level or a single contour - if (hierarchy.at(i)[3] < 0 || contourIdx >= 0) - indexes.push(i); - } - while (!indexes.empty()) - { - // get current element - const int cur = indexes.top(); - indexes.pop(); - - // check current element depth - int curLevel = -1; - int par = cur; - while (par >= 0) - { - par = hierarchy.at(par)[3]; // parent - ++curLevel; - } - if (curLevel <= maxLevel) - { - indexesToFill.push_back(cur); - } - - int next = hierarchy.at(cur)[2]; // first child - while (next > 0) - { - indexes.push(next); - next = hierarchy.at(next)[0]; // next sibling - } - } - } - std::vector contoursToFill; - for (const int & idx : indexesToFill) - contoursToFill.push_back(_contours.getMat(idx)); - fillPoly(image, contoursToFill, color, lineType, 0, offset); - } } diff --git a/modules/imgproc/src/generalized_hough.cpp b/modules/imgproc/src/generalized_hough.cpp index 5ff9f933ed..6a2ca5f032 100644 --- a/modules/imgproc/src/generalized_hough.cpp +++ b/modules/imgproc/src/generalized_hough.cpp @@ -692,7 +692,11 @@ namespace getContourPoints(edges, dx, dy, points); features.resize(levels_ + 1); - std::for_each(features.begin(), features.end(), [=](std::vector& e) { e.clear(); e.reserve(maxBufferSize_); }); + const size_t maxBufferSize = maxBufferSize_; + std::for_each(features.begin(), features.end(), [maxBufferSize](std::vector& e) { + e.clear(); + e.reserve(maxBufferSize); + }); for (size_t i = 0; i < points.size(); ++i) { diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 2970456cab..6410a1e87e 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -1216,7 +1216,7 @@ void cv::calcHist( InputArrayOfArrays images, const std::vector& channels, CV_OCL_RUN(images.total() == 1 && channels.size() == 1 && images.channels(0) == 1 && channels[0] == 0 && images.isUMatVector() && mask.empty() && !accumulate && histSize.size() == 1 && histSize[0] == BINS && ranges.size() == 2 && - ranges[0] == 0 && ranges[1] == BINS, + ranges[0] == 0 && ranges[1] == static_cast(BINS), ocl_calcHist(images, hist)) int i, dims = (int)histSize.size(), rsz = (int)ranges.size(), csz = (int)channels.size(); diff --git a/modules/imgproc/src/hough.cpp b/modules/imgproc/src/hough.cpp index 8b2269253f..f82ecec504 100644 --- a/modules/imgproc/src/hough.cpp +++ b/modules/imgproc/src/hough.cpp @@ -120,7 +120,7 @@ static void HoughLinesStandard( InputArray src, OutputArray lines, int type, float rho, float theta, int threshold, int linesMax, - double min_theta, double max_theta ) + double min_theta, double max_theta, bool use_edgeval = false ) { CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error"); @@ -184,17 +184,31 @@ HoughLinesStandard( InputArray src, OutputArray lines, int type, irho, tabSin, tabCos); // stage 1. fill accumulator - for( i = 0; i < height; i++ ) - for( j = 0; j < width; j++ ) - { - if( image[i * step + j] != 0 ) - for(int n = 0; n < numangle; n++ ) - { - int r = cvRound( j * tabCos[n] + i * tabSin[n] ); - r += (numrho - 1) / 2; - accum[(n+1) * (numrho+2) + r+1]++; - } - } + if (use_edgeval) { + for( i = 0; i < height; i++ ) + for( j = 0; j < width; j++ ) + { + if( image[i * step + j] != 0 ) + for(int n = 0; n < numangle; n++ ) + { + int r = cvRound( j * tabCos[n] + i * tabSin[n] ); + r += (numrho - 1) / 2; + accum[(n + 1) * (numrho + 2) + r + 1] += image[i * step + j]; + } + } + } else { + for( i = 0; i < height; i++ ) + for( j = 0; j < width; j++ ) + { + if( image[i * step + j] != 0 ) + for(int n = 0; n < numangle; n++ ) + { + int r = cvRound( j * tabCos[n] + i * tabSin[n] ); + r += (numrho - 1) / 2; + accum[(n + 1) * (numrho + 2) + r + 1]++; + } + } + } // stage 2. find local maximums findLocalMaximums( numrho, numangle, threshold, accum, _sort_buf ); @@ -561,7 +575,8 @@ HoughLinesProbabilistic( Mat& image, Point line_end[2]; float a, b; int* adata = accum.ptr(); - int i = point.y, j = point.x, k, x0, y0, dx0, dy0, xflag; + int i = point.y, j = point.x, k, xflag; + int64_t x0, y0, dx0, dy0; int good_line; const int shift = 16; @@ -612,8 +627,8 @@ HoughLinesProbabilistic( Mat& image, for( k = 0; k < 2; k++ ) { - int gap = 0, x = x0, y = y0, dx = dx0, dy = dy0; - + int gap = 0; + int64_t x = x0, y = y0, dx = dx0, dy = dy0 ; if( k > 0 ) dx = -dx, dy = -dy; @@ -622,7 +637,7 @@ HoughLinesProbabilistic( Mat& image, for( ;; x += dx, y += dy ) { uchar* mdata; - int i1, j1; + int64_t i1, j1; if( xflag ) { @@ -647,8 +662,8 @@ HoughLinesProbabilistic( Mat& image, if( *mdata ) { gap = 0; - line_end[k].y = i1; - line_end[k].x = j1; + line_end[k].y = static_cast(i1); + line_end[k].x = static_cast(j1); } else if( ++gap > lineGap ) break; @@ -660,7 +675,7 @@ HoughLinesProbabilistic( Mat& image, for( k = 0; k < 2; k++ ) { - int x = x0, y = y0, dx = dx0, dy = dy0; + int64_t x = x0, y = y0, dx = dx0, dy = dy0; if( k > 0 ) dx = -dx, dy = -dy; @@ -670,7 +685,7 @@ HoughLinesProbabilistic( Mat& image, for( ;; x += dx, y += dy ) { uchar* mdata; - int i1, j1; + int64_t i1, j1; if( xflag ) { @@ -907,7 +922,7 @@ static bool ocl_HoughLinesP(InputArray _src, OutputArray _lines, double rho, dou void HoughLines( InputArray _image, OutputArray lines, double rho, double theta, int threshold, - double srn, double stn, double min_theta, double max_theta ) + double srn, double stn, double min_theta, double max_theta, bool use_edgeval ) { CV_INSTRUMENT_REGION(); @@ -922,7 +937,7 @@ void HoughLines( InputArray _image, OutputArray lines, ocl_HoughLines(_image, lines, rho, theta, threshold, min_theta, max_theta)); if( srn == 0 && stn == 0 ) - HoughLinesStandard(_image, lines, type, (float)rho, (float)theta, threshold, INT_MAX, min_theta, max_theta ); + HoughLinesStandard(_image, lines, type, (float)rho, (float)theta, threshold, INT_MAX, min_theta, max_theta, use_edgeval ); else HoughLinesSDiv(_image, lines, type, (float)rho, (float)theta, threshold, cvRound(srn), cvRound(stn), INT_MAX, min_theta, max_theta); } diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 6ef04ed5b6..1162b194c4 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -1276,8 +1276,8 @@ public: #endif for( ; x1 < bcols; x1++ ) { - int sx = cvRound(sX[x1]*INTER_TAB_SIZE); - int sy = cvRound(sY[x1]*INTER_TAB_SIZE); + int sx = cvRound(sX[x1]*static_cast(INTER_TAB_SIZE)); + int sy = cvRound(sY[x1]*static_cast(INTER_TAB_SIZE)); int v = (sy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE-1)); XY[x1*2] = saturate_cast(sx >> INTER_BITS); XY[x1*2+1] = saturate_cast(sy >> INTER_BITS); @@ -1316,8 +1316,8 @@ public: for( ; x1 < bcols; x1++ ) { - int sx = cvRound(sXY[x1*2]*INTER_TAB_SIZE); - int sy = cvRound(sXY[x1*2+1]*INTER_TAB_SIZE); + int sx = cvRound(sXY[x1*2]*static_cast(INTER_TAB_SIZE)); + int sy = cvRound(sXY[x1*2+1]*static_cast(INTER_TAB_SIZE)); int v = (sy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE-1)); XY[x1*2] = saturate_cast(sx >> INTER_BITS); XY[x1*2+1] = saturate_cast(sy >> INTER_BITS); @@ -2016,7 +2016,7 @@ void cv::convertMaps( InputArray _map1, InputArray _map2, bool useSSE4_1 = CV_CPU_HAS_SUPPORT_SSE4_1; #endif - const float scale = 1.f/INTER_TAB_SIZE; + const float scale = 1.f/static_cast(INTER_TAB_SIZE); int x, y; for( y = 0; y < size.height; y++ ) { @@ -2096,8 +2096,8 @@ void cv::convertMaps( InputArray _map1, InputArray _map2, #endif for( ; x < size.width; x++ ) { - int ix = saturate_cast(src1f[x]*INTER_TAB_SIZE); - int iy = saturate_cast(src2f[x]*INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x]*static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src2f[x]*static_cast(INTER_TAB_SIZE)); dst1[x*2] = saturate_cast(ix >> INTER_BITS); dst1[x*2+1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE-1))); @@ -2142,8 +2142,8 @@ void cv::convertMaps( InputArray _map1, InputArray _map2, #endif for( ; x < size.width; x++ ) { - int ix = saturate_cast(src1f[x*2]*INTER_TAB_SIZE); - int iy = saturate_cast(src1f[x*2+1]*INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x*2]*static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src1f[x*2+1]*static_cast(INTER_TAB_SIZE)); dst1[x*2] = saturate_cast(ix >> INTER_BITS); dst1[x*2+1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE-1))); @@ -3270,7 +3270,7 @@ void WarpPerspectiveLine_Process_CV_SIMD(const double *M, short* xy, short* alph for( ; x1 < bw; x1++ ) { double W = W0 + M[6]*x1; - W = W ? INTER_TAB_SIZE/W : 0; + W = W ? static_cast(INTER_TAB_SIZE)/W : 0; double fX = std::max((double)INT_MIN, std::min((double)INT_MAX, (X0 + M[0]*x1)*W)); double fY = std::max((double)INT_MIN, std::min((double)INT_MAX, (Y0 + M[3]*x1)*W)); int X = saturate_cast(fX); diff --git a/modules/imgproc/src/imgwarp.sse4_1.cpp b/modules/imgproc/src/imgwarp.sse4_1.cpp index 8fffd2d276..903acfdb7b 100644 --- a/modules/imgproc/src/imgwarp.sse4_1.cpp +++ b/modules/imgproc/src/imgwarp.sse4_1.cpp @@ -157,8 +157,8 @@ void convertMaps_32f1c16s_SSE41(const float* src1f, const float* src2f, short* d } for (; x < width; x++) { - int ix = saturate_cast(src1f[x] * INTER_TAB_SIZE); - int iy = saturate_cast(src2f[x] * INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x] * static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src2f[x] * static_cast(INTER_TAB_SIZE)); dst1[x * 2] = saturate_cast(ix >> INTER_BITS); dst1[x * 2 + 1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE - 1))); @@ -190,8 +190,8 @@ void convertMaps_32f2c16s_SSE41(const float* src1f, short* dst1, ushort* dst2, i } for (; x < width; x++) { - int ix = saturate_cast(src1f[x * 2] * INTER_TAB_SIZE); - int iy = saturate_cast(src1f[x * 2 + 1] * INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x * 2] * static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src1f[x * 2 + 1] * static_cast(INTER_TAB_SIZE)); dst1[x * 2] = saturate_cast(ix >> INTER_BITS); dst1[x * 2 + 1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE - 1))); @@ -469,7 +469,7 @@ public: for (; x1 < bw; x1++) { double W = W0 + M[6] * x1; - W = W ? INTER_TAB_SIZE / W : 0; + W = W ? static_cast(INTER_TAB_SIZE) / W : 0; double fX = std::max((double)INT_MIN, std::min((double)INT_MAX, (X0 + M[0] * x1)*W)); double fY = std::max((double)INT_MIN, std::min((double)INT_MAX, (Y0 + M[3] * x1)*W)); int X = saturate_cast(fX); diff --git a/modules/imgproc/src/precomp.hpp b/modules/imgproc/src/precomp.hpp index 6c3c41e3bd..18aa57905f 100644 --- a/modules/imgproc/src/precomp.hpp +++ b/modules/imgproc/src/precomp.hpp @@ -61,6 +61,7 @@ #include #include #include +#include #define GET_OPTIMIZED(func) (func) diff --git a/modules/imgproc/src/smooth.simd.hpp b/modules/imgproc/src/smooth.simd.hpp index 7389cdbce9..dd9d40e1b3 100644 --- a/modules/imgproc/src/smooth.simd.hpp +++ b/modules/imgproc/src/smooth.simd.hpp @@ -1324,7 +1324,9 @@ void vlineSmooth3N(const ufixedpoint16* const * src, con ufixedpoint32 val[] = { (m[0] + m[1] + m[2]) * ufixedpoint16((uint8_t)128) }; v_128_4 = vx_setall_s32(*((int32_t*)val)); } - v_int16 v_mul01 = v_reinterpret_as_s16(vx_setall_u32(*((uint32_t*)m))); + uint32_t val01; + std::memcpy(&val01, m, sizeof(val01)); + v_int16 v_mul01 = v_reinterpret_as_s16(vx_setall_u32(val01)); v_int16 v_mul2 = v_reinterpret_as_s16(vx_setall_u16(*((uint16_t*)(m + 2)))); for (; i <= len - 4*VECSZ; i += 4*VECSZ) { diff --git a/modules/imgproc/src/templmatch.cpp b/modules/imgproc/src/templmatch.cpp index 7b394c23ec..40be708a43 100644 --- a/modules/imgproc/src/templmatch.cpp +++ b/modules/imgproc/src/templmatch.cpp @@ -1056,7 +1056,7 @@ static bool ipp_crossCorr(const Mat& src, const Mat& tpl, Mat& dst, bool normed) if (ippiCrossCorrNorm==0) return false; - IppEnum funCfg = (IppEnum)(ippAlgAuto | ippiROIValid); + IppEnum funCfg = (IppEnum)(+ippAlgAuto | ippiROIValid); if(normed) funCfg |= ippiNorm; else @@ -1093,7 +1093,7 @@ static bool ipp_sqrDistance(const Mat& src, const Mat& tpl, Mat& dst) if (ippiSqrDistanceNorm==0) return false; - IppEnum funCfg = (IppEnum)(ippAlgAuto | ippiROIValid | ippiNormNone); + IppEnum funCfg = (IppEnum)(+ippAlgAuto | ippiROIValid | ippiNormNone); status = ippiSqrDistanceNormGetBufferSize(srcRoiSize, tplRoiSize, funCfg, &bufSize); if ( status < 0 ) return false; diff --git a/modules/imgproc/test/test_contours.cpp b/modules/imgproc/test/test_contours.cpp index d2ff5d6a33..2bcd642a17 100644 --- a/modules/imgproc/test/test_contours.cpp +++ b/modules/imgproc/test/test_contours.cpp @@ -75,9 +75,9 @@ static void d2xy(int n, int d, int *x, int *y) } } -TEST(Imgproc_FindContours, hilbert) +static Mat draw_hilbert(int n = 64, int scale = 10) { - int n = 64, n2 = n*n, scale = 10, w = (n + 2)*scale; + int n2 = n*n, w = (n + 2)*scale; Point ofs(scale, scale); Mat img(w, w, CV_8U); img.setTo(Scalar::all(0)); @@ -91,12 +91,19 @@ TEST(Imgproc_FindContours, hilbert) p = q; } dilate(img, img, Mat()); + return img; +} + +TEST(Imgproc_FindContours, hilbert) +{ + Mat img = draw_hilbert(); vector > contours; + + findContours(img, contours, noArray(), RETR_LIST, CHAIN_APPROX_NONE); + ASSERT_EQ(1, (int)contours.size()); + ASSERT_EQ(78632, (int)contours[0].size()); + findContours(img, contours, noArray(), RETR_LIST, CHAIN_APPROX_SIMPLE); - img.setTo(Scalar::all(0)); - - drawContours(img, contours, 0, Scalar::all(255), 1); - ASSERT_EQ(1, (int)contours.size()); ASSERT_EQ(9832, (int)contours[0].size()); } @@ -168,6 +175,38 @@ TEST(Imgproc_FindContours, regression_4363_shared_nbd) } } +TEST(Imgproc_DrawContours, regression_26264) +{ + Mat img = draw_hilbert(32); + img.push_back(~img); + + for (int i = 50; i < 200; i += 17) + { + rectangle(img, Rect(i, i, img.cols - (i*2), img.rows - (i*2)), Scalar(0), 7); + rectangle(img, Rect(i, i, img.cols - (i*2), img.rows - (i*2)), Scalar(255), 1); + } + + vector > contours; + vector hierarchy; + findContours(img, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); + img.setTo(Scalar::all(0)); + Mat img1 = img.clone(); + Mat img2 = img.clone(); + Mat img3 = img.clone(); + + int idx = 0; + while (idx >= 0) + { + drawContours(img, contours, idx, Scalar::all(255), FILLED, LINE_8, hierarchy); + drawContours(img2, contours, idx, Scalar::all(255), 1, LINE_8, hierarchy); + idx = hierarchy[idx][0]; + } + + drawContours(img1, contours, -1, Scalar::all(255), FILLED, LINE_8, hierarchy); + drawContours(img3, contours, -1, Scalar::all(255), 1, LINE_8, hierarchy); + ASSERT_EQ(0, cvtest::norm(img, img1, NORM_INF)); + ASSERT_EQ(0, cvtest::norm(img2, img3, NORM_INF)); +} TEST(Imgproc_PointPolygonTest, regression_10222) { diff --git a/modules/imgproc/test/test_filter.cpp b/modules/imgproc/test/test_filter.cpp index 948e21e8bf..91b0b096aa 100644 --- a/modules/imgproc/test/test_filter.cpp +++ b/modules/imgproc/test/test_filter.cpp @@ -590,107 +590,65 @@ TEST(Imgproc_Laplace, accuracy) { CV_LaplaceTest test; test.safe_run(); } TEST(Imgproc_PreCornerDetect, accuracy) { CV_PreCornerDetectTest test; test.safe_run(); } ////////////////////////////////////////////////////////////////////////////////// +typedef std::pair Imgproc_DepthAndDepth; +typedef testing::TestWithParam< Imgproc_DepthAndDepth> Imgproc_FilterSupportedFormats; -class CV_FilterSupportedFormatsTest : public cvtest::BaseTest +TEST_P(Imgproc_FilterSupportedFormats, normal) { -public: - CV_FilterSupportedFormatsTest() {} - ~CV_FilterSupportedFormatsTest() {} -protected: - void run(int) - { - const int depths[][2] = - { - {CV_8U, CV_8U}, - {CV_8U, CV_16U}, - {CV_8U, CV_16S}, - {CV_8U, CV_32F}, - {CV_8U, CV_64F}, - {CV_16U, CV_16U}, - {CV_16U, CV_32F}, - {CV_16U, CV_64F}, - {CV_16S, CV_16S}, - {CV_16S, CV_32F}, - {CV_16S, CV_64F}, - {CV_32F, CV_32F}, - {CV_64F, CV_64F}, - {-1, -1} - }; + // use some "odd" size to do yet another smoke + // testing of the non-SIMD loop tails + Size sz(163, 117); + Mat small_kernel(5, 5, CV_32F), big_kernel(21, 21, CV_32F); + Mat kernelX(11, 1, CV_32F), kernelY(7, 1, CV_32F); + Mat symkernelX(11, 1, CV_32F), symkernelY(7, 1, CV_32F); + randu(small_kernel, -10, 10); + randu(big_kernel, -1, 1); + randu(kernelX, -1, 1); + randu(kernelY, -1, 1); + flip(kernelX, symkernelX, 0); + symkernelX += kernelX; + flip(kernelY, symkernelY, 0); + symkernelY += kernelY; - int i = 0; - volatile int fidx = -1; - try - { - // use some "odd" size to do yet another smoke - // testing of the non-SIMD loop tails - Size sz(163, 117); - Mat small_kernel(5, 5, CV_32F), big_kernel(21, 21, CV_32F); - Mat kernelX(11, 1, CV_32F), kernelY(7, 1, CV_32F); - Mat symkernelX(11, 1, CV_32F), symkernelY(7, 1, CV_32F); - randu(small_kernel, -10, 10); - randu(big_kernel, -1, 1); - randu(kernelX, -1, 1); - randu(kernelY, -1, 1); - flip(kernelX, symkernelX, 0); - symkernelX += kernelX; - flip(kernelY, symkernelY, 0); - symkernelY += kernelY; + Mat elem_ellipse = getStructuringElement(MORPH_ELLIPSE, Size(7, 7)); + Mat elem_rect = getStructuringElement(MORPH_RECT, Size(7, 7)); - Mat elem_ellipse = getStructuringElement(MORPH_ELLIPSE, Size(7, 7)); - Mat elem_rect = getStructuringElement(MORPH_RECT, Size(7, 7)); + int sdepth = std::get<0>(GetParam()); + int ddepth = std::get<1>(GetParam()); + Mat src(sz, CV_MAKETYPE(sdepth, 5)), dst; + randu(src, 0, 100); + // non-separable filtering with a small kernel + EXPECT_NO_THROW(cv::filter2D(src, dst, ddepth, small_kernel)); + EXPECT_NO_THROW(cv::filter2D(src, dst, ddepth, big_kernel)); + EXPECT_NO_THROW(cv::sepFilter2D(src, dst, ddepth, kernelX, kernelY)); + EXPECT_NO_THROW(cv::sepFilter2D(src, dst, ddepth, symkernelX, symkernelY)); + EXPECT_NO_THROW(cv::Sobel(src, dst, ddepth, 2, 0, 5)); + EXPECT_NO_THROW(cv::Scharr(src, dst, ddepth, 0, 1)); + if( sdepth != ddepth ) + return; + EXPECT_NO_THROW(cv::GaussianBlur(src, dst, Size(5, 5), 1.2, 1.2)); + EXPECT_NO_THROW(cv::blur(src, dst, Size(11, 11))); + EXPECT_NO_THROW(cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_ellipse)); + EXPECT_NO_THROW(cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_rect)); +} - for( i = 0; depths[i][0] >= 0; i++ ) - { - int sdepth = depths[i][0]; - int ddepth = depths[i][1]; - Mat src(sz, CV_MAKETYPE(sdepth, 5)), dst; - randu(src, 0, 100); - // non-separable filtering with a small kernel - fidx = 0; - cv::filter2D(src, dst, ddepth, small_kernel); - fidx++; - cv::filter2D(src, dst, ddepth, big_kernel); - fidx++; - cv::sepFilter2D(src, dst, ddepth, kernelX, kernelY); - fidx++; - cv::sepFilter2D(src, dst, ddepth, symkernelX, symkernelY); - fidx++; - cv::Sobel(src, dst, ddepth, 2, 0, 5); - fidx++; - cv::Scharr(src, dst, ddepth, 0, 1); - if( sdepth != ddepth ) - continue; - fidx++; - cv::GaussianBlur(src, dst, Size(5, 5), 1.2, 1.2); - fidx++; - cv::blur(src, dst, Size(11, 11)); - fidx++; - cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_ellipse); - fidx++; - cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_rect); - } - } - catch(...) - { - ts->printf(cvtest::TS::LOG, "Combination of depths %d => %d in %s is not supported (yet it should be)", - depths[i][0], depths[i][1], - fidx == 0 ? "filter2D (small kernel)" : - fidx == 1 ? "filter2D (large kernel)" : - fidx == 2 ? "sepFilter2D" : - fidx == 3 ? "sepFilter2D (symmetrical/asymmetrical kernel)" : - fidx == 4 ? "Sobel" : - fidx == 5 ? "Scharr" : - fidx == 6 ? "GaussianBlur" : - fidx == 7 ? "blur" : - fidx == 8 || fidx == 9 ? "morphologyEx" : - "unknown???"); - - ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); - } - } -}; - -TEST(Imgproc_Filtering, supportedFormats) { CV_FilterSupportedFormatsTest test; test.safe_run(); } +INSTANTIATE_TEST_CASE_P(/**/, Imgproc_FilterSupportedFormats, + testing::Values( + make_pair( CV_8U, CV_8U ), + make_pair( CV_8U, CV_16U ), + make_pair( CV_8U, CV_16S ), + make_pair( CV_8U, CV_32F), + make_pair( CV_8U, CV_64F), + make_pair( CV_16U, CV_16U), + make_pair( CV_16U, CV_32F), + make_pair( CV_16U, CV_64F), + make_pair( CV_16S, CV_16S), + make_pair( CV_16S, CV_32F), + make_pair( CV_16S, CV_64F), + make_pair( CV_32F, CV_32F), + make_pair( CV_64F, CV_64F) + ) +); TEST(Imgproc_Blur, borderTypes) { diff --git a/modules/imgproc/test/test_fitellipse.cpp b/modules/imgproc/test/test_fitellipse.cpp index b564db96da..3e6d0478ca 100644 --- a/modules/imgproc/test/test_fitellipse.cpp +++ b/modules/imgproc/test/test_fitellipse.cpp @@ -94,7 +94,7 @@ TEST(Imgproc_FitEllipse_JavaCase, accuracy) { pts.push_back(Point2f(-1, -1)*scale+shift); pts.push_back(Point2f(1, -1)*scale+shift); - // check that we get almost vertical ellipse centered around (1, 3) + // check that we get almost circle centered around (0, 0) RotatedRect e = fitEllipse(pts); EXPECT_NEAR(e.center.x, 0, 0.01); EXPECT_NEAR(e.center.y, 0, 0.01); diff --git a/modules/imgproc/test/test_fitellipse_ams.cpp b/modules/imgproc/test/test_fitellipse_ams.cpp index caa5df003d..92e7f2e7a2 100644 --- a/modules/imgproc/test/test_fitellipse_ams.cpp +++ b/modules/imgproc/test/test_fitellipse_ams.cpp @@ -8,6 +8,25 @@ namespace opencv_test { namespace { +static bool checkEllipse(const RotatedRect& ellipseAMSTest, const RotatedRect& ellipseAMSTrue, const float tol) { + Point2f ellipseAMSTrueVertices[4]; + Point2f ellipseAMSTestVertices[4]; + ellipseAMSTest.points(ellipseAMSTestVertices); + ellipseAMSTrue.points(ellipseAMSTrueVertices); + float AMSDiff = 0.0f; + for (size_t i=0; i <=3; i++) { + Point2f diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[0]; + float d = diff.x * diff.x + diff.y * diff.y; + for (size_t j=1; j <=3; j++) { + diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[j]; + float dd = diff.x * diff.x + diff.y * diff.y; + if(ddpts; pts.push_back(Point2f(173.41854895999165f, 125.84473135880411f)); @@ -51,29 +70,12 @@ TEST(Imgproc_FitEllipseAMS_Issue_1, accuracy) { pts.push_back(Point2f(6.719616410428614f, 50.15263031354927f)); pts.push_back(Point2f(5.122267598477748f, 46.03603214691343f)); - bool AMSGoodQ; float tol = 0.01f; RotatedRect ellipseAMSTrue = cv::RotatedRect(Point2f(94.4037f, 84.743f), Size2f(190.614f, 153.543f), 19.832f); RotatedRect ellipseAMSTest = fitEllipseAMS(pts); - Point2f ellipseAMSTrueVertices[4]; - Point2f ellipseAMSTestVertices[4]; - ellipseAMSTest.points(ellipseAMSTestVertices); - ellipseAMSTrue.points(ellipseAMSTrueVertices); - float AMSDiff = 0.0f; - for (size_t i=0; i <=3; i++) { - Point2f diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[0]; - float d = diff.x * diff.x + diff.y * diff.y; - for (size_t j=1; i <=3; i++) { - diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[j]; - float dd = diff.x * diff.x + diff.y * diff.y; - if(ddpts; pts.push_back(Point2f(173.41854895999165f, 125.84473135880411f)); @@ -51,29 +70,12 @@ TEST(Imgproc_FitEllipseDirect_Issue_1, accuracy) { pts.push_back(Point2f(6.719616410428614f, 50.15263031354927f)); pts.push_back(Point2f(5.122267598477748f, 46.03603214691343f)); - bool directGoodQ; float tol = 0.01f; RotatedRect ellipseDirectTrue = cv::RotatedRect(Point2f(91.3256f, 90.4668f),Size2f(187.211f, 140.031f), 21.5808f); RotatedRect ellipseDirectTest = fitEllipseDirect(pts); - Point2f ellipseDirectTrueVertices[4]; - Point2f ellipseDirectTestVertices[4]; - ellipseDirectTest.points(ellipseDirectTestVertices); - ellipseDirectTrue.points(ellipseDirectTrueVertices); - float directDiff = 0.0f; - for (size_t i=0; i <=3; i++) { - Point2f diff = ellipseDirectTrueVertices[i] - ellipseDirectTestVertices[0]; - float d = diff.x * diff.x + diff.y * diff.y; - for (size_t j=1; i <=3; i++) { - diff = ellipseDirectTrueVertices[i] - ellipseDirectTestVertices[j]; - float dd = diff.x * diff.x + diff.y * diff.y; - if(dd lines; + int threshold{220*25-1}; + bool use_edgeval{true}; + HoughLines(img, lines, 1, CV_PI/180, threshold, 0, 0, 0.0, CV_PI, use_edgeval); + + // check results + ASSERT_EQ(3U, lines.size()); + // detected lines is assumed sorted from stronger to weaker. + EXPECT_EQ(12, lines[0][0]); + EXPECT_EQ(6, lines[1][0]); + EXPECT_EQ(18, lines[2][0]); + EXPECT_NEAR(CV_PI/2, lines[0][1], CV_PI/180 + 1e-6); + EXPECT_NEAR(CV_PI/2, lines[1][1], CV_PI/180 + 1e-6); + EXPECT_NEAR(CV_PI/2, lines[2][1], CV_PI/180 + 1e-6); +} + +TEST(WeightedHoughLines, diagonal) +{ + Mat img(25, 25, CV_8UC1, Scalar(0)); + // draw lines. + line(img, Point(0, 0), Point(25, 25), Scalar(128)); + line(img, Point(0, 25), Point(25, 0), Scalar(255)); + + // detect lines + std::vector lines; + int threshold{128*25-1}; + bool use_edgeval{true}; + HoughLines(img, lines, 1, CV_PI/180, threshold, 0, 0, 0.0, CV_PI, use_edgeval); + + // check results + ASSERT_EQ(2U, lines.size()); + // detected lines is assumed sorted from stronger to weaker. + EXPECT_EQ(18, lines[0][0]); // 25*sqrt(2)/2 = 17.67 ~ 18 + EXPECT_EQ(0, lines[1][0]); + EXPECT_NEAR(CV_PI/4, lines[0][1], CV_PI/180 + 1e-6); + EXPECT_NEAR(CV_PI*3/4, lines[1][1], CV_PI/180 + 1e-6); +} + INSTANTIATE_TEST_CASE_P( ImgProc, StandartHoughLinesTest, testing::Combine(testing::Values( "shared/pic5.png", "../stitching/a1.png" ), testing::Values( 1, 10 ), testing::Values( 0.05, 0.1 ), diff --git a/modules/imgproc/test/test_imgwarp_strict.cpp b/modules/imgproc/test/test_imgwarp_strict.cpp index 8d61d227b6..92c76e254f 100644 --- a/modules/imgproc/test/test_imgwarp_strict.cpp +++ b/modules/imgproc/test/test_imgwarp_strict.cpp @@ -1635,7 +1635,7 @@ void CV_WarpPerspective_Test::warpPerspective(const Mat& _src, Mat& _dst) continue; } - den *= INTER_TAB_SIZE; + den *= static_cast(INTER_TAB_SIZE); int v0 = saturate_cast((tM[0] * dx + tM[1] * dy + tM[2]) * den); int v1 = saturate_cast((tM[3] * dx + tM[4] * dy + tM[5]) * den); diff --git a/modules/java/android_sdk/CMakeLists.txt b/modules/java/android_sdk/CMakeLists.txt index a9e6ff52af..42fd08e15b 100644 --- a/modules/java/android_sdk/CMakeLists.txt +++ b/modules/java/android_sdk/CMakeLists.txt @@ -20,6 +20,11 @@ set(SOURSE_SETS_JAVA_SRC_DIRS "'java/src'") set(SOURSE_SETS_RES_SRC_DIRS "'java/res'") set(SOURSE_SETS_MANIFEST_SRC_FILE "'java/AndroidManifest.xml'") set(BUILD_GRADLE_COMPILE_OPTIONS " + android { + buildFeatures { + buildConfig true + } + } compileOptions { sourceCompatibility JavaVersion.VERSION_${ANDROID_GRADLE_JAVA_VERSION_INIT} targetCompatibility JavaVersion.VERSION_${ANDROID_GRADLE_JAVA_VERSION_INIT} @@ -183,7 +188,6 @@ set(SOURSE_SETS_JNI_LIBS_SRC_DIRS "'../../jni'") set(SOURSE_SETS_JAVA_SRC_DIRS "'src'") set(SOURSE_SETS_RES_SRC_DIRS "'${OpenCV_SOURCE_DIR}/modules/java/android_sdk/android_gradle_lib/res'") set(SOURSE_SETS_MANIFEST_SRC_FILE "'AndroidManifest.xml'") -set(BUILD_GRADLE_COMPILE_OPTIONS "") set(MAVEN_PUBLISH_PLUGIN_DECLARATION "") set(BUILD_GRADLE_ANDROID_PUBLISHING_CONFIG "") set(BUILD_GRADLE_PUBLISHING_CONFIG "") diff --git a/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java b/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java index b28c2121cd..0053a25a07 100644 --- a/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java +++ b/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java @@ -2,6 +2,7 @@ package org.opencv.android; import org.opencv.core.Mat; import org.opencv.core.Size; +import org.opencv.core.MatOfInt; import org.opencv.imgproc.Imgproc; @@ -123,8 +124,11 @@ public class NativeCameraView extends CameraBridgeViewBase { return false; } + MatOfInt params = new MatOfInt(Videoio.CAP_PROP_FRAME_WIDTH, width, + Videoio.CAP_PROP_FRAME_HEIGHT, height); + Log.d(TAG, "Try to open camera with index " + localCameraIndex); - mCamera = new VideoCapture(localCameraIndex, Videoio.CAP_ANDROID); + mCamera = new VideoCapture(localCameraIndex, Videoio.CAP_ANDROID, params); if (mCamera == null) return false; @@ -139,9 +143,6 @@ public class NativeCameraView extends CameraBridgeViewBase { mFrame = new RotatedCameraFrame(new NativeCameraFrame(mCamera), frameRotation); - mCamera.set(Videoio.CAP_PROP_FRAME_WIDTH, width); - mCamera.set(Videoio.CAP_PROP_FRAME_HEIGHT, height); - if (frameRotation % 180 == 0) { mFrameWidth = (int) mCamera.get(Videoio.CAP_PROP_FRAME_WIDTH); mFrameHeight = (int) mCamera.get(Videoio.CAP_PROP_FRAME_HEIGHT); @@ -181,10 +182,9 @@ public class NativeCameraView extends CameraBridgeViewBase { @Override public Mat rgba() { - mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','3')); - mCapture.retrieve(mBgr); - Log.d(TAG, "Retrived frame with size " + mBgr.cols() + "x" + mBgr.rows() + " and channels: " + mBgr.channels()); - Imgproc.cvtColor(mBgr, mRgba, Imgproc.COLOR_RGB2RGBA); + mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','4')); + mCapture.retrieve(mRgba); + Log.d(TAG, "Retrieved frame with size " + mRgba.cols() + "x" + mRgba.rows() + " and channels: " + mRgba.channels()); return mRgba; } @@ -192,7 +192,7 @@ public class NativeCameraView extends CameraBridgeViewBase { public Mat gray() { mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('G','R','E','Y')); mCapture.retrieve(mGray); - Log.d(TAG, "Retrived frame with size " + mGray.cols() + "x" + mGray.rows() + " and channels: " + mGray.channels()); + Log.d(TAG, "Retrieved frame with size " + mGray.cols() + "x" + mGray.rows() + " and channels: " + mGray.channels()); return mGray; } @@ -200,20 +200,17 @@ public class NativeCameraView extends CameraBridgeViewBase { mCapture = capture; mGray = new Mat(); mRgba = new Mat(); - mBgr = new Mat(); } @Override public void release() { if (mGray != null) mGray.release(); if (mRgba != null) mRgba.release(); - if (mBgr != null) mBgr.release(); } private VideoCapture mCapture; private Mat mRgba; private Mat mGray; - private Mat mBgr; }; private class CameraWorker implements Runnable { diff --git a/modules/js/generator/embindgen.py b/modules/js/generator/embindgen.py index 00d2406046..8352893133 100644 --- a/modules/js/generator/embindgen.py +++ b/modules/js/generator/embindgen.py @@ -132,7 +132,7 @@ white_list = None namespace_prefix_override = None # Features to be exported -export_enums = False +export_enums = True export_consts = True with_wrapped_functions = True with_default_params = True @@ -248,16 +248,17 @@ class ArgInfo(object): self.const = True elif m == "/Ref": self.reference = True - if self.tp == "Mat": - if self.outputarg: - self.tp = "cv::Mat&" - elif self.inputarg: - self.tp = "const cv::Mat&" - if self.tp == "vector_Mat": - if self.outputarg: - self.tp = "std::vector&" - elif self.inputarg: - self.tp = "const std::vector&" + if self.tp == "Mat" and (self.inputarg or self.outputarg): + self.tp = "cv::Mat&" + if self.inputarg and not self.outputarg: + self.const = True + if self.tp == "vector_Mat" and (self.inputarg or self.outputarg): + self.tp = "std::vector&" + if self.reference and not self.const: + self.inputarg = False + self.outputarg = True + elif self.inputarg and not self.outputarg: + self.const = True self.tp = handle_vector(self.tp).strip() if self.const: self.tp = "const " + self.tp @@ -916,7 +917,7 @@ class JSWrapperGenerator(object): if ns_name.split('.')[0] != 'cv': continue for name, enum in sorted(ns.enums.items()): - if not name.endswith('.anonymous'): + if '.unnamed_' not in name: name = name.replace("cv.", "") enum_values = [] for enum_val in enum: @@ -936,7 +937,10 @@ class JSWrapperGenerator(object): for ns_name, ns in sorted(self.namespaces.items()): if ns_name.split('.')[0] != 'cv': continue + # TODO CALIB_FIX_FOCAL_LENGTH is defined both in cv:: and cv::fisheye + prefix = 'FISHEYE_' if 'fisheye' in ns_name else '' for name, const in sorted(ns.consts.items()): + name = prefix + name # print("Gen consts: ", name, const) self.bindings.append(const_template.substitute(js_name=name, value=const)) diff --git a/modules/js/src/core_bindings.cpp b/modules/js/src/core_bindings.cpp index 0dc0c4779b..00e49cd0a7 100644 --- a/modules/js/src/core_bindings.cpp +++ b/modules/js/src/core_bindings.cpp @@ -378,7 +378,6 @@ namespace binding_utils return result; } - void Tracker_init_wrapper(cv::Tracker& arg0, const cv::Mat& arg1, const Rect& arg2) { return arg0.init(arg1, arg2); @@ -467,11 +466,15 @@ EMSCRIPTEN_BINDINGS(binding_utils) register_vector("DoubleVector"); register_vector("StringVector"); register_vector("PointVector"); + register_vector("Point3fVector"); register_vector("MatVector"); register_vector("RectVector"); register_vector("KeyPointVector"); register_vector("DMatchVector"); + register_vector>("CharVectorVector"); register_vector>("DMatchVectorVector"); + register_vector>("KeyPointVectorVector"); + register_vector>("PointVectorVector"); emscripten::class_("Mat") @@ -520,7 +523,7 @@ EMSCRIPTEN_BINDINGS(binding_utils) .function("colRange", select_overload(&cv::Mat::colRange)) .function("colRange", select_overload(&cv::Mat::colRange)) .function("step1", select_overload(&cv::Mat::step1)) - .function("clone", select_overload(&cv::Mat::clone)) + .function("mat_clone", select_overload(&cv::Mat::clone)) .function("depth", select_overload(&cv::Mat::depth)) .function("col", select_overload(&cv::Mat::col)) .function("dot", select_overload(&binding_utils::matDot)) @@ -584,7 +587,7 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("epsilon", &cv::TermCriteria::epsilon); #define EMSCRIPTEN_CV_SIZE(type) \ - emscripten::value_object("#type") \ + emscripten::value_object(#type) \ .field("width", &type::width) \ .field("height", &type::height); @@ -592,12 +595,13 @@ EMSCRIPTEN_BINDINGS(binding_utils) EMSCRIPTEN_CV_SIZE(Size2f) #define EMSCRIPTEN_CV_POINT(type) \ - emscripten::value_object("#type") \ + emscripten::value_object(#type) \ .field("x", &type::x) \ .field("y", &type::y); \ EMSCRIPTEN_CV_POINT(Point) EMSCRIPTEN_CV_POINT(Point2f) + EMSCRIPTEN_CV_POINT(Point3f) #define EMSCRIPTEN_CV_RECT(type, name) \ emscripten::value_object> (name) \ @@ -614,10 +618,6 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("size", &cv::RotatedRect::size) .field("angle", &cv::RotatedRect::angle); - function("rotatedRectPoints", select_overload(&binding_utils::rotatedRectPoints)); - function("rotatedRectBoundingRect", select_overload(&binding_utils::rotatedRectBoundingRect)); - function("rotatedRectBoundingRect2f", select_overload(&binding_utils::rotatedRectBoundingRect2f)); - emscripten::value_object("KeyPoint") .field("angle", &cv::KeyPoint::angle) .field("class_id", &cv::KeyPoint::class_id) @@ -644,10 +644,25 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("minLoc", &binding_utils::MinMaxLoc::minLoc) .field("maxLoc", &binding_utils::MinMaxLoc::maxLoc); + emscripten::value_object("Exception") + .field("code", &cv::Exception::code) + .field("msg", &binding_utils::getExceptionMsg, &binding_utils::setExceptionMsg); + emscripten::value_object("Circle") .field("center", &binding_utils::Circle::center) .field("radius", &binding_utils::Circle::radius); + function("boxPoints", select_overload(&binding_utils::rotatedRectPoints)); + function("rotatedRectPoints", select_overload(&binding_utils::rotatedRectPoints)); + function("rotatedRectBoundingRect", select_overload(&binding_utils::rotatedRectBoundingRect)); + function("rotatedRectBoundingRect2f", select_overload(&binding_utils::rotatedRectBoundingRect2f)); + function("exceptionFromPtr", &binding_utils::exceptionFromPtr, allow_raw_pointers()); + function("minMaxLoc", select_overload(&binding_utils::minMaxLoc)); + function("minMaxLoc", select_overload(&binding_utils::minMaxLoc_1)); + function("CV_MAT_DEPTH", &binding_utils::cvMatDepth); + function("getBuildInformation", &binding_utils::getBuildInformation); + +#ifdef HAVE_OPENCV_IMGPROC emscripten::value_object("Moments") .field("m00", &cv::Moments::m00) .field("m10", &cv::Moments::m10) @@ -674,49 +689,24 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("nu12", &cv::Moments::nu12) .field("nu03", &cv::Moments::nu03); - emscripten::value_object("Exception") - .field("code", &cv::Exception::code) - .field("msg", &binding_utils::getExceptionMsg, &binding_utils::setExceptionMsg); - - function("exceptionFromPtr", &binding_utils::exceptionFromPtr, allow_raw_pointers()); - -#ifdef HAVE_OPENCV_IMGPROC function("minEnclosingCircle", select_overload(&binding_utils::minEnclosingCircle)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_1)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_2)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_3)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_4)); -#endif - - function("minMaxLoc", select_overload(&binding_utils::minMaxLoc)); - - function("minMaxLoc", select_overload(&binding_utils::minMaxLoc_1)); - -#ifdef HAVE_OPENCV_IMGPROC function("morphologyDefaultBorderValue", &cv::morphologyDefaultBorderValue); #endif - function("CV_MAT_DEPTH", &binding_utils::cvMatDepth); - #ifdef HAVE_OPENCV_VIDEO function("CamShift", select_overload(&binding_utils::CamShiftWrapper)); - function("meanShift", select_overload(&binding_utils::meanShiftWrapper)); emscripten::class_("Tracker") .function("init", select_overload(&binding_utils::Tracker_init_wrapper), pure_virtual()) .function("update", select_overload(&binding_utils::Tracker_update_wrapper), pure_virtual()); - #endif - function("getBuildInformation", &binding_utils::getBuildInformation); - #ifdef HAVE_PTHREADS_PF function("parallel_pthreads_set_threads_num", &cv::parallel_pthreads_set_threads_num); function("parallel_pthreads_get_threads_num", &cv::parallel_pthreads_get_threads_num); diff --git a/modules/js/src/helpers.js b/modules/js/src/helpers.js index 962a0b4d90..d89414209a 100644 --- a/modules/js/src/helpers.js +++ b/modules/js/src/helpers.js @@ -66,7 +66,6 @@ Module['imread'] = function(imageSource) { ctx = canvas.getContext('2d'); } else { throw new Error('Please input the valid canvas or img id.'); - return; } var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -82,11 +81,9 @@ Module['imshow'] = function(canvasSource, mat) { } if (!(canvas instanceof HTMLCanvasElement)) { throw new Error('Please input the valid canvas element or id.'); - return; } if (!(mat instanceof cv.Mat)) { throw new Error('Please input the valid cv.Mat instance.'); - return; } // convert the mat type to cv.CV_8U @@ -108,7 +105,6 @@ Module['imshow'] = function(canvasSource, mat) { break; default: throw new Error('Bad number of channels (Source image must have 1, 3 or 4 channels)'); - return; } var imgData = new ImageData(new Uint8ClampedArray(img.data), img.cols, img.rows); var ctx = canvas.getContext('2d'); @@ -128,7 +124,6 @@ Module['VideoCapture'] = function(videoSource) { } if (!(video instanceof HTMLVideoElement)) { throw new Error('Please input the valid video element or id.'); - return; } var canvas = document.createElement('canvas'); canvas.width = video.width; @@ -138,15 +133,12 @@ Module['VideoCapture'] = function(videoSource) { this.read = function(frame) { if (!(frame instanceof cv.Mat)) { throw new Error('Please input the valid cv.Mat instance.'); - return; } if (frame.type() !== cv.CV_8UC4) { throw new Error('Bad type of input mat: the type should be cv.CV_8UC4.'); - return; } if (frame.cols !== video.width || frame.rows !== video.height) { throw new Error('Bad size of input mat: the size should be same as the video.'); - return; } ctx.drawImage(video, 0, 0, video.width, video.height); frame.data.set(ctx.getImageData(0, 0, video.width, video.height).data); @@ -263,7 +255,7 @@ function Scalar(v0, v1, v2, v3) { Scalar.prototype = new Array; // eslint-disable-line no-array-constructor Scalar.all = function(v) { - return new Scalar(v, v, v, v); + return Scalar(v, v, v, v); }; Module['Scalar'] = Scalar; @@ -273,8 +265,8 @@ function MinMaxLoc() { case 0: { this.minVal = 0; this.maxVal = 0; - this.minLoc = new Point(); - this.maxLoc = new Point(); + this.minLoc = Point(0, 0); + this.maxLoc = Point(0, 0); break; } case 4: { @@ -295,7 +287,7 @@ Module['MinMaxLoc'] = MinMaxLoc; function Circle() { switch (arguments.length) { case 0: { - this.center = new Point(); + this.center = Point(0, 0); this.radius = 0; break; } diff --git a/modules/js/test/test_core.js b/modules/js/test/test_core.js index bfd9d2cd0d..3cd352ef83 100644 --- a/modules/js/test/test_core.js +++ b/modules/js/test/test_core.js @@ -4,8 +4,380 @@ QUnit.module('Core', {}); +QUnit.test('test_operations_on_arrays', function(assert) { + // Transpose + { + let mat1 = cv.Mat.eye(9, 7, cv.CV_8UC3); + let mat2 = new cv.Mat(); + + cv.transpose(mat1, mat2); + + // Verify result. + let size = mat2.size(); + assert.equal(mat2.channels(), 3); + assert.equal(size.height, 7); + assert.equal(size.width, 9); + } + + // Concat + { + let mat = cv.Mat.ones({height: 10, width: 5}, cv.CV_8UC3); + let mat2 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); + let mat3 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); + + let out = new cv.Mat(); + let input = new cv.MatVector(); + input.push_back(mat); + input.push_back(mat2); + input.push_back(mat3); + + cv.vconcat(input, out); + + // Verify result. + let size = out.size(); + assert.equal(out.channels(), 3); + assert.equal(size.height, 30); + assert.equal(size.width, 5); + assert.equal(out.elemSize1(), 1); + + cv.hconcat(input, out); + + // Verify result. + size = out.size(); + assert.equal(out.channels(), 3); + assert.equal(size.height, 10); + assert.equal(size.width, 15); + assert.equal(out.elemSize1(), 1); + + input.delete(); + out.delete(); + } + + // Min, Max + { + let data1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]); + let data2 = new Uint8Array([0, 4, 0, 8, 0, 12, 0, 16, 0]); + + let expectedMin = new Uint8Array([0, 2, 0, 4, 0, 6, 0, 8, 0]); + let expectedMax = new Uint8Array([1, 4, 3, 8, 5, 12, 7, 16, 9]); + + let dataPtr = cv._malloc(3*3*1); + let dataPtr2 = cv._malloc(3*3*1); + + let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); + dataHeap.set(new Uint8Array(data1.buffer)); + + let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); + dataHeap2.set(new Uint8Array(data2.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); + let mat3 = new cv.Mat(); + + cv.min(mat1, mat2, mat3); + + // Verify result. + let size = mat2.size(); + assert.equal(mat2.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedMin); + + cv.max(mat1, mat2, mat3); + + // Verify result. + size = mat2.size(); + assert.equal(mat2.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedMax); + + cv._free(dataPtr); + cv._free(dataPtr2); + } + + // Bitwise operations + { + let data1 = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); + let data2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); + + let expectedAnd = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); + let expectedOr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); + let expectedXor = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); + + let expectedNot = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); + + let dataPtr = cv._malloc(3*3*1); + let dataPtr2 = cv._malloc(3*3*1); + + let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); + dataHeap.set(new Uint8Array(data1.buffer)); + + let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); + dataHeap2.set(new Uint8Array(data2.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); + let mat3 = new cv.Mat(); + let none = new cv.Mat(); + + cv.bitwise_not(mat1, mat3, none); + + // Verify result. + let size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedNot); + + cv.bitwise_and(mat1, mat2, mat3, none); + + // Verify result. + size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedAnd); + + cv.bitwise_or(mat1, mat2, mat3, none); + + // Verify result. + size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedOr); + + cv.bitwise_xor(mat1, mat2, mat3, none); + + // Verify result. + size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedXor); + + cv._free(dataPtr); + cv._free(dataPtr2); + } + + // Arithmetic operations + { + let data1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); + let data2 = new Uint8Array([0, 2, 4, 6, 8, 10, 12, 14, 16]); + let data3 = new Uint8Array([0, 1, 0, 1, 0, 1, 0, 1, 0]); + + // |data1 - data2| + let expectedAbsDiff = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); + let expectedAdd = new Uint8Array([0, 3, 6, 9, 12, 15, 18, 21, 24]); + + const alpha = 4; + const beta = -1; + const gamma = 3; + // 4*data1 - data2 + 3 + let expectedWeightedAdd = new Uint8Array([3, 5, 7, 9, 11, 13, 15, 17, 19]); + + let dataPtr = cv._malloc(3*3*1); + let dataPtr2 = cv._malloc(3*3*1); + let dataPtr3 = cv._malloc(3*3*1); + + let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); + dataHeap.set(new Uint8Array(data1.buffer)); + let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); + dataHeap2.set(new Uint8Array(data2.buffer)); + let dataHeap3 = new Uint8Array(cv.HEAPU8.buffer, dataPtr3, 3*3*1); + dataHeap3.set(new Uint8Array(data3.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); + let mat3 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr3, 0); + + let dst = new cv.Mat(); + let none = new cv.Mat(); + + cv.absdiff(mat1, mat2, dst); + + // Verify result. + let size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(dst.data, expectedAbsDiff); + + cv.add(mat1, mat2, dst, none, -1); + + // Verify result. + size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + assert.deepEqual(dst.data, expectedAdd); + + cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst, -1); + + // Verify result. + size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + assert.deepEqual(dst.data, expectedWeightedAdd); + + // default parameter + cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst); + + // Verify result. + size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(dst.data, expectedWeightedAdd); + + mat1.delete(); + mat2.delete(); + mat3.delete(); + dst.delete(); + none.delete(); + } + + // Invert + { + let inv1 = new cv.Mat(); + let inv2 = new cv.Mat(); + let inv3 = new cv.Mat(); + let inv4 = new cv.Mat(); + + let data1 = new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + let data2 = new Float32Array([0, 0, 0, + 0, 5, 0, + 0, 0, 0]); + let data3 = new Float32Array([1, 1, 1, 0, + 0, 3, 1, 2, + 2, 3, 1, 0, + 1, 0, 2, 1]); + let data4 = new Float32Array([1, 4, 5, + 4, 2, 2, + 5, 2, 2]); + + let expected1 = new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + // Inverse does not exist! + let expected3 = new Float32Array([-3, -1/2, 3/2, 1, + 1, 1/4, -1/4, -1/2, + 3, 1/4, -5/4, -1/2, + -3, 0, 1, 1]); + let expected4 = new Float32Array([0, -1, 1, + -1, 23/2, -9, + 1, -9, 7]); + + let dataPtr1 = cv._malloc(3*3*4); + let dataPtr2 = cv._malloc(3*3*4); + let dataPtr3 = cv._malloc(4*4*4); + let dataPtr4 = cv._malloc(3*3*4); + + let dataHeap = new Float32Array(cv.HEAP32.buffer, dataPtr1, 3*3); + dataHeap.set(new Float32Array(data1.buffer)); + let dataHeap2 = new Float32Array(cv.HEAP32.buffer, dataPtr2, 3*3); + dataHeap2.set(new Float32Array(data2.buffer)); + let dataHeap3 = new Float32Array(cv.HEAP32.buffer, dataPtr3, 4*4); + dataHeap3.set(new Float32Array(data3.buffer)); + let dataHeap4 = new Float32Array(cv.HEAP32.buffer, dataPtr4, 3*3); + dataHeap4.set(new Float32Array(data4.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr1, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr2, 0); + let mat3 = new cv.Mat(4, 4, cv.CV_32FC1, dataPtr3, 0); + let mat4 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr4, 0); + + QUnit.assert.deepEqualWithTolerance = function( value, expected, tolerance ) { + for (let i = 0; i < value.length; i= i+1) { + this.pushResult( { + result: Math.abs(value[i]-expected[i]) < tolerance, + actual: value[i], + expected: expected[i], + } ); + } + }; + + cv.invert(mat1, inv1, 0); + + // Verify result. + let size = inv1.size(); + assert.equal(inv1.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + assert.deepEqualWithTolerance(inv1.data32F, expected1, 0.0001); + + cv.invert(mat2, inv2, 0); + + // Verify result. + assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); + + cv.invert(mat3, inv3, 0); + + // Verify result. + size = inv3.size(); + assert.equal(inv3.channels(), 1); + assert.equal(size.height, 4); + assert.equal(size.width, 4); + assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); + + cv.invert(mat3, inv3, 1); + + // Verify result. + assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); + + cv.invert(mat4, inv4, 2); + + // Verify result. + assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); + + cv.invert(mat4, inv4, 3); + + // Verify result. + assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); + + mat1.delete(); + mat2.delete(); + mat3.delete(); + mat4.delete(); + inv1.delete(); + inv2.delete(); + inv3.delete(); + inv4.delete(); + } + + //Rotate + { + let dst = new cv.Mat(); + let src = cv.matFromArray(3, 2, cv.CV_8U, [1,2,3,4,5,6]); + + cv.rotate(src, dst, cv.ROTATE_90_CLOCKWISE); + + let size = dst.size(); + assert.equal(size.height, 2, "ROTATE_HEIGHT"); + assert.equal(size.width, 3, "ROTATE_WIGTH"); + + let expected = new Uint8Array([5,3,1,6,4,2]); + + assert.deepEqual(dst.data, expected); + + dst.delete(); + src.delete(); + } +}); + QUnit.test('test_LUT', function(assert) { - // test LUT { let src = cv.matFromArray(3, 3, cv.CV_8UC1, [255, 128, 0, 0, 128, 255, 1, 2, 254]); let lutTable = []; @@ -18,7 +390,7 @@ QUnit.test('test_LUT', function(assert) { cv.LUT(src, lut, dst); - //console.log(dst.data); + // Verify result. assert.equal(dst.ucharAt(0), 0); assert.equal(dst.ucharAt(1), 127); assert.equal(dst.ucharAt(2), 255); diff --git a/modules/js/test/test_imgproc.js b/modules/js/test/test_imgproc.js index 0b1b686643..84df0fd468 100644 --- a/modules/js/test/test_imgproc.js +++ b/modules/js/test/test_imgproc.js @@ -70,6 +70,54 @@ QUnit.module('Image Processing', {}); +QUnit.test('applyColorMap', function(assert) { + { + let src = cv.matFromArray(2, 1, cv.CV_8U, [50,100]); + cv.applyColorMap(src, src, cv.COLORMAP_BONE); + + // Verify result. + let expected = new Uint8Array([60,44,44,119,89,87]); + + assert.deepEqual(src.data, expected); + src.delete(); + } +}); + +QUnit.test('blendLinear', function(assert) { + { + let src1 = cv.matFromArray(2, 1, cv.CV_8U, [50,100]); + let src2 = cv.matFromArray(2, 1, cv.CV_8U, [200,20]); + let weights1 = cv.matFromArray(2, 1, cv.CV_32F, [0.4,0.5]); + let weights2 = cv.matFromArray(2, 1, cv.CV_32F, [0.6,0.5]); + let dst = new cv.Mat(); + cv.blendLinear(src1, src2, weights1, weights2, dst); + + // Verify result. + let expected = new Uint8Array([140,60]); + + assert.deepEqual(dst.data, expected); + src1.delete(); + src2.delete(); + weights1.delete(); + weights2.delete(); + dst.delete(); + } +}); + +QUnit.test('createHanningWindow', function(assert) { + { + let dst = new cv.Mat(); + cv.createHanningWindow(dst, new cv.Size(5, 3), cv.CV_32F); + + // Verify result. + let expected = cv.matFromArray(3, 5, cv.CV_32F, [0.,0.,0.,0.,0.,0.,0.70710677,1.,0.70710677,0.,0.,0.,0.,0.,0.]); + + assert.deepEqual(dst.data, expected.data); + dst.delete(); + expected.delete(); + } +}); + QUnit.test('test_imgProc', function(assert) { // calcHist { @@ -127,6 +175,7 @@ QUnit.test('test_imgProc', function(assert) { dest.delete(); source.delete(); } + // equalizeHist { let source = new cv.Mat(10, 10, cv.CV_8UC1); @@ -196,7 +245,9 @@ QUnit.test('test_imgProc', function(assert) { expected_img.delete(); compare_result.delete(); } +}); +QUnit.test('Drawing Functions', function(assert) { // fillPoly { let img_width = 6; @@ -359,6 +410,7 @@ QUnit.test('test_shape', function(assert) { }); QUnit.test('test_min_enclosing', function(assert) { + // minEnclosingCircle { let points = new cv.Mat(4, 1, cv.CV_32FC2); @@ -378,6 +430,31 @@ QUnit.test('test_min_enclosing', function(assert) { points.delete(); } + + // minEnclosingTriangle + { + let dst = cv.Mat.zeros(80, 80, cv.CV_8U); + let contours = new cv.MatVector(); + let hierarchy = new cv.Mat(); + let triangle = new cv.Mat(); + + cv.drawMarker(dst, new cv.Point(40, 40), new cv.Scalar(255)); + cv.findContoursLinkRuns(dst,contours,hierarchy); + cv.minEnclosingTriangle(contours.get(0),triangle); + + // Verify result. + const triangleData = triangle.data32F; + assert.deepEqual(triangleData[0], triangleData[4]); + assert.deepEqual(triangleData[1], 20); + assert.deepEqual(triangleData[2], 30); + assert.deepEqual(triangleData[3], 40); + assert.deepEqual(triangleData[5], 60); + + dst.delete(); + contours.delete(); + hierarchy.delete(); + triangle.delete(); + } }); QUnit.test('test_filter', function(assert) { @@ -427,6 +504,58 @@ QUnit.test('test_filter', function(assert) { assert.equal(mat2.channels(), 1); assert.equal(size.height, 7); assert.equal(size.width, 7); + mat1.delete(); + mat2.delete(); + } + + // spatialGradient + { + let src = cv.matFromArray(4, 4, cv.CV_8U, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); + let dx = new cv.Mat(); + let dy = new cv.Mat(); + cv.spatialGradient(src, dx, dy); + + // Verify result. + let expected_dx = new cv.Mat(); + let expected_dy = new cv.Mat(); + cv.Sobel(src, expected_dx, cv.CV_16SC1, 1, 0, 3); + cv.Sobel(src, expected_dy, cv.CV_16SC1, 0, 1, 3); + + assert.deepEqual(dx.data, expected_dx.data); + assert.deepEqual(dy.data, expected_dy.data); + + src.delete(); + dx.delete(); + dy.delete(); + expected_dx.delete(); + expected_dy.delete(); + } + + // sqrBoxFilter + { + let src = cv.matFromArray(2, 3, cv.CV_8U, [1,2,1,1,2,1]); + let dst = new cv.Mat(); + cv.sqrBoxFilter(src, dst, cv.CV_32F, new cv.Size(3, 3)); + + // Verify result. + let expected = cv.matFromArray(2, 3, cv.CV_32F,[3.0,2.0,3.0,3.0,2.0,3.0]); + + assert.deepEqual(dst.data, expected.data); + src.delete(); + dst.delete(); + expected.delete(); + } + + // stackBlur + { + let src = cv.matFromArray(2, 3, cv.CV_8U, [10,25,30,45,50,60]); + cv.stackBlur(src, src, new cv.Size(3, 3)); + + // Verify result. + let expected = new Uint8Array([22,29,36,38,43,50]); + + assert.deepEqual(src.data, expected); + src.delete(); } // medianBlur @@ -438,23 +567,12 @@ QUnit.test('test_filter', function(assert) { // Verify result. let size = mat2.size(); + assert.equal(mat2.channels(), 3); assert.equal(size.height, 9); assert.equal(size.width, 9); - } - - // Transpose - { - let mat1 = cv.Mat.eye(9, 9, cv.CV_8UC3); - let mat2 = new cv.Mat(); - - cv.transpose(mat1, mat2); - - // Verify result. - let size = mat2.size(); - assert.equal(mat2.channels(), 3); - assert.equal(size.height, 9); - assert.equal(size.width, 9); + mat1.delete(); + mat2.delete(); } // bilateralFilter @@ -481,8 +599,9 @@ QUnit.test('test_filter', function(assert) { mat1.delete(); mat2.delete(); } +}); - // Watershed +QUnit.test('test_watershed', function(assert) { { let mat = cv.Mat.ones(11, 11, cv.CV_8UC3); let out = new cv.Mat(11, 11, cv.CV_32SC1); @@ -499,44 +618,9 @@ QUnit.test('test_filter', function(assert) { mat.delete(); out.delete(); } +}); - // Concat - { - let mat = cv.Mat.ones({height: 10, width: 5}, cv.CV_8UC3); - let mat2 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); - let mat3 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); - - - let out = new cv.Mat(); - let input = new cv.MatVector(); - input.push_back(mat); - input.push_back(mat2); - input.push_back(mat3); - - cv.vconcat(input, out); - - // Verify result. - let size = out.size(); - assert.equal(out.channels(), 3); - assert.equal(size.height, 30); - assert.equal(size.width, 5); - assert.equal(out.elemSize1(), 1); - - cv.hconcat(input, out); - - // Verify result. - size = out.size(); - assert.equal(out.channels(), 3); - assert.equal(size.height, 10); - assert.equal(size.width, 15); - assert.equal(out.elemSize1(), 1); - - input.delete(); - out.delete(); - } - - - // distanceTransform letiants +QUnit.test('test_distanceTransform', function(assert) { { let mat = cv.Mat.ones(11, 11, cv.CV_8UC1); let out = new cv.Mat(11, 11, cv.CV_32FC1); @@ -551,7 +635,6 @@ QUnit.test('test_filter', function(assert) { assert.equal(size.width, 11); assert.equal(out.elemSize1(), 4); - cv.distanceTransformWithLabels(mat, out, labels, cv.DIST_L2, maskSize, cv.DIST_LABEL_CCOMP); @@ -572,200 +655,9 @@ QUnit.test('test_filter', function(assert) { out.delete(); labels.delete(); } +}); - // Min, Max - { - let data1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]); - let data2 = new Uint8Array([0, 4, 0, 8, 0, 12, 0, 16, 0]); - - let expectedMin = new Uint8Array([0, 2, 0, 4, 0, 6, 0, 8, 0]); - let expectedMax = new Uint8Array([1, 4, 3, 8, 5, 12, 7, 16, 9]); - - let dataPtr = cv._malloc(3*3*1); - let dataPtr2 = cv._malloc(3*3*1); - - let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); - dataHeap.set(new Uint8Array(data1.buffer)); - - let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); - dataHeap2.set(new Uint8Array(data2.buffer)); - - - let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); - - let mat3 = new cv.Mat(); - - cv.min(mat1, mat2, mat3); - // Verify result. - let size = mat2.size(); - assert.equal(mat2.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedMin); - - - cv.max(mat1, mat2, mat3); - // Verify result. - size = mat2.size(); - assert.equal(mat2.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedMax); - - cv._free(dataPtr); - cv._free(dataPtr2); - } - - // Bitwise operations - { - let data1 = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); - let data2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); - - let expectedAnd = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); - let expectedOr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); - let expectedXor = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); - - let expectedNot = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); - - let dataPtr = cv._malloc(3*3*1); - let dataPtr2 = cv._malloc(3*3*1); - - let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); - dataHeap.set(new Uint8Array(data1.buffer)); - - let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); - dataHeap2.set(new Uint8Array(data2.buffer)); - - - let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); - - let mat3 = new cv.Mat(); - let none = new cv.Mat(); - - cv.bitwise_not(mat1, mat3, none); - // Verify result. - let size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedNot); - - cv.bitwise_and(mat1, mat2, mat3, none); - // Verify result. - size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedAnd); - - - cv.bitwise_or(mat1, mat2, mat3, none); - // Verify result. - size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedOr); - - cv.bitwise_xor(mat1, mat2, mat3, none); - // Verify result. - size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedXor); - - cv._free(dataPtr); - cv._free(dataPtr2); - } - - // Arithmetic operations - { - let data1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); - let data2 = new Uint8Array([0, 2, 4, 6, 8, 10, 12, 14, 16]); - let data3 = new Uint8Array([0, 1, 0, 1, 0, 1, 0, 1, 0]); - - // |data1 - data2| - let expectedAbsDiff = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); - let expectedAdd = new Uint8Array([0, 3, 6, 9, 12, 15, 18, 21, 24]); - - const alpha = 4; - const beta = -1; - const gamma = 3; - // 4*data1 - data2 + 3 - let expectedWeightedAdd = new Uint8Array([3, 5, 7, 9, 11, 13, 15, 17, 19]); - - let dataPtr = cv._malloc(3*3*1); - let dataPtr2 = cv._malloc(3*3*1); - let dataPtr3 = cv._malloc(3*3*1); - - let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); - dataHeap.set(new Uint8Array(data1.buffer)); - let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); - dataHeap2.set(new Uint8Array(data2.buffer)); - let dataHeap3 = new Uint8Array(cv.HEAPU8.buffer, dataPtr3, 3*3*1); - dataHeap3.set(new Uint8Array(data3.buffer)); - - let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); - let mat3 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr3, 0); - - let dst = new cv.Mat(); - let none = new cv.Mat(); - - cv.absdiff(mat1, mat2, dst); - // Verify result. - let size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedAbsDiff); - - cv.add(mat1, mat2, dst, none, -1); - // Verify result. - size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedAdd); - - cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst, -1); - // Verify result. - size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedWeightedAdd); - - // default parameter - cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst); - // Verify result. - size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedWeightedAdd); - - mat1.delete(); - mat2.delete(); - mat3.delete(); - dst.delete(); - none.delete(); - } - - // Integral letiants +QUnit.test('test_integral', function(assert) { { let mat = cv.Mat.eye({height: 100, width: 100}, cv.CV_8UC3); let sum = new cv.Mat(); @@ -797,162 +689,55 @@ QUnit.test('test_filter', function(assert) { sqSum.delete(); title.delete(); } +}); - // Mean, meanSTDev +QUnit.test('test_rotatedRectangleIntersection', function(assert) { { - let mat = cv.Mat.eye({height: 100, width: 100}, cv.CV_8UC3); - let sum = new cv.Mat(); - let sqSum = new cv.Mat(); - let title = new cv.Mat(); + let dst = cv.Mat.zeros(80, 80, cv.CV_8U); + let contours = new cv.MatVector(); + let hierarchy = new cv.Mat(); + let intersectionPoints = new cv.Mat(); - cv.integral(mat, sum, -1); + cv.drawMarker(dst, new cv.Point(40, 40), new cv.Scalar(255)); + cv.findContoursLinkRuns(dst,contours,hierarchy); + let rr1 = cv.minAreaRect(contours.get(0)); + let rr2 = cv.minAreaRect(contours.get(0)); + let rr3 = new cv.RotatedRect({x: 40, y: 40}, {height: 10, width: 20}, 45); + + let intersectionType = cv.rotatedRectangleIntersection(rr1, rr2, intersectionPoints); // Verify result. - let size = sum.size(); - assert.equal(sum.channels(), 3); - assert.equal(size.height, 100+1); - assert.equal(size.width, 100+1); + assert.deepEqual(intersectionType, cv.INTERSECT_FULL); + intersectionPoints.convertTo(intersectionPoints, cv.CV_32S); + let intersectionPointsData = intersectionPoints.data32S; + assert.deepEqual(intersectionPointsData[0], 30); + assert.deepEqual(intersectionPointsData[1], 40); + assert.deepEqual(intersectionPointsData[2], 40); + assert.deepEqual(intersectionPointsData[3], 30); + assert.deepEqual(intersectionPointsData[4], 50); + assert.deepEqual(intersectionPointsData[5], 40); + assert.deepEqual(intersectionPointsData[6], 40); + assert.deepEqual(intersectionPointsData[7], 50); + + intersectionType = cv.rotatedRectangleIntersection(rr1, rr3, intersectionPoints); - cv.integral2(mat, sum, sqSum, -1, -1); // Verify result. - size = sum.size(); - assert.equal(sum.channels(), 3); - assert.equal(size.height, 100+1); - assert.equal(size.width, 100+1); - - size = sqSum.size(); - assert.equal(sqSum.channels(), 3); - assert.equal(size.height, 100+1); - assert.equal(size.width, 100+1); - - mat.delete(); - sum.delete(); - sqSum.delete(); - title.delete(); - } - - // Invert - { - let inv1 = new cv.Mat(); - let inv2 = new cv.Mat(); - let inv3 = new cv.Mat(); - let inv4 = new cv.Mat(); - - - let data1 = new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - let data2 = new Float32Array([0, 0, 0, - 0, 5, 0, - 0, 0, 0]); - let data3 = new Float32Array([1, 1, 1, 0, - 0, 3, 1, 2, - 2, 3, 1, 0, - 1, 0, 2, 1]); - let data4 = new Float32Array([1, 4, 5, - 4, 2, 2, - 5, 2, 2]); - - let expected1 = new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - // Inverse does not exist! - let expected3 = new Float32Array([-3, -1/2, 3/2, 1, - 1, 1/4, -1/4, -1/2, - 3, 1/4, -5/4, -1/2, - -3, 0, 1, 1]); - let expected4 = new Float32Array([0, -1, 1, - -1, 23/2, -9, - 1, -9, 7]); - - let dataPtr1 = cv._malloc(3*3*4); - let dataPtr2 = cv._malloc(3*3*4); - let dataPtr3 = cv._malloc(4*4*4); - let dataPtr4 = cv._malloc(3*3*4); - - let dataHeap = new Float32Array(cv.HEAP32.buffer, dataPtr1, 3*3); - dataHeap.set(new Float32Array(data1.buffer)); - let dataHeap2 = new Float32Array(cv.HEAP32.buffer, dataPtr2, 3*3); - dataHeap2.set(new Float32Array(data2.buffer)); - let dataHeap3 = new Float32Array(cv.HEAP32.buffer, dataPtr3, 4*4); - dataHeap3.set(new Float32Array(data3.buffer)); - let dataHeap4 = new Float32Array(cv.HEAP32.buffer, dataPtr4, 3*3); - dataHeap4.set(new Float32Array(data4.buffer)); - - let mat1 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr1, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr2, 0); - let mat3 = new cv.Mat(4, 4, cv.CV_32FC1, dataPtr3, 0); - let mat4 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr4, 0); - - QUnit.assert.deepEqualWithTolerance = function( value, expected, tolerance ) { - for (let i = 0; i < value.length; i= i+1) { - this.pushResult( { - result: Math.abs(value[i]-expected[i]) < tolerance, - actual: value[i], - expected: expected[i], - } ); - } - }; - - cv.invert(mat1, inv1, 0); - // Verify result. - let size = inv1.size(); - assert.equal(inv1.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - assert.deepEqualWithTolerance(inv1.data32F, expected1, 0.0001); - - - cv.invert(mat2, inv2, 0); - // Verify result. - assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); - - cv.invert(mat3, inv3, 0); - // Verify result. - size = inv3.size(); - assert.equal(inv3.channels(), 1); - assert.equal(size.height, 4); - assert.equal(size.width, 4); - assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); - - cv.invert(mat3, inv3, 1); - // Verify result. - assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); - - cv.invert(mat4, inv4, 2); - // Verify result. - assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); - - cv.invert(mat4, inv4, 3); - // Verify result. - assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); - - mat1.delete(); - mat2.delete(); - mat3.delete(); - mat4.delete(); - inv1.delete(); - inv2.delete(); - inv3.delete(); - inv4.delete(); - } - //Rotate - { - let dst = new cv.Mat(); - let src = cv.matFromArray(3, 2, cv.CV_8U, [1,2,3,4,5,6]); - - cv.rotate(src, dst, cv.ROTATE_90_CLOCKWISE); - - let size = dst.size(); - assert.equal(size.height, 2, "ROTATE_HEIGHT"); - assert.equal(size.width, 3, "ROTATE_WIGTH"); - - let expected = new Uint8Array([5,3,1,6,4,2]); - - assert.deepEqual(dst.data, expected); + assert.deepEqual(intersectionType, cv.INTERSECT_PARTIAL); + intersectionPoints.convertTo(intersectionPoints, cv.CV_32S); + intersectionPointsData = intersectionPoints.data32S; + assert.deepEqual(intersectionPointsData[0], 39); + assert.deepEqual(intersectionPointsData[1], 31); + assert.deepEqual(intersectionPointsData[2], 49); + assert.deepEqual(intersectionPointsData[3], 41); + assert.deepEqual(intersectionPointsData[4], 41); + assert.deepEqual(intersectionPointsData[5], 49); + assert.deepEqual(intersectionPointsData[6], 31); + assert.deepEqual(intersectionPointsData[7], 39); dst.delete(); - src.delete(); + contours.delete(); + hierarchy.delete(); + intersectionPoints.delete(); } }); @@ -973,7 +758,6 @@ QUnit.test('warpPolar', function(assert) { ]); }); - QUnit.test('IntelligentScissorsMB', function(assert) { const lines = new cv.Mat(50, 100, cv.CV_8U, new cv.Scalar(0)); lines.row(10).setTo(new cv.Scalar(255)); diff --git a/modules/js/test/test_mat.js b/modules/js/test/test_mat.js index a1d5c98b17..f0c5211568 100644 --- a/modules/js/test/test_mat.js +++ b/modules/js/test/test_mat.js @@ -173,7 +173,7 @@ QUnit.test('test_mat_creation', function(assert) { // clone { let mat = cv.Mat.ones(5, 5, cv.CV_8UC1); - let mat2 = mat.clone(); + let mat2 = mat.mat_clone(); assert.equal(mat.channels, mat2.channels); assert.equal(mat.size().height, mat2.size().height); diff --git a/modules/js/test/test_utils.js b/modules/js/test/test_utils.js index 03096aec74..c02b4f682a 100644 --- a/modules/js/test/test_utils.js +++ b/modules/js/test/test_utils.js @@ -245,5 +245,8 @@ QUnit.test('test_rotated_rect', function(assert) { assert.equal(points[0].x, cv.RotatedRect.boundingRect2f(rect).x); assert.equal(points[1].y, cv.RotatedRect.boundingRect2f(rect).y); + + let points1 = cv.boxPoints(rect); + assert.deepEqual(points, points1); } }); diff --git a/modules/objc/generator/gen_objc.py b/modules/objc/generator/gen_objc.py index 861ac41098..484ca3b9c7 100755 --- a/modules/objc/generator/gen_objc.py +++ b/modules/objc/generator/gen_objc.py @@ -1121,7 +1121,7 @@ class ObjectiveCWrapperGenerator(object): name = line[p0:p1] for arg in args: if arg.name == name: - toWrite.append(re.sub('\*\s*@param ', '* @param ', line)) + toWrite.append(re.sub(r'\*\s*@param ', '* @param ', line)) break else: s0 = line.find("@see") @@ -1513,13 +1513,13 @@ def escape_underscore(str): return str.replace('_', '\\_') def escape_texttt(str): - return re.sub(re.compile('texttt{(.*?)\}', re.DOTALL), lambda x: 'texttt{' + escape_underscore(x.group(1)) + '}', str) + return re.sub(re.compile('texttt{(.*?)}', re.DOTALL), lambda x: 'texttt{' + escape_underscore(x.group(1)) + '}', str) def get_macros(tex): out = "" - if re.search("\\\\fork\s*{", tex): + if re.search(r"\\fork\s*{", tex): out += "\\newcommand{\\fork}[4]{ \\left\\{ \\begin{array}{l l} #1 & \\text{#2}\\\\\\\\ #3 & \\text{#4}\\\\\\\\ \\end{array} \\right.} " - if re.search("\\\\vecthreethree\s*{", tex): + if re.search(r"\\vecthreethree\s*{", tex): out += "\\newcommand{\\vecthreethree}[9]{ \\begin{bmatrix} #1 & #2 & #3\\\\\\\\ #4 & #5 & #6\\\\\\\\ #7 & #8 & #9 \\end{bmatrix} } " return out @@ -1663,7 +1663,9 @@ if __name__ == "__main__": h_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.h')] hpp_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.hpp')] srcfiles = h_files + hpp_files - srcfiles = [f for f in srcfiles if not re_bad.search(f.replace('\\', '/'))] + # Use relative paths to avoid being affected by the name of the parent directory. + # See https://github.com/opencv/opencv/issues/26712 + srcfiles = [f for f in srcfiles if not re_bad.search(os.path.relpath(f, module_location).replace('\\', '/'))] logging.info("\nFiles (%d):\n%s", len(srcfiles), pformat(srcfiles)) common_headers_fname = os.path.join(misc_location, 'filelist_common') diff --git a/modules/objdetect/src/aruco/aruco_board.cpp b/modules/objdetect/src/aruco/aruco_board.cpp index f5f274ec0c..b6a6653d40 100644 --- a/modules/objdetect/src/aruco/aruco_board.cpp +++ b/modules/objdetect/src/aruco/aruco_board.cpp @@ -115,7 +115,13 @@ void Board::Impl::generateImage(Size outSize, OutputArray img, int marginSize, i } // get marker - Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order + Point2f vecWidth = outCorners[1] - outCorners[0]; + float width = (float)cv::norm(vecWidth); + + Point2f vecHeight = outCorners[2] - outCorners[0]; + float height = (float)cv::norm(vecHeight); + + Size dst_sz(cvRound(width), cvRound(height)); dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height); //marker should be square dictionary.generateImageMarker(ids[m], dst_sz.width, marker, borderBits); diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index d575f668d0..f1540a00d9 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include namespace cv @@ -3710,7 +3709,11 @@ bool QRDetectMulti::checkSets(vector >& true_points_group, vecto vector set_size(true_points_group.size()); for (size_t i = 0; i < true_points_group.size(); i++) { - set_size[i] = int( (true_points_group[i].size() - 2 ) * (true_points_group[i].size() - 1) * true_points_group[i].size()) / 6; + const std::uint64_t true_points_group_size = true_points_group[i].size(); + // ensure set_size[i] doesn't overflow + CV_Assert(true_points_group_size <= 2345); + set_size[i] = static_cast((true_points_group_size - 2) * (true_points_group_size - 1) * + true_points_group_size / 6); } vector< vector< Vec3i > > all_points(true_points_group.size()); diff --git a/modules/objdetect/test/test_boarddetection.cpp b/modules/objdetect/test/test_boarddetection.cpp index 0c99e6de61..1fa6e11994 100644 --- a/modules/objdetect/test/test_boarddetection.cpp +++ b/modules/objdetect/test/test_boarddetection.cpp @@ -325,5 +325,51 @@ TEST(CV_ArucoDictionary, extendDictionary) { ASSERT_EQ(custom_dictionary.bytesList.rows, 150); ASSERT_EQ(cv::norm(custom_dictionary.bytesList, base_dictionary.bytesList.rowRange(0, 150)), 0.); } +TEST(CV_ArucoBoardGenerateImage_RotationTest, HandlesRotatedMarkersWithoutBoundingBoxError) +{ + using namespace cv; + using namespace cv::aruco; + Dictionary dict = getPredefinedDictionary(DICT_4X4_50); + DetectorParameters detectorParams; + ArucoDetector detector(dict, detectorParams); + std::vector angles = {0.0f, 45.0f, 90.0f, 135.0f}; + for (auto angle_deg : angles) + { + float angle_rad = angle_deg * static_cast(CV_PI) / 180.0f; + float c = cos(angle_rad); + float s = sin(angle_rad); + std::vector markerCorners(4); + markerCorners[0] = Point3f(0.f, 0.f, 0.f); + markerCorners[1] = Point3f(1.f, 0.f, 0.f); + markerCorners[2] = Point3f(1.f, 1.f, 0.f); + markerCorners[3] = Point3f(0.f, 1.f, 0.f); + for (auto &p : markerCorners) + { + float xNew = p.x * c - p.y * s; + float yNew = p.x * s + p.y * c; + p.x = xNew; + p.y = yNew; + } + std::vector> allObjPoints{markerCorners}; + std::vector ids{0}; + Board board(allObjPoints, dict, ids); + float markerSize = 1.0f; + float rotatedSize = markerSize * std::sqrt(2.0f); + int borderBits = 1; + int marginSize = 20; + int sidePixels = static_cast((rotatedSize + 2.0f * borderBits) * 500) + 2 * marginSize; + Mat outImg; + Size outSize(sidePixels, sidePixels); + ASSERT_NO_THROW(board.generateImage(outSize, outImg, marginSize, borderBits)) + << "board.generateImage() threw an exception at angle " << angle_deg; + std::vector detectedIds; + std::vector> detectedCorners; + detector.detectMarkers(outImg, detectedCorners, detectedIds); + ASSERT_EQ(detectedIds.size(), (size_t)1) + << "Failed to detect single marker at angle: " << angle_deg; + EXPECT_EQ(detectedIds[0], 0) + << "Marker ID mismatch at angle: " << angle_deg; + } +} }} // namespace diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index 9f1ea7a800..3830439765 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -528,7 +528,7 @@ TEST(Objdetect_QRCode_decode, decode_regression_21929) Mat qrImg; QRCodeEncoder::Params params; params.version = 8; // 49x49 - Ptr qrcode_enc = cv::QRCodeEncoder::create(params);; + Ptr qrcode_enc = cv::QRCodeEncoder::create(params); qrcode_enc->encode(expect_msg, qrImg); Mat src; @@ -552,7 +552,7 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25) Mat qrImg; QRCodeEncoder::Params params; params.version = 25; // 117x117 - Ptr qrcode_enc = cv::QRCodeEncoder::create(params);; + Ptr qrcode_enc = cv::QRCodeEncoder::create(params); qrcode_enc->encode(expect_msg, qrImg); Mat src; diff --git a/modules/photo/include/opencv2/photo.hpp b/modules/photo/include/opencv2/photo.hpp index 4e0256b97e..7c93482226 100644 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@ -708,16 +708,56 @@ CV_EXPORTS_W void decolor( InputArray src, OutputArray grayscale, OutputArray co //! @{ -//! seamlessClone algorithm flags -enum +//! Flags for the seamlessClone algorithm +enum SeamlessCloneFlags { - /** The power of the method is fully expressed when inserting objects with complex outlines into a new background*/ + /** + @brief Normal seamless cloning. + This method is ideal for inserting objects with complex outlines into a new background. + It preserves the original appearance and lighting of the inserted object, ensuring a natural blend. + */ NORMAL_CLONE = 1, - /** The classic method, color-based selection and alpha masking might be time consuming and often leaves an undesirable - halo. Seamless cloning, even averaged with the original image, is not effective. Mixed seamless cloning based on a loose selection proves effective.*/ - MIXED_CLONE = 2, - /** Monochrome transfer allows the user to easily replace certain features of one object by alternative features.*/ - MONOCHROME_TRANSFER = 3}; + + /** + @brief Mixed seamless cloning. + This method addresses cases where simple color-based selection or alpha masking is time-consuming + and may result in undesirable halos. By combining structure from the source and texture from the + destination, mixed seamless cloning is highly effective, even with loosely defined selections. + */ + MIXED_CLONE = 2, + + /** + @brief Monochrome transfer cloning. + This method allows users to replace specific features of an object, such as grayscale textures + or patterns, with alternative features. It is particularly useful for artistic effects or + targeted object modifications. + */ + MONOCHROME_TRANSFER = 3, + + /** + @brief Enhanced normal seamless cloning. + Similar to `NORMAL_CLONE`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + NORMAL_CLONE_WIDE = 9, + + /** + @brief Enhanced mixed seamless cloning. + Similar to `MIXED_CLONE`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + MIXED_CLONE_WIDE = 10, + + /** + @brief Enhanced monochrome transfer cloning. + Similar to `MONOCHROME_TRANSFER`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + MONOCHROME_TRANSFER_WIDE = 11 +}; /** @example samples/cpp/tutorial_code/photo/seamless_cloning/cloning_demo.cpp @@ -726,18 +766,19 @@ An example using seamlessClone function /** @example samples/cpp/snippets/cloning_demo.cpp An example using illuminationChange, colorChange, seamlessClone, textureFlattening functions */ -/** @brief Image editing tasks concern either global changes (color/intensity corrections, filters, -deformations) or local changes concerned to a selection. Here we are interested in achieving local -changes, ones that are restricted to a region manually selected (ROI), in a seamless and effortless -manner. The extent of the changes ranges from slight distortions to complete replacement by novel -content @cite PM03 . +/** @brief Performs seamless cloning to blend a region from a source image into a destination image. +This function is designed for local image editing, allowing changes restricted to a region +(manually selected as the ROI) to be applied effortlessly and seamlessly. These changes can +range from slight distortions to complete replacement by novel content @cite PM03. -@param src Input 8-bit 3-channel image. -@param dst Input 8-bit 3-channel image. -@param mask Input 8-bit 1 or 3-channel image. -@param p Point in dst image where object is placed. -@param blend Output image with the same size and type as dst. -@param flags Cloning method that could be cv::NORMAL_CLONE, cv::MIXED_CLONE or cv::MONOCHROME_TRANSFER +@param src The source image (8-bit 3-channel), from which a region will be blended into the destination. +@param dst The destination image (8-bit 3-channel), where the src image will be blended. +@param mask A binary mask (8-bit, 1, 3, or 4-channel) specifying the region in the source image to blend. +Non-zero pixels indicate the region to be blended. If an empty Mat is provided, a mask with +all non-zero pixels is created internally. +@param p The point where the center of the src image is placed in the dst image. +@param blend The output image that stores the result of the seamless cloning. It has the same size and type as `dst`. +@param flags Flags that control the type of cloning method, can take values of `cv::SeamlessCloneFlags`. */ CV_EXPORTS_W void seamlessClone( InputArray src, InputArray dst, InputArray mask, Point p, OutputArray blend, int flags); diff --git a/modules/photo/misc/js/gen_dict.json b/modules/photo/misc/js/gen_dict.json index d8a6db100a..c03b081b34 100644 --- a/modules/photo/misc/js/gen_dict.json +++ b/modules/photo/misc/js/gen_dict.json @@ -6,6 +6,7 @@ "createMergeDebevec", "createMergeMertens", "createMergeRobertson", "createTonemapDrago", "createTonemapMantiuk", "createTonemapReinhard", "inpaint"], "CalibrateCRF": ["process"], + "AlignExposures": ["process"], "AlignMTB" : ["calculateShift", "shiftMat", "computeBitmaps", "getMaxBits", "setMaxBits", "getExcludeRange", "setExcludeRange", "getCut", "setCut"], "CalibrateDebevec" : ["getLambda", "setLambda", "getSamples", "setSamples", "getRandom", "setRandom"], diff --git a/modules/photo/perf/perf_cuda.cpp b/modules/photo/perf/perf_cuda.cpp index b179129962..c95aaa469c 100644 --- a/modules/photo/perf/perf_cuda.cpp +++ b/modules/photo/perf/perf_cuda.cpp @@ -71,7 +71,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_NonLocalMeans, const cv::Size size = GET_PARAM(0); const int depth = GET_PARAM(1); const int channels = GET_PARAM(2); - const int search_widow_size = GET_PARAM(3); + const int search_window_size = GET_PARAM(3); const int block_size = GET_PARAM(4); const float h = 10; @@ -87,7 +87,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_NonLocalMeans, const cv::cuda::GpuMat d_src(src); cv::cuda::GpuMat dst; - TEST_CYCLE() cv::cuda::nonLocalMeans(d_src, dst, h, search_widow_size, block_size, borderMode); + TEST_CYCLE() cv::cuda::nonLocalMeans(d_src, dst, h, search_window_size, block_size, borderMode); CUDA_SANITY_CHECK(dst); } @@ -114,7 +114,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_FastNonLocalMeans, const cv::Size size = GET_PARAM(0); const int depth = GET_PARAM(1); - const int search_widow_size = GET_PARAM(2); + const int search_window_size = GET_PARAM(2); const int block_size = GET_PARAM(3); const float h = 10; @@ -128,7 +128,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_FastNonLocalMeans, const cv::cuda::GpuMat d_src(src); cv::cuda::GpuMat dst; - TEST_CYCLE() cv::cuda::fastNlMeansDenoising(d_src, dst, h, search_widow_size, block_size); + TEST_CYCLE() cv::cuda::fastNlMeansDenoising(d_src, dst, h, search_window_size, block_size); CUDA_SANITY_CHECK(dst); } @@ -136,7 +136,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_FastNonLocalMeans, { cv::Mat dst; - TEST_CYCLE() cv::fastNlMeansDenoising(src, dst, h, block_size, search_widow_size); + TEST_CYCLE() cv::fastNlMeansDenoising(src, dst, h, block_size, search_window_size); CPU_SANITY_CHECK(dst); } @@ -157,7 +157,7 @@ PERF_TEST_P(Sz_Depth_WinSz_BlockSz, CUDA_FastNonLocalMeansColored, const cv::Size size = GET_PARAM(0); const int depth = GET_PARAM(1); - const int search_widow_size = GET_PARAM(2); + const int search_window_size = GET_PARAM(2); const int block_size = GET_PARAM(3); const float h = 10; @@ -171,7 +171,7 @@ PERF_TEST_P(Sz_Depth_WinSz_BlockSz, CUDA_FastNonLocalMeansColored, const cv::cuda::GpuMat d_src(src); cv::cuda::GpuMat dst; - TEST_CYCLE() cv::cuda::fastNlMeansDenoisingColored(d_src, dst, h, h, search_widow_size, block_size); + TEST_CYCLE() cv::cuda::fastNlMeansDenoisingColored(d_src, dst, h, h, search_window_size, block_size); CUDA_SANITY_CHECK(dst); } @@ -179,7 +179,7 @@ PERF_TEST_P(Sz_Depth_WinSz_BlockSz, CUDA_FastNonLocalMeansColored, { cv::Mat dst; - TEST_CYCLE() cv::fastNlMeansDenoisingColored(src, dst, h, h, block_size, search_widow_size); + TEST_CYCLE() cv::fastNlMeansDenoisingColored(src, dst, h, h, block_size, search_window_size); CPU_SANITY_CHECK(dst); } diff --git a/modules/photo/src/calibrate.cpp b/modules/photo/src/calibrate.cpp index 9614a1c9ae..0a399afae8 100644 --- a/modules/photo/src/calibrate.cpp +++ b/modules/photo/src/calibrate.cpp @@ -228,7 +228,7 @@ public: dst.create(LDR_SIZE, 1, CV_32FCC); Mat response = dst.getMat(); - response = linearResponse(3) / (LDR_SIZE / 2.0f); + response = linearResponse(3) / (static_cast(LDR_SIZE) / 2.0f); Mat card = Mat::zeros(LDR_SIZE, 1, CV_32FCC); for(size_t i = 0; i < images.size(); i++) { diff --git a/modules/photo/src/merge.cpp b/modules/photo/src/merge.cpp index 18050574ab..0707d27e9f 100644 --- a/modules/photo/src/merge.cpp +++ b/modules/photo/src/merge.cpp @@ -338,7 +338,7 @@ public: Mat response = input_response.getMat(); if(response.empty()) { - float middle = LDR_SIZE / 2.0f; + float middle = static_cast(LDR_SIZE) / 2.0f; response = linearResponse(channels) / middle; } CV_Assert(response.rows == LDR_SIZE && response.cols == 1 && diff --git a/modules/photo/src/seamless_cloning.cpp b/modules/photo/src/seamless_cloning.cpp index d21a3f21fd..f9bced841c 100644 --- a/modules/photo/src/seamless_cloning.cpp +++ b/modules/photo/src/seamless_cloning.cpp @@ -47,18 +47,17 @@ using namespace std; using namespace cv; -static Mat checkMask(InputArray _mask, Size size) +static Mat checkMask(InputArray mask, Size size) { - Mat mask = _mask.getMat(); Mat gray; - if (mask.channels() > 1) + if (mask.channels() == 3 || mask.channels() == 4) cvtColor(mask, gray, COLOR_BGRA2GRAY); else { if (mask.empty()) gray = Mat(size.height, size.width, CV_8UC1, Scalar(255)); else - mask.copyTo(gray); + return mask.getMat(); } return gray; @@ -68,9 +67,11 @@ void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point { CV_INSTRUMENT_REGION(); CV_Assert(!_src.empty()); + CV_Assert(!_dst.empty()); const Mat src = _src.getMat(); const Mat dest = _dst.getMat(); + Mat mask = checkMask(_mask, src.size()); dest.copyTo(_blend); Mat blend = _blend.getMat(); @@ -80,10 +81,18 @@ void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point Rect roi_s = boundingRect(mask); if (roi_s.empty()) return; - Rect roi_d(p.x - roi_s.width / 2, p.y - roi_s.height / 2, roi_s.width, roi_s.height); - Mat destinationROI = dest(roi_d).clone(); + int l_from_center = p.x - roi_s.width / 2; + int t_from_center = p.y - roi_s.height / 2; + if (flags >= NORMAL_CLONE_WIDE) + { + l_from_center = p.x - (mask.cols / 2 - roi_s.x); + t_from_center = p.y - (mask.rows / 2 - roi_s.y); + } + + Rect roi_d(l_from_center, t_from_center, roi_s.width, roi_s.height); + Mat destinationROI = dest(roi_d); Mat sourceROI = Mat::zeros(roi_s.height, roi_s.width, src.type()); src(roi_s).copyTo(sourceROI,mask(roi_s)); diff --git a/modules/photo/src/seamless_cloning_impl.cpp b/modules/photo/src/seamless_cloning_impl.cpp index 4b3258a1d9..377bdb8f8d 100644 --- a/modules/photo/src/seamless_cloning_impl.cpp +++ b/modules/photo/src/seamless_cloning_impl.cpp @@ -332,11 +332,13 @@ void Cloning::normalClone(const Mat &destination, const Mat &patch, Mat &binaryM switch(flag) { case NORMAL_CLONE: + case NORMAL_CLONE_WIDE: arrayProduct(patchGradientX, binaryMaskFloat, patchGradientX); arrayProduct(patchGradientY, binaryMaskFloat, patchGradientY); break; case MIXED_CLONE: + case MIXED_CLONE_WIDE: { AutoBuffer maskIndices(n_elem_in_line); for (int i = 0; i < n_elem_in_line; ++i) @@ -373,6 +375,7 @@ void Cloning::normalClone(const Mat &destination, const Mat &patch, Mat &binaryM break; case MONOCHROME_TRANSFER: + case MONOCHROME_TRANSFER_WIDE: Mat gray; cvtColor(patch, gray, COLOR_BGR2GRAY ); diff --git a/modules/photo/test/test_cloning.cpp b/modules/photo/test/test_cloning.cpp index e4d806a7f9..fa486b23c1 100644 --- a/modules/photo/test/test_cloning.cpp +++ b/modules/photo/test/test_cloning.cpp @@ -75,7 +75,7 @@ TEST(Photo_SeamlessClone_normal, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 1); + seamlessClone(source, destination, mask, p, result, NORMAL_CLONE); Mat reference = imread(reference_path); ASSERT_FALSE(reference.empty()) << "Could not load reference image " << reference_path; @@ -88,7 +88,7 @@ TEST(Photo_SeamlessClone_normal, regression) EXPECT_LE(errorL1, reference.total() * numerical_precision) << "size=" << reference.size(); mask = Scalar(0, 0, 0); - seamlessClone(source, destination, mask, p, result, 1); + seamlessClone(source, destination, mask, p, result, NORMAL_CLONE); reference = destination; errorINF = cvtest::norm(reference, result, NORM_INF); @@ -117,7 +117,7 @@ TEST(Photo_SeamlessClone_mixed, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 2); + seamlessClone(source, destination, mask, p, result, MIXED_CLONE); SAVE(result); @@ -150,7 +150,7 @@ TEST(Photo_SeamlessClone_featureExchange, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 3); + seamlessClone(source, destination, mask, p, result, MONOCHROME_TRANSFER); SAVE(result); diff --git a/modules/photo/test/test_hdr.cpp b/modules/photo/test/test_hdr.cpp index 10050abbcb..a26e83e49e 100644 --- a/modules/photo/test/test_hdr.cpp +++ b/modules/photo/test/test_hdr.cpp @@ -228,7 +228,7 @@ TEST(Photo_CalibrateDebevec, regression) double max; minMaxLoc(diff, NULL, &max); #if defined(__arm__) || defined(__aarch64__) - ASSERT_LT(max, 0.131); + ASSERT_LT(max, 0.2); #else ASSERT_LT(max, 0.1); #endif diff --git a/modules/python/package/setup.py b/modules/python/package/setup.py index 10a472c7a9..ed143b7bcf 100644 --- a/modules/python/package/setup.py +++ b/modules/python/package/setup.py @@ -66,6 +66,7 @@ def main(): "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: C++", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering", diff --git a/modules/python/python_loader.cmake b/modules/python/python_loader.cmake index 41dce57c18..dc16083c2a 100644 --- a/modules/python/python_loader.cmake +++ b/modules/python/python_loader.cmake @@ -139,6 +139,7 @@ if(${PYTHON}_VERSION_STRING VERSION_GREATER "3.6" AND PYTHON_DEFAULT_VERSION VER # halts on hard error. add_custom_command( TARGET copy_opencv_typing_stubs + POST_BUILD COMMAND ${PYTHON_DEFAULT_EXECUTABLE} ${PYTHON_SOURCE_DIR}/src2/copy_typings_stubs_on_success.py --stubs_dir ${OPENCV_PYTHON_BINDINGS_DIR}/cv2 --output_dir ${__loader_path}/cv2 diff --git a/modules/python/src2/typing_stubs_generation/nodes/type_node.py b/modules/python/src2/typing_stubs_generation/nodes/type_node.py index 6b8f2fa661..a1b7ce5b46 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/type_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/type_node.py @@ -179,8 +179,10 @@ class AnyTypeNode(TypeNode): class PrimitiveTypeNode(TypeNode): """Type node representing a primitive built-in types e.g. int, float, str. """ - def __init__(self, ctype_name: str, typename: Optional[str] = None) -> None: - super().__init__(ctype_name) + def __init__(self, ctype_name: str, + typename: Optional[str] = None, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(ctype_name, required_modules) self._typename = typename if typename is not None else ctype_name @property @@ -188,28 +190,32 @@ class PrimitiveTypeNode(TypeNode): return self._typename @classmethod - def int_(cls, ctype_name: Optional[str] = None): + def int_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "int" - return PrimitiveTypeNode(ctype_name, typename="int") + return PrimitiveTypeNode(ctype_name, typename="int", required_modules=required_modules) @classmethod - def float_(cls, ctype_name: Optional[str] = None): + def float_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "float" - return PrimitiveTypeNode(ctype_name, typename="float") + return PrimitiveTypeNode(ctype_name, typename="float", required_modules=required_modules) @classmethod - def bool_(cls, ctype_name: Optional[str] = None): + def bool_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "bool" - return PrimitiveTypeNode(ctype_name, typename="bool") + return PrimitiveTypeNode(ctype_name, typename="bool", required_modules=required_modules) @classmethod - def str_(cls, ctype_name: Optional[str] = None): + def str_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "string" - return PrimitiveTypeNode(ctype_name, "str") + return PrimitiveTypeNode(ctype_name, "str", required_modules=required_modules) class AliasRefTypeNode(TypeNode): diff --git a/modules/python/src2/typing_stubs_generation/predefined_types.py b/modules/python/src2/typing_stubs_generation/predefined_types.py index 8a5af1e488..5a15fafe61 100644 --- a/modules/python/src2/typing_stubs_generation/predefined_types.py +++ b/modules/python/src2/typing_stubs_generation/predefined_types.py @@ -136,18 +136,23 @@ _PREDEFINED_TYPES = ( dtype="numpy.float64"), NDArrayTypeNode("vector", dtype="numpy.uint8"), NDArrayTypeNode("vector_uchar", dtype="numpy.uint8"), - AliasTypeNode.class_("LayerId", "DictValue"), + + # DNN, optional + AliasTypeNode.class_("LayerId", "DictValue", required_modules=("dnn",)), AliasTypeNode.dict_("LayerParams", key_type=PrimitiveTypeNode.str_(), value_type=UnionTypeNode("DictValue", items=( PrimitiveTypeNode.int_(), PrimitiveTypeNode.float_(), PrimitiveTypeNode.str_()) - )), - PrimitiveTypeNode.int_("cvflann_flann_distance_t"), - PrimitiveTypeNode.int_("flann_flann_distance_t"), - PrimitiveTypeNode.int_("cvflann_flann_algorithm_t"), - PrimitiveTypeNode.int_("flann_flann_algorithm_t"), + ), + required_modules=("dnn",)), + + # Flann, optional + PrimitiveTypeNode.int_("cvflann_flann_distance_t", required_modules=("flann",)), + PrimitiveTypeNode.int_("flann_flann_distance_t", required_modules=("flann",)), + PrimitiveTypeNode.int_("cvflann_flann_algorithm_t", required_modules=("flann",)), + PrimitiveTypeNode.int_("flann_flann_algorithm_t", required_modules=("flann",)), AliasTypeNode.dict_("flann_IndexParams", key_type=PrimitiveTypeNode.str_(), value_type=UnionTypeNode("flann_IndexParams::value", items=( @@ -155,7 +160,9 @@ _PREDEFINED_TYPES = ( PrimitiveTypeNode.int_(), PrimitiveTypeNode.float_(), PrimitiveTypeNode.str_()) - ), export_name="IndexParams"), + ), + export_name="IndexParams", + required_modules=("flann",)), AliasTypeNode.dict_("flann_SearchParams", key_type=PrimitiveTypeNode.str_(), value_type=UnionTypeNode("flann_IndexParams::value", items=( @@ -163,17 +170,29 @@ _PREDEFINED_TYPES = ( PrimitiveTypeNode.int_(), PrimitiveTypeNode.float_(), PrimitiveTypeNode.str_()) - ), export_name="SearchParams"), - AliasTypeNode.dict_("map_string_and_string", PrimitiveTypeNode.str_("map_string_and_string::key"), - PrimitiveTypeNode.str_("map_string_and_string::value")), - AliasTypeNode.dict_("map_string_and_int", PrimitiveTypeNode.str_("map_string_and_int::key"), - PrimitiveTypeNode.int_("map_string_and_int::value")), - AliasTypeNode.dict_("map_string_and_vector_size_t", PrimitiveTypeNode.str_("map_string_and_vector_size_t::key"), - SequenceTypeNode("map_string_and_vector_size_t::value", PrimitiveTypeNode.int_("size_t"))), - AliasTypeNode.dict_("map_string_and_vector_float", PrimitiveTypeNode.str_("map_string_and_vector_float::key"), - SequenceTypeNode("map_string_and_vector_float::value", PrimitiveTypeNode.float_())), - AliasTypeNode.dict_("map_int_and_double", PrimitiveTypeNode.int_("map_int_and_double::key"), - PrimitiveTypeNode.float_("map_int_and_double::value")), + ), + export_name="SearchParams", + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_string", + PrimitiveTypeNode.str_("map_string_and_string::key"), + PrimitiveTypeNode.str_("map_string_and_string::value"), + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_int", + PrimitiveTypeNode.str_("map_string_and_int::key"), + PrimitiveTypeNode.int_("map_string_and_int::value"), + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_vector_size_t", + PrimitiveTypeNode.str_("map_string_and_vector_size_t::key"), + SequenceTypeNode("map_string_and_vector_size_t::value", PrimitiveTypeNode.int_("size_t")), + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_vector_float", + PrimitiveTypeNode.str_("map_string_and_vector_float::key"), + SequenceTypeNode("map_string_and_vector_float::value", PrimitiveTypeNode.float_()), + required_modules=("flann",)), + AliasTypeNode.dict_("map_int_and_double", + PrimitiveTypeNode.int_("map_int_and_double::key"), + PrimitiveTypeNode.float_("map_int_and_double::value"), + required_modules=("flann",)), # G-API from opencv_contrib AliasTypeNode.union_("GProtoArg", diff --git a/modules/stereo/test/test_stereomatching.cpp b/modules/stereo/test/test_stereomatching.cpp index c17d92292a..ad230509df 100644 --- a/modules/stereo/test/test_stereomatching.cpp +++ b/modules/stereo/test/test_stereomatching.cpp @@ -789,7 +789,7 @@ protected: calcROI = getValidDisparityROI(cROI, cROI, params.mindisp, params.ndisp, params.winSize); bm->compute( leftImg, rightImg, tempDisp ); - tempDisp.convertTo(leftDisp, CV_32F, 1./StereoMatcher::DISP_SCALE); + tempDisp.convertTo(leftDisp, CV_32F, 1./static_cast(StereoMatcher::DISP_SCALE)); //check for fixed-type disparity data type Mat_ fixedFloatDisp; @@ -802,7 +802,7 @@ protected: for (int x = 0; x < leftDisp.cols; x++) { if (leftDisp.at(y, x) < params.mindisp) - leftDisp.at(y, x) = -1./StereoMatcher::DISP_SCALE; // treat disparity < mindisp as no disparity + leftDisp.at(y, x) = -1./static_cast(StereoMatcher::DISP_SCALE); // treat disparity < mindisp as no disparity } return params.winSize/2; diff --git a/modules/ts/misc/summary.py b/modules/ts/misc/summary.py index 693a161b10..d0da1e60ed 100755 --- a/modules/ts/misc/summary.py +++ b/modules/ts/misc/summary.py @@ -36,8 +36,8 @@ import testlog_parser, sys, os, xml, glob, re from table_formatter import * from optparse import OptionParser -numeric_re = re.compile("(\d+)") -cvtype_re = re.compile("(8U|8S|16U|16S|32S|32F|64F)C(\d{1,3})") +numeric_re = re.compile(r"(\d+)") +cvtype_re = re.compile(r"(8U|8S|16U|16S|32S|32F|64F)C(\d{1,3})") cvtypes = { '8U': 0, '8S': 1, '16U': 2, '16S': 3, '32S': 4, '32F': 5, '64F': 6 } convert = lambda text: int(text) if text.isdigit() else text diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index fea1068ad5..877c0dc10e 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -316,14 +316,14 @@ enum { CAP_PROP_OPENNI_OUTPUT_MODE = 100, #pragma warning( disable: 5054 ) #endif //! OpenNI shortcuts -enum { CAP_OPENNI_IMAGE_GENERATOR_PRESENT = CAP_OPENNI_IMAGE_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT, - CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE = CAP_OPENNI_IMAGE_GENERATOR + CAP_PROP_OPENNI_OUTPUT_MODE, - CAP_OPENNI_DEPTH_GENERATOR_PRESENT = CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT, - CAP_OPENNI_DEPTH_GENERATOR_BASELINE = CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_BASELINE, - CAP_OPENNI_DEPTH_GENERATOR_FOCAL_LENGTH = CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_FOCAL_LENGTH, - CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION = CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_REGISTRATION, - CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION_ON = CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION, - CAP_OPENNI_IR_GENERATOR_PRESENT = CAP_OPENNI_IR_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT, +enum { CAP_OPENNI_IMAGE_GENERATOR_PRESENT = +CAP_OPENNI_IMAGE_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT, + CAP_OPENNI_IMAGE_GENERATOR_OUTPUT_MODE = +CAP_OPENNI_IMAGE_GENERATOR + CAP_PROP_OPENNI_OUTPUT_MODE, + CAP_OPENNI_DEPTH_GENERATOR_PRESENT = +CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT, + CAP_OPENNI_DEPTH_GENERATOR_BASELINE = +CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_BASELINE, + CAP_OPENNI_DEPTH_GENERATOR_FOCAL_LENGTH = +CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_FOCAL_LENGTH, + CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION = +CAP_OPENNI_DEPTH_GENERATOR + CAP_PROP_OPENNI_REGISTRATION, + CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION_ON = CAP_OPENNI_DEPTH_GENERATOR_REGISTRATION, + CAP_OPENNI_IR_GENERATOR_PRESENT = +CAP_OPENNI_IR_GENERATOR + CAP_PROP_OPENNI_GENERATOR_PRESENT }; #ifdef _MSC_VER #pragma warning( pop ) @@ -573,6 +573,18 @@ enum { CAP_PROP_ARAVIS_AUTOTRIGGER = 600 //!< Autom //! @} ARAVIS + +/** @name Android + @{ +*/ + +//! Properties of cameras available through NDK Camera API backend +enum { CAP_PROP_ANDROID_DEVICE_TORCH = 8001, + }; + +//! @} Android + + /** @name AVFoundation framework for iOS @{ */ @@ -700,6 +712,25 @@ enum VideoCaptureOBSensorProperties{ //! @} videoio_flags_others +/** @brief Read data stream interface + */ +class CV_EXPORTS_W IStreamReader +{ +public: + virtual ~IStreamReader(); + + /** @brief Read bytes from stream */ + virtual long long read(char* buffer, long long size) = 0; + + /** @brief Sets the stream position + * + * @param offset Seek offset + * @param origin SEEK_SET / SEEK_END / SEEK_CUR + * + * @see fseek + */ + virtual long long seek(long long offset, int origin) = 0; +}; class IVideoCapture; //! @cond IGNORED @@ -775,6 +806,14 @@ public: */ CV_WRAP explicit VideoCapture(int index, int apiPreference, const std::vector& params); + /** @overload + @brief Opens a video using data stream. + + The `params` parameter allows to specify extra parameters encoded as pairs `(paramId_1, paramValue_1, paramId_2, paramValue_2, ...)`. + See cv::VideoCaptureProperties + */ + CV_WRAP VideoCapture(const Ptr& source, int apiPreference, const std::vector& params); + /** @brief Default destructor The method first calls VideoCapture::release to close the already opened file or camera. @@ -829,6 +868,19 @@ public: */ CV_WRAP virtual bool open(int index, int apiPreference, const std::vector& params); + /** @brief Opens a video using data stream. + + @overload + + The `params` parameter allows to specify extra parameters encoded as pairs `(paramId_1, paramValue_1, paramId_2, paramValue_2, ...)`. + See cv::VideoCaptureProperties + + @return `true` if the file has been successfully opened + + The method first calls VideoCapture::release to close the already opened file or camera. + */ + CV_WRAP virtual bool open(const Ptr& source, int apiPreference, const std::vector& params); + /** @brief Returns true if video capturing has been initialized already. If the previous call to VideoCapture constructor or VideoCapture::open() succeeded, the method returns diff --git a/modules/videoio/include/opencv2/videoio/registry.hpp b/modules/videoio/include/opencv2/videoio/registry.hpp index cf72247b3f..47e94b1909 100644 --- a/modules/videoio/include/opencv2/videoio/registry.hpp +++ b/modules/videoio/include/opencv2/videoio/registry.hpp @@ -35,6 +35,9 @@ CV_EXPORTS_W std::vector getCameraBackends(); /** @brief Returns list of available backends which works via `cv::VideoCapture(filename)` */ CV_EXPORTS_W std::vector getStreamBackends(); +/** @brief Returns list of available backends which works via `cv::VideoCapture(buffer)` */ +CV_EXPORTS_W std::vector getStreamBufferedBackends(); + /** @brief Returns list of available backends which works via `cv::VideoWriter()` */ CV_EXPORTS_W std::vector getWriterBackends(); @@ -58,6 +61,13 @@ CV_EXPORTS_W std::string getStreamBackendPluginVersion( CV_OUT int& version_API ); +/** @brief Returns description and ABI/API version of videoio plugin's buffer capture interface */ +CV_EXPORTS_W std::string getStreamBufferedBackendPluginVersion( + VideoCaptureAPIs api, + CV_OUT int& version_ABI, + CV_OUT int& version_API +); + /** @brief Returns description and ABI/API version of videoio plugin's writer interface */ CV_EXPORTS_W std::string getWriterBackendPluginVersion( VideoCaptureAPIs api, diff --git a/modules/videoio/include/opencv2/videoio/utils.private.hpp b/modules/videoio/include/opencv2/videoio/utils.private.hpp index e331aaf2ac..0fa6d285b0 100644 --- a/modules/videoio/include/opencv2/videoio/utils.private.hpp +++ b/modules/videoio/include/opencv2/videoio/utils.private.hpp @@ -10,6 +10,37 @@ namespace cv { CV_EXPORTS std::string icvExtractPattern(const std::string& filename, unsigned *offset); + +class PluginStreamReader : public IStreamReader +{ +public: + PluginStreamReader(void* _opaque, + long long (*_read)(void* opaque, char* buffer, long long size), + long long (*_seek)(void* opaque, long long offset, int way)) + { + opaque = _opaque; + readCallback = _read; + seekCallback = _seek; + } + + virtual ~PluginStreamReader() {} + + long long read(char* buffer, long long size) override + { + return readCallback(opaque, buffer, size); + } + + long long seek(long long offset, int way) override + { + return seekCallback(opaque, offset, way); + } + +private: + void* opaque; + long long (*readCallback)(void* opaque, char* buffer, long long size); + long long (*seekCallback)(void* opaque, long long offset, int way); +}; + } #endif // OPENCV_VIDEOIO_UTILS_PRIVATE_HPP diff --git a/modules/videoio/misc/objc/gen_dict.json b/modules/videoio/misc/objc/gen_dict.json index 70dc844f0c..f42211a949 100644 --- a/modules/videoio/misc/objc/gen_dict.json +++ b/modules/videoio/misc/objc/gen_dict.json @@ -14,7 +14,8 @@ "func_arg_fix" : { "VideoCapture" : { "(BOOL)open:(int)index apiPreference:(int)apiPreference" : { "open" : {"name" : "openWithIndex"} }, - "(BOOL)open:(int)index apiPreference:(int)apiPreference params:(IntVector*)params" : { "open" : {"name" : "openWithIndexAndParameters"} } + "(BOOL)open:(int)index apiPreference:(int)apiPreference params:(IntVector*)params" : { "open" : {"name" : "openWithIndexAndParameters"} }, + "(BOOL)open:(IStreamReader*)source apiPreference:(int)apiPreference params:(IntVector*)params" : { "open" : {"name" : "openWithStreamReader"} } } } } diff --git a/modules/videoio/misc/python/pyopencv_videoio.hpp b/modules/videoio/misc/python/pyopencv_videoio.hpp index e729c8631f..ab26e00c82 100644 --- a/modules/videoio/misc/python/pyopencv_videoio.hpp +++ b/modules/videoio/misc/python/pyopencv_videoio.hpp @@ -31,4 +31,114 @@ template<> bool pyopencv_to(PyObject* obj, cv::VideoCapture& stream, const ArgIn return true; } +class PythonStreamReader : public cv::IStreamReader +{ +public: + PythonStreamReader(PyObject* _obj = nullptr) : obj(_obj) + { + if (obj) + Py_INCREF(obj); + } + + ~PythonStreamReader() + { + if (obj) + Py_DECREF(obj); + } + + long long read(char* buffer, long long size) CV_OVERRIDE + { + if (!obj) + return 0; + + PyObject* ioBase = reinterpret_cast(obj); + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject* py_size = pyopencv_from(static_cast(size)); + + PyObject* res = PyObject_CallMethodObjArgs(ioBase, PyString_FromString("read"), py_size, NULL); + bool hasPyReadError = PyErr_Occurred() != nullptr; + char* src = PyBytes_AsString(res); + size_t len = static_cast(PyBytes_Size(res)); + bool hasPyBytesError = PyErr_Occurred() != nullptr; + if (src && len <= static_cast(size)) + { + std::memcpy(buffer, src, len); + } + Py_DECREF(res); + Py_DECREF(py_size); + + PyGILState_Release(gstate); + + if (hasPyReadError) + CV_Error(cv::Error::StsError, "Python .read() call error"); + if (hasPyBytesError) + CV_Error(cv::Error::StsError, "Python buffer access error"); + + CV_CheckLE(len, static_cast(size), "Stream chunk size should be less or equal than requested size"); + + return len; + } + + long long seek(long long offset, int way) CV_OVERRIDE + { + if (!obj) + return 0; + + PyObject* ioBase = reinterpret_cast(obj); + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject* py_offset = pyopencv_from(static_cast(offset)); + PyObject* py_whence = pyopencv_from(way); + + PyObject* res = PyObject_CallMethodObjArgs(ioBase, PyString_FromString("seek"), py_offset, py_whence, NULL); + bool hasPySeekError = PyErr_Occurred() != nullptr; + long long pos = PyLong_AsLongLong(res); + bool hasPyConvertError = PyErr_Occurred() != nullptr; + Py_DECREF(res); + Py_DECREF(py_offset); + Py_DECREF(py_whence); + + PyGILState_Release(gstate); + + if (hasPySeekError) + CV_Error(cv::Error::StsError, "Python .seek() call error"); + if (hasPyConvertError) + CV_Error(cv::Error::StsError, "Python .seek() result => long long conversion error"); + return pos; + } + +private: + PyObject* obj; +}; + +template<> +bool pyopencv_to(PyObject* obj, Ptr& p, const ArgInfo&) +{ + if (!obj) + return false; + + PyObject* ioModule = PyImport_ImportModule("io"); + PyObject* type = PyObject_GetAttrString(ioModule, "BufferedIOBase"); + Py_DECREF(ioModule); + bool isValidPyType = PyObject_IsInstance(obj, type) == 1; + Py_DECREF(type); + + if (!isValidPyType) + { + PyErr_SetString(PyExc_TypeError, "Input stream should be derived from io.BufferedIOBase"); + return false; + } + + if (!PyErr_Occurred()) { + p = makePtr(obj); + return true; + } + return false; +} + #endif // HAVE_OPENCV_VIDEOIO diff --git a/modules/videoio/misc/python/test/test_videoio.py b/modules/videoio/misc/python/test/test_videoio.py index 2bbfeecda0..181ade769d 100644 --- a/modules/videoio/misc/python/test/test_videoio.py +++ b/modules/videoio/misc/python/test/test_videoio.py @@ -3,6 +3,8 @@ from __future__ import print_function import numpy as np import cv2 as cv +import io +import sys from tests_common import NewOpenCVTests @@ -21,5 +23,69 @@ class Bindings(NewOpenCVTests): for backend in backends: self.check_name(cv.videoio_registry.getBackendName(backend)) + def test_capture_stream_file(self): + if sys.version_info[0] < 3: + raise self.skipTest('Python 3.x required') + + api_pref = None + for backend in cv.videoio_registry.getStreamBufferedBackends(): + if not cv.videoio_registry.hasBackend(backend): + continue + if not cv.videoio_registry.isBackendBuiltIn(backend): + _, abi, api = cv.videoio_registry.getStreamBufferedBackendPluginVersion(backend) + if (abi < 1 or (abi == 1 and api < 2)): + continue + api_pref = backend + break + + if not api_pref: + raise self.skipTest("No available backends") + + with open(self.find_file("cv/video/768x576.avi"), "rb") as f: + cap = cv.VideoCapture(f, api_pref, []) + self.assertTrue(cap.isOpened()) + hasFrame, frame = cap.read() + self.assertTrue(hasFrame) + self.assertEqual(frame.shape, (576, 768, 3)) + + def test_capture_stream_buffer(self): + if sys.version_info[0] < 3: + raise self.skipTest('Python 3.x required') + + api_pref = None + for backend in cv.videoio_registry.getStreamBufferedBackends(): + if not cv.videoio_registry.hasBackend(backend): + continue + if not cv.videoio_registry.isBackendBuiltIn(backend): + _, abi, api = cv.videoio_registry.getStreamBufferedBackendPluginVersion(backend) + if (abi < 1 or (abi == 1 and api < 2)): + continue + api_pref = backend + break + + if not api_pref: + raise self.skipTest("No available backends") + + class BufferStream(io.BufferedIOBase): + def __init__(self, filepath): + self.f = open(filepath, "rb") + + def read(self, size=-1): + return self.f.read(size) + + def seek(self, offset, whence): + return self.f.seek(offset, whence) + + def __del__(self): + self.f.close() + + stream = BufferStream(self.find_file("cv/video/768x576.avi")) + + cap = cv.VideoCapture(stream, api_pref, []) + self.assertTrue(cap.isOpened()) + hasFrame, frame = cap.read() + self.assertTrue(hasFrame) + self.assertEqual(frame.shape, (576, 768, 3)) + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/videoio/src/backend.hpp b/modules/videoio/src/backend.hpp index 2a95ec05aa..b75cc51ea8 100644 --- a/modules/videoio/src/backend.hpp +++ b/modules/videoio/src/backend.hpp @@ -18,6 +18,7 @@ public: virtual ~IBackend() {} virtual Ptr createCapture(int camera, const VideoCaptureParameters& params) const = 0; virtual Ptr createCapture(const std::string &filename, const VideoCaptureParameters& params) const = 0; + virtual Ptr createCapture(const Ptr&stream, const VideoCaptureParameters& params) const = 0; virtual Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const = 0; }; @@ -34,15 +35,19 @@ public: typedef Ptr (*FN_createCaptureFile)(const std::string & filename); typedef Ptr (*FN_createCaptureCamera)(int camera); +typedef Ptr (*FN_createCaptureStream)(const Ptr& stream); typedef Ptr (*FN_createCaptureFileWithParams)(const std::string & filename, const VideoCaptureParameters& params); typedef Ptr (*FN_createCaptureCameraWithParams)(int camera, const VideoCaptureParameters& params); +typedef Ptr (*FN_createCaptureStreamWithParams)(const Ptr& stream, const VideoCaptureParameters& params); typedef Ptr (*FN_createWriter)(const std::string& filename, int fourcc, double fps, const Size& sz, const VideoWriterParameters& params); Ptr createBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, + FN_createCaptureStream createCaptureStream, FN_createWriter createWriter); Ptr createBackendFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, + FN_createCaptureStreamWithParams createCaptureStream, FN_createWriter createWriter); Ptr createPluginBackendFactory(VideoCaptureAPIs id, const char* baseName); diff --git a/modules/videoio/src/backend_plugin.cpp b/modules/videoio/src/backend_plugin.cpp index 8cc5130e7f..500154d238 100644 --- a/modules/videoio/src/backend_plugin.cpp +++ b/modules/videoio/src/backend_plugin.cpp @@ -208,6 +208,7 @@ public: Ptr createCapture(int camera, const VideoCaptureParameters& params) const CV_OVERRIDE; Ptr createCapture(const std::string &filename) const; Ptr createCapture(const std::string &filename, const VideoCaptureParameters& params) const CV_OVERRIDE; + Ptr createCapture(const Ptr& stream, const VideoCaptureParameters& params) const CV_OVERRIDE; Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const CV_OVERRIDE; @@ -447,16 +448,52 @@ class PluginCapture : public cv::IVideoCapture { const OpenCV_VideoIO_Capture_Plugin_API* plugin_api_; CvPluginCapture capture_; + Ptr readStream_; public: static Ptr create(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, - const std::string &filename, int camera, const VideoCaptureParameters& params) + const std::string &filename, const Ptr& stream, int camera, const VideoCaptureParameters& params) { CV_Assert(plugin_api); CV_Assert(plugin_api->v0.Capture_release); CvPluginCapture capture = NULL; + if (stream && plugin_api->api_header.api_version >= 2 && plugin_api->v2.Capture_open_stream) + { + std::vector vint_params = params.getIntVector(); + int* c_params = vint_params.data(); + unsigned n_params = (unsigned)(vint_params.size() / 2); + + if (CV_ERROR_OK == plugin_api->v2.Capture_open_stream( + stream.get(), + [](void* opaque, char* buffer, long long size) -> long long { + CV_LOG_VERBOSE(NULL, 0, "IStreamReader::read(" << size << ")..."); + auto is = reinterpret_cast(opaque); + try { + return is->read(buffer, size); + } catch (...) { + CV_LOG_WARNING(NULL, "IStreamReader::read(" << size << ") failed"); + return 0; + } + }, + [](void* opaque, long long offset, int way) -> long long { + CV_LOG_VERBOSE(NULL, 0, "IStreamReader::seek(" << offset << ", way=" << way << ")..."); + auto is = reinterpret_cast(opaque); + try { + return is->seek(offset, way); + } catch (...) { + CV_LOG_WARNING(NULL, "IStreamReader::seek(" << offset << ", way=" << way << ") failed"); + return -1; + } + }, c_params, n_params, &capture)) + { + CV_Assert(capture); + return makePtr(plugin_api, capture, stream); + } + } + else if (stream) + return Ptr(); if (plugin_api->api_header.api_version >= 1 && plugin_api->v1.Capture_open_with_params) { @@ -488,8 +525,8 @@ public: return Ptr(); } - PluginCapture(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, CvPluginCapture capture) - : plugin_api_(plugin_api), capture_(capture) + PluginCapture(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, CvPluginCapture capture, const Ptr& readStream = Ptr()) + : plugin_api_(plugin_api), capture_(capture), readStream_(readStream) { CV_Assert(plugin_api_); CV_Assert(capture_); } @@ -667,7 +704,7 @@ Ptr PluginBackend::createCapture(int camera, const VideoCapturePa try { if (capture_api_) - return PluginCapture::create(capture_api_, std::string(), camera, params); //.staticCast(); + return PluginCapture::create(capture_api_, std::string(), nullptr, camera, params); //.staticCast(); if (plugin_api_) { Ptr cap = legacy::PluginCapture::create(plugin_api_, std::string(), camera); //.staticCast(); @@ -691,7 +728,7 @@ Ptr PluginBackend::createCapture(const std::string &filename, con try { if (capture_api_) - return PluginCapture::create(capture_api_, filename, 0, params); //.staticCast(); + return PluginCapture::create(capture_api_, filename, nullptr, 0, params); //.staticCast(); if (plugin_api_) { Ptr cap = legacy::PluginCapture::create(plugin_api_, filename, 0); //.staticCast(); @@ -710,6 +747,25 @@ Ptr PluginBackend::createCapture(const std::string &filename, con return Ptr(); } +Ptr PluginBackend::createCapture(const Ptr& stream, const VideoCaptureParameters& params) const +{ + try + { + if (capture_api_) + return PluginCapture::create(capture_api_, std::string(), stream, 0, params); //.staticCast(); + if (plugin_api_) + { + CV_Error(Error::StsNotImplemented, "Legacy plugin API for stream capture"); + } + } + catch (...) + { + CV_LOG_DEBUG(NULL, "Video I/O: can't open stream capture"); + throw; + } + return Ptr(); +} + Ptr PluginBackend::createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const { diff --git a/modules/videoio/src/backend_static.cpp b/modules/videoio/src/backend_static.cpp index 3001906acf..511aff9ffa 100644 --- a/modules/videoio/src/backend_static.cpp +++ b/modules/videoio/src/backend_static.cpp @@ -36,10 +36,11 @@ class StaticBackend: public IBackend public: FN_createCaptureFile fn_createCaptureFile_; FN_createCaptureCamera fn_createCaptureCamera_; + FN_createCaptureStream fn_createCaptureStream_; FN_createWriter fn_createWriter_; - StaticBackend(FN_createCaptureFile fn_createCaptureFile, FN_createCaptureCamera fn_createCaptureCamera, FN_createWriter fn_createWriter) - : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createWriter_(fn_createWriter) + StaticBackend(FN_createCaptureFile fn_createCaptureFile, FN_createCaptureCamera fn_createCaptureCamera, FN_createCaptureStream fn_createCaptureStream, FN_createWriter fn_createWriter) + : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createCaptureStream_(fn_createCaptureStream), fn_createWriter_(fn_createWriter) { // nothing } @@ -72,6 +73,19 @@ public: } return Ptr(); } + Ptr createCapture(const Ptr& stream, const VideoCaptureParameters& params) const CV_OVERRIDE + { + if (fn_createCaptureStream_) + { + Ptr cap = fn_createCaptureStream_(stream); + if (cap && !params.empty()) + { + applyParametersFallback(cap, params); + } + return cap; + } + return Ptr(); + } Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const CV_OVERRIDE { @@ -87,8 +101,8 @@ protected: Ptr backend; public: - StaticBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, FN_createWriter createWriter) - : backend(makePtr(createCaptureFile, createCaptureCamera, createWriter)) + StaticBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, FN_createCaptureStream createCaptureStream, FN_createWriter createWriter) + : backend(makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter)) { // nothing } @@ -106,9 +120,10 @@ public: Ptr createBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, + FN_createCaptureStream createCaptureStream, FN_createWriter createWriter) { - return makePtr(createCaptureFile, createCaptureCamera, createWriter).staticCast(); + return makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter).staticCast(); } @@ -118,10 +133,11 @@ class StaticBackendWithParams: public IBackend public: FN_createCaptureFileWithParams fn_createCaptureFile_; FN_createCaptureCameraWithParams fn_createCaptureCamera_; + FN_createCaptureStreamWithParams fn_createCaptureStream_; FN_createWriter fn_createWriter_; - StaticBackendWithParams(FN_createCaptureFileWithParams fn_createCaptureFile, FN_createCaptureCameraWithParams fn_createCaptureCamera, FN_createWriter fn_createWriter) - : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createWriter_(fn_createWriter) + StaticBackendWithParams(FN_createCaptureFileWithParams fn_createCaptureFile, FN_createCaptureCameraWithParams fn_createCaptureCamera, FN_createCaptureStreamWithParams fn_createCaptureStream, FN_createWriter fn_createWriter) + : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createCaptureStream_(fn_createCaptureStream), fn_createWriter_(fn_createWriter) { // nothing } @@ -140,6 +156,12 @@ public: return fn_createCaptureFile_(filename, params); return Ptr(); } + Ptr createCapture(const Ptr& stream, const VideoCaptureParameters& params) const CV_OVERRIDE + { + if (fn_createCaptureStream_) + return fn_createCaptureStream_(stream, params); + return Ptr(); + } Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const CV_OVERRIDE { @@ -155,8 +177,8 @@ protected: Ptr backend; public: - StaticBackendWithParamsFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, FN_createWriter createWriter) - : backend(makePtr(createCaptureFile, createCaptureCamera, createWriter)) + StaticBackendWithParamsFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, FN_createCaptureStreamWithParams createCaptureStream, FN_createWriter createWriter) + : backend(makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter)) { // nothing } @@ -174,9 +196,10 @@ public: Ptr createBackendFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, + FN_createCaptureStreamWithParams createCaptureStream, FN_createWriter createWriter) { - return makePtr(createCaptureFile, createCaptureCamera, createWriter).staticCast(); + return makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter).staticCast(); } diff --git a/modules/videoio/src/cap.cpp b/modules/videoio/src/cap.cpp index b967057eae..3c1ecf1750 100644 --- a/modules/videoio/src/cap.cpp +++ b/modules/videoio/src/cap.cpp @@ -62,6 +62,10 @@ static bool param_VIDEOWRITER_DEBUG = utils::getConfigurationParameterBool("OPEN CV_LOG_WARNING(nullptr, __VA_ARGS__) \ } +IStreamReader::~IStreamReader() +{ + // nothing +} VideoCapture::VideoCapture() : throwOnFail(false) {} @@ -79,6 +83,13 @@ VideoCapture::VideoCapture(const String& filename, int apiPreference, const std: open(filename, apiPreference, params); } +VideoCapture::VideoCapture(const Ptr& source, int apiPreference, const std::vector& params) + : throwOnFail(false) +{ + CV_TRACE_FUNCTION(); + open(source, apiPreference, params); +} + VideoCapture::VideoCapture(int index, int apiPreference) : throwOnFail(false) { CV_TRACE_FUNCTION(); @@ -185,7 +196,7 @@ bool VideoCapture::open(const String& filename, int apiPreference, const std::ve else { CV_CAPTURE_LOG_DEBUG(NULL, - cv::format("VIDEOIO(%s): backend is not available " + cv::format("VIDEOIO(%s): backend is not available " "(plugin is missing, or can't be loaded due " "dependencies or it is not compatible)", info.name)); @@ -225,6 +236,131 @@ bool VideoCapture::open(const String& filename, int apiPreference, const std::ve return false; } +bool VideoCapture::open(const Ptr& stream, int apiPreference, const std::vector& params) + +{ + CV_INSTRUMENT_REGION(); + + if (apiPreference == CAP_ANY) + { + CV_Error_(Error::StsBadArg, ("Avoid CAP_ANY - explicit backend expected to avoid read data stream reset")); + } + + if (isOpened()) + { + release(); + } + + const VideoCaptureParameters parameters(params); + const std::vector backends = cv::videoio_registry::getAvailableBackends_CaptureByStream(); + for (size_t i = 0; i < backends.size(); i++) + { + const VideoBackendInfo& info = backends[i]; + if (apiPreference != info.id) + continue; + + if (!info.backendFactory) + { + CV_LOG_DEBUG(NULL, "VIDEOIO(" << info.name << "): factory is not available (plugins require filesystem support)"); + continue; + } + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): trying capture buffer ...", + info.name)); + CV_Assert(!info.backendFactory.empty()); + const Ptr backend = info.backendFactory->getBackend(); + if (!backend.empty()) + { + try + { + icap = backend->createCapture(stream, parameters); + if (!icap.empty()) + { + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): created, isOpened=%d", + info.name, icap->isOpened())); + if (icap->isOpened()) + { + return true; + } + icap.release(); + } + else + { + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): can't create capture", + info.name)); + } + } + catch (const cv::Exception& e) + { + if (throwOnFail) + { + throw; + } + CV_LOG_WARNING(NULL, + cv::format("VIDEOIO(%s): raised OpenCV exception:\n\n%s\n", + info.name, e.what())); + } + catch (const std::exception& e) + { + if (throwOnFail) + { + throw; + } + CV_LOG_WARNING(NULL, cv::format("VIDEOIO(%s): raised C++ exception:\n\n%s\n", + info.name, e.what())); + } + catch (...) + { + if (throwOnFail) + { + throw; + } + CV_LOG_WARNING(NULL, + cv::format("VIDEOIO(%s): raised unknown C++ exception!\n\n", + info.name)); + } + } + else + { + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): backend is not available " + "(plugin is missing, or can't be loaded due " + "dependencies or it is not compatible)", + info.name)); + } + } + + bool found = cv::videoio_registry::isBackendBuiltIn(static_cast(apiPreference)); + if (found) + { + CV_LOG_WARNING(NULL, cv::format("VIDEOIO(%s): backend is generally available " + "but can't be used to capture by read data stream", + cv::videoio_registry::getBackendName(static_cast(apiPreference)).c_str())); + } + + if (throwOnFail) + { + CV_Error_(Error::StsError, ("could not open read data stream")); + } + + if (cv::videoio_registry::checkDeprecatedBackend(apiPreference)) + { + CV_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): backend is removed from OpenCV", + cv::videoio_registry::getBackendName((VideoCaptureAPIs) apiPreference).c_str())); + } + else + { + CV_LOG_DEBUG(NULL, "VIDEOIO: choosen backend does not work or wrong. " + "Please make sure that your computer support chosen backend and OpenCV built " + "with right flags."); + } + + return false; +} + bool VideoCapture::open(int cameraNum, int apiPreference) { return open(cameraNum, apiPreference, std::vector()); @@ -323,7 +459,7 @@ bool VideoCapture::open(int cameraNum, int apiPreference, const std::vector else { CV_CAPTURE_LOG_DEBUG(NULL, - cv::format("VIDEOIO(%s): backend is not available " + cv::format("VIDEOIO(%s): backend is not available " "(plugin is missing, or can't be loaded due " "dependencies or it is not compatible)", info.name)); diff --git a/modules/videoio/src/cap_android_camera.cpp b/modules/videoio/src/cap_android_camera.cpp index 52f741c444..d0bcfb367b 100644 --- a/modules/videoio/src/cap_android_camera.cpp +++ b/modules/videoio/src/cap_android_camera.cpp @@ -33,23 +33,27 @@ using namespace cv; #define FOURCC_BGR CV_FOURCC_MACRO('B','G','R','3') #define FOURCC_RGB CV_FOURCC_MACRO('R','G','B','3') +#define FOURCC_BGRA CV_FOURCC_MACRO('B','G','R','4') +#define FOURCC_RGBA CV_FOURCC_MACRO('R','G','B','4') #define FOURCC_GRAY CV_FOURCC_MACRO('G','R','E','Y') #define FOURCC_NV21 CV_FOURCC_MACRO('N','V','2','1') #define FOURCC_YV12 CV_FOURCC_MACRO('Y','V','1','2') #define FOURCC_UNKNOWN 0xFFFFFFFF -template class RangeValue { -public: +template struct RangeValue { T min, max; /** * return absolute value from relative value * * value: in percent (50 for 50%) * */ T value(int percent) { - return static_cast(min + (max - min) * percent / 100); + return static_cast(min + ((max - min) * percent) / 100); + } + RangeValue(T minv = 0, T maxv = 0) : min(minv), max(maxv) {} + bool Supported() const { return (min != max); } + T clamp( T value ) const { + return (value > max) ? max : ((value < min) ? min : value); } - RangeValue() { min = max = static_cast(0); } - bool Supported(void) const { return (min != max); } }; static inline void deleter_ACameraManager(ACameraManager *cameraManager) { @@ -165,8 +169,7 @@ void OnCaptureFailed(void* context, * on camera. For this sample purpose, clamp to a range showing visible * video on preview: 100000ns ~ 250000000ns */ -static const long kMinExposureTime = 1000000L; -static const long kMaxExposureTime = 250000000L; +static const RangeValue exposureTimeLimits = { 1000000, 250000000 }; static double elapsedTimeFrom(std::chrono::time_point start) { return std::chrono::duration(std::chrono::system_clock::now() - start).count(); @@ -188,7 +191,7 @@ class AndroidCameraCapture : public IVideoCapture int32_t frameWidth = 0; int32_t frameStride = 0; int32_t frameHeight = 0; - int32_t colorFormat; + int32_t colorFormat = COLOR_FormatUnknown; std::vector buffer; bool sessionOutputAdded = false; bool targetAdded = false; @@ -198,8 +201,9 @@ class AndroidCameraCapture : public IVideoCapture bool settingHeight = false; int desiredWidth = 640; int desiredHeight = 480; - bool autoExposure = true; - int64_t exposureTime = 0L; + uint8_t flashMode = ACAMERA_FLASH_MODE_OFF; + uint8_t aeMode = ACAMERA_CONTROL_AE_MODE_ON; + int64_t exposureTime = 0; RangeValue exposureRange; int32_t sensitivity = 0; RangeValue sensitivityRange; @@ -212,7 +216,24 @@ public: std::condition_variable condition; public: - AndroidCameraCapture() {} + AndroidCameraCapture(const VideoCaptureParameters& params) + { + desiredWidth = params.get(CAP_PROP_FRAME_WIDTH, desiredWidth); + desiredHeight = params.get(CAP_PROP_FRAME_HEIGHT, desiredHeight); + + static const struct { + int propId; + uint32_t defaultValue; + } items[] = { + { CAP_PROP_AUTO_EXPOSURE, 1 }, + { CAP_PROP_FOURCC, FOURCC_UNKNOWN }, + { CAP_PROP_ANDROID_DEVICE_TORCH, 0 } + }; + + for (auto it = std::begin(items); it != std::end(items); ++it) { + setProperty(it->propId, params.get(it->propId, it->defaultValue)); + } + } ~AndroidCameraCapture() { cleanUp(); } @@ -357,14 +378,20 @@ public: if (colorFormat == COLOR_FormatYUV420Planar) { Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data()); switch (fourCC) { + case FOURCC_BGRA: + cvtColor(yuv, out, COLOR_YUV2BGRA_YV12); + break; + case FOURCC_RGBA: + cvtColor(yuv, out, COLOR_YUV2RGBA_YV12); + break; case FOURCC_BGR: - cv::cvtColor(yuv, out, cv::COLOR_YUV2BGR_YV12); + cvtColor(yuv, out, COLOR_YUV2BGR_YV12); break; case FOURCC_RGB: - cv::cvtColor(yuv, out, cv::COLOR_YUV2RGB_YV12); + cvtColor(yuv, out, COLOR_YUV2RGB_YV12); break; case FOURCC_GRAY: - cv::cvtColor(yuv, out, cv::COLOR_YUV2GRAY_YV12); + cvtColor(yuv, out, COLOR_YUV2GRAY_YV12); break; case FOURCC_YV12: yuv.copyTo(out); @@ -377,14 +404,20 @@ public: Mat yuv(frameHeight + frameHeight/2, frameStride, CV_8UC1, buffer.data()); Mat tmp = (frameWidth == frameStride) ? yuv : yuv(Rect(0, 0, frameWidth, frameHeight + frameHeight / 2)); switch (fourCC) { + case FOURCC_BGRA: + cvtColor(tmp, out, COLOR_YUV2BGRA_NV21); + break; + case FOURCC_RGBA: + cvtColor(tmp, out, COLOR_YUV2RGBA_NV21); + break; case FOURCC_BGR: - cv::cvtColor(tmp, out, cv::COLOR_YUV2BGR_NV21); + cvtColor(tmp, out, COLOR_YUV2BGR_NV21); break; case FOURCC_RGB: - cv::cvtColor(tmp, out, cv::COLOR_YUV2RGB_NV21); + cvtColor(tmp, out, COLOR_YUV2RGB_NV21); break; case FOURCC_GRAY: - cv::cvtColor(tmp, out, cv::COLOR_YUV2GRAY_NV21); + cvtColor(tmp, out, COLOR_YUV2GRAY_NV21); break; case FOURCC_NV21: tmp.copyTo(out); @@ -408,13 +441,15 @@ public: case CAP_PROP_FRAME_HEIGHT: return isOpened() ? frameHeight : desiredHeight; case CAP_PROP_AUTO_EXPOSURE: - return autoExposure ? 1 : 0; + return (aeMode == ACAMERA_CONTROL_AE_MODE_ON) ? 1 : 0; case CAP_PROP_EXPOSURE: return exposureTime; case CAP_PROP_ISO_SPEED: return sensitivity; case CAP_PROP_FOURCC: return fourCC; + case CAP_PROP_ANDROID_DEVICE_TORCH: + return (flashMode == ACAMERA_FLASH_MODE_TORCH) ? 1 : 0; default: break; } @@ -452,6 +487,8 @@ public: switch (newFourCC) { case FOURCC_BGR: case FOURCC_RGB: + case FOURCC_BGRA: + case FOURCC_RGBA: case FOURCC_GRAY: fourCC = newFourCC; return true; @@ -478,29 +515,31 @@ public: } } case CAP_PROP_AUTO_EXPOSURE: - autoExposure = (value != 0); + aeMode = (value != 0) ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; if (isOpened()) { - uint8_t aeMode = autoExposure ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; - camera_status_t status = ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_u8, ACAMERA_CONTROL_AE_MODE, aeMode); } return true; case CAP_PROP_EXPOSURE: if (isOpened() && exposureRange.Supported()) { - exposureTime = (int64_t)value; + exposureTime = exposureRange.clamp(static_cast(value)); LOGI("Setting CAP_PROP_EXPOSURE will have no effect unless CAP_PROP_AUTO_EXPOSURE is off"); - camera_status_t status = ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_i64, ACAMERA_SENSOR_EXPOSURE_TIME, exposureTime); } return false; case CAP_PROP_ISO_SPEED: if (isOpened() && sensitivityRange.Supported()) { - sensitivity = (int32_t)value; + sensitivity = sensitivityRange.clamp(static_cast(value)); LOGI("Setting CAP_PROP_ISO_SPEED will have no effect unless CAP_PROP_AUTO_EXPOSURE is off"); - camera_status_t status = ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_i32, ACAMERA_SENSOR_SENSITIVITY, sensitivity); } return false; + case CAP_PROP_ANDROID_DEVICE_TORCH: + flashMode = (value != 0) ? ACAMERA_FLASH_MODE_TORCH : ACAMERA_FLASH_MODE_OFF; + if (isOpened()) { + return submitRequest(ACaptureRequest_setEntry_u8, ACAMERA_FLASH_MODE, flashMode); + } + return true; default: break; } @@ -561,7 +600,7 @@ public: return false; } std::shared_ptr cameraMetadata = std::shared_ptr(metadata, deleter_ACameraMetadata); - ACameraMetadata_const_entry entry; + ACameraMetadata_const_entry entry = {}; ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); double bestScore = std::numeric_limits::max(); @@ -594,25 +633,19 @@ public: } LOGI("Best resolution match: %dx%d", bestMatchWidth, bestMatchHeight); - ACameraMetadata_const_entry val = { 0, }; - camera_status_t status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val); - if (status == ACAMERA_OK) { - exposureRange.min = val.data.i64[0]; - if (exposureRange.min < kMinExposureTime) { - exposureRange.min = kMinExposureTime; - } - exposureRange.max = val.data.i64[1]; - if (exposureRange.max > kMaxExposureTime) { - exposureRange.max = kMaxExposureTime; - } + ACameraMetadata_const_entry val; + cStatus = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val); + if (cStatus == ACAMERA_OK) { + exposureRange.min = exposureTimeLimits.clamp(val.data.i64[0]); + exposureRange.max = exposureTimeLimits.clamp(val.data.i64[1]); exposureTime = exposureRange.value(2); } else { LOGW("Unsupported ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE"); - exposureRange.min = exposureRange.max = 0l; - exposureTime = 0l; + exposureRange.min = exposureRange.max = 0; + exposureTime = 0; } - status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE, &val); - if (status == ACAMERA_OK){ + cStatus = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE, &val); + if (cStatus == ACAMERA_OK){ sensitivityRange.min = val.data.i32[0]; sensitivityRange.max = val.data.i32[1]; sensitivity = sensitivityRange.value(2); @@ -693,12 +726,13 @@ public: return false; } captureSession = std::shared_ptr(session, deleter_ACameraCaptureSession); - uint8_t aeMode = autoExposure ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; + ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode); ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity); - if (!autoExposure) { + if (aeMode != ACAMERA_CONTROL_AE_MODE_ON) { ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime); } + ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_FLASH_MODE, 1, &flashMode); cStatus = ACameraCaptureSession_setRepeatingRequest(captureSession.get(), GetCaptureCallback(), 1, &request, nullptr); if (cStatus != ACAMERA_OK) { @@ -732,6 +766,18 @@ public: cameraManager = nullptr; imageReader = nullptr; } + + template + bool submitRequest(FuncT setFn, uint32_t tag, const T &data) + { + ACaptureRequest *request = captureRequest.get(); + + return request && + setFn(request, tag, 1, &data) == ACAMERA_OK && + ACameraCaptureSession_setRepeatingRequest(captureSession.get(), + GetCaptureCallback(), + 1, &request, nullptr) == ACAMERA_OK; + } }; /******************************** Session management *******************************/ @@ -788,8 +834,8 @@ void OnCaptureFailed(void* context, /****************** Implementation of interface functions ********************/ -Ptr cv::createAndroidCapture_cam( int index ) { - Ptr res = makePtr(); +Ptr cv::createAndroidCapture_cam(int index, const VideoCaptureParameters& params) { + Ptr res = makePtr(params); if (res && res->initCapture(index)) return res; return Ptr(); diff --git a/modules/videoio/src/cap_android_mediandk.cpp b/modules/videoio/src/cap_android_mediandk.cpp index 80c701f2ec..6dfb79843a 100644 --- a/modules/videoio/src/cap_android_mediandk.cpp +++ b/modules/videoio/src/cap_android_mediandk.cpp @@ -21,10 +21,20 @@ #define INPUT_TIMEOUT_MS 2000 +#define COLOR_FormatUnknown -1 #define COLOR_FormatYUV420Planar 19 #define COLOR_FormatYUV420SemiPlanar 21 #define COLOR_FormatSurface 0x7f000789 //See https://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities for codes +#define FOURCC_BGR CV_FOURCC_MACRO('B','G','R','3') +#define FOURCC_RGB CV_FOURCC_MACRO('R','G','B','3') +#define FOURCC_BGRA CV_FOURCC_MACRO('B','G','R','4') +#define FOURCC_RGBA CV_FOURCC_MACRO('R','G','B','4') +#define FOURCC_GRAY CV_FOURCC_MACRO('G','R','E','Y') +#define FOURCC_NV12 CV_FOURCC_MACRO('N','V','1','2') +#define FOURCC_YV12 CV_FOURCC_MACRO('Y','V','1','2') +#define FOURCC_UNKNOWN 0xFFFFFFFF + using namespace cv; #define TAG "NativeCodec" @@ -51,9 +61,9 @@ class AndroidMediaNdkCapture : public IVideoCapture public: AndroidMediaNdkCapture(): sawInputEOS(false), sawOutputEOS(false), - frameStride(0), frameWidth(0), frameHeight(0), colorFormat(0), - videoWidth(0), videoHeight(0), - videoFrameCount(0), + frameStride(0), frameWidth(0), frameHeight(0), + colorFormat(COLOR_FormatUnknown), fourCC(FOURCC_BGR), + videoWidth(0), videoHeight(0), videoFrameCount(0), videoRotation(0), videoRotationCode(-1), videoOrientationAuto(false) {} @@ -65,6 +75,7 @@ public: int32_t frameWidth; int32_t frameHeight; int32_t colorFormat; + uint32_t fourCC; int32_t videoWidth; int32_t videoHeight; float videoFrameRate; @@ -73,7 +84,6 @@ public: int32_t videoRotationCode; bool videoOrientationAuto; std::vector buffer; - Mat frame; ~AndroidMediaNdkCapture() { cleanUp(); } @@ -157,23 +167,51 @@ public: return false; } - Mat yuv(frameHeight + frameHeight/2, frameStride, CV_8UC1, buffer.data()); + ColorConversionCodes ccCode; + const Mat yuv(frameHeight + frameHeight/2, + frameWidth, CV_8UC1, buffer.data(), frameStride); if (colorFormat == COLOR_FormatYUV420Planar) { - cv::cvtColor(yuv, frame, cv::COLOR_YUV2BGR_YV12); + switch(fourCC) + { + case FOURCC_BGR: ccCode = COLOR_YUV2BGR_YV12; break; + case FOURCC_RGB: ccCode = COLOR_YUV2RGB_YV12; break; + case FOURCC_BGRA: ccCode = COLOR_YUV2BGRA_YV12; break; + case FOURCC_RGBA: ccCode = COLOR_YUV2RGBA_YV12; break; + case FOURCC_GRAY: ccCode = COLOR_YUV2GRAY_YV12; break; + case FOURCC_YV12: break; + case FOURCC_UNKNOWN: fourCC = FOURCC_YV12; break; + default: LOGE("Unexpected FOURCC value: %d", fourCC); + return false; + } } else if (colorFormat == COLOR_FormatYUV420SemiPlanar) { - cv::cvtColor(yuv, frame, cv::COLOR_YUV2BGR_NV21); + // Attention: COLOR_FormatYUV420SemiPlanar seems to correspond to NV12. + // This is different from the Camera2 interface, where NV21 + // is used in this situation. + switch(fourCC) + { + case FOURCC_BGR: ccCode = COLOR_YUV2BGR_NV12; break; + case FOURCC_RGB: ccCode = COLOR_YUV2RGB_NV12; break; + case FOURCC_BGRA: ccCode = COLOR_YUV2BGRA_NV12; break; + case FOURCC_RGBA: ccCode = COLOR_YUV2RGBA_NV12; break; + case FOURCC_GRAY: ccCode = COLOR_YUV2GRAY_NV12; break; + case FOURCC_NV12: break; + case FOURCC_UNKNOWN: fourCC = FOURCC_NV12; break; + default: LOGE("Unexpected FOURCC value: %d", fourCC); + return false; + } } else { LOGE("Unsupported video format: %d", colorFormat); return false; } - Mat croppedFrame = frame(Rect(0, 0, videoWidth, videoHeight)); - out.assign(croppedFrame); + if (fourCC == FOURCC_YV12 || fourCC == FOURCC_NV12) + yuv.copyTo(out); + else + cvtColor(yuv, out, ccCode); - if (videoOrientationAuto && -1 != videoRotationCode) { - cv::rotate(out, out, videoRotationCode); - } + if (videoOrientationAuto && -1 != videoRotationCode) + rotate(out, out, videoRotationCode); return true; } @@ -194,8 +232,11 @@ public: case CAP_PROP_FRAME_COUNT: return videoFrameCount; case CAP_PROP_ORIENTATION_META: return videoRotation; case CAP_PROP_ORIENTATION_AUTO: return videoOrientationAuto ? 1 : 0; + case CAP_PROP_FOURCC: return fourCC; } - return 0; + + // unknown parameter or value not available + return -1; } bool setProperty(int property_id, double value) CV_OVERRIDE @@ -206,6 +247,31 @@ public: videoOrientationAuto = value != 0 ? true : false; return true; } + case CAP_PROP_FOURCC: { + uint32_t newFourCC = cvRound(value); + switch (newFourCC) + { + case FOURCC_BGR: + case FOURCC_RGB: + case FOURCC_BGRA: + case FOURCC_RGBA: + case FOURCC_GRAY: + fourCC = newFourCC; + return true; + case FOURCC_YV12: + if (colorFormat != COLOR_FormatYUV420SemiPlanar) { + fourCC = (colorFormat == COLOR_FormatUnknown) ? FOURCC_UNKNOWN : FOURCC_YV12; + return true; + } + break; + case FOURCC_NV12: + if (colorFormat != COLOR_FormatYUV420Planar) { + fourCC = (colorFormat == COLOR_FormatUnknown) ? FOURCC_UNKNOWN : FOURCC_NV12; + return true; + } + break; + } + } } return false; @@ -485,10 +551,12 @@ public: } Mat image = image_.getMat(); - if (CV_8UC3 != image.type() || image.cols > width || image.rows > height) { + if (image.cols > width || image.rows > height || + (CV_8UC1 != image.type() && CV_8UC3 != image.type() && CV_8UC4 != image.type())) { LOGE( - "Expected input to be a mat of maximum %d x %d of type CV_8UC3 (%d), but received %d x %d of type: %d", - width, height, CV_8UC3, + "Expected input to be a mat of maximum %d x %d of type CV_8UC1, CV_8UC3 or CV_8UC4" + " (%d, %d, %d), but received %d x %d of type %d", + width, height, CV_8UC1, CV_8UC3, CV_8UC4, image.cols, image.rows, image.type() ); return; @@ -498,33 +566,45 @@ public: ANativeWindow_Buffer buffer; if (0 != ANativeWindow_lock(surface, &buffer, NULL)) { LOGE("Failed to lock the surface"); - } else { - if (AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM == buffer.format) { - Mat bufferMat(image.rows, image.cols, CV_8UC4, buffer.bits, buffer.stride * 4); - cvtColor(image, bufferMat, COLOR_BGR2RGBA); - } else { - LOGE("Unknow surface buffer format: %u", buffer.format); - } - - ANativeWindow_unlockAndPost(surface); + return; } - #else - LOGV("[write] image: %d x %d", image.cols, image.rows); - //OpenCV don't support RGB to NV12 so we need to connvert to YV12 and then manually changed it to NV12 + if (AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM == buffer.format) { + Mat bufferMat(image.rows, image.cols, CV_8UC4, buffer.bits, buffer.stride * 4); + switch (image.type()) { + case CV_8UC4: image.copyTo(bufferMat); break; + case CV_8UC3: cvtColor(image, bufferMat, COLOR_BGR2RGBA); break; + case CV_8UC1: cvtColor(image, bufferMat, COLOR_GRAY2RGBA); break; + } + } else { + LOGE("Unknown surface buffer format: 0x%x", buffer.format); + } + + ANativeWindow_unlockAndPost(surface); + #else + //OpenCV doesn't support RGB to NV12 so we need to convert to YV12 and then manually changed it to NV12 Mat imageYV12; - cvtColor(image, imageYV12, COLOR_BGR2YUV_YV12); + switch (image.type()) { + case CV_8UC4: cvtColor(image, imageYV12, COLOR_RGBA2YUV_YV12); break; + case CV_8UC3: cvtColor(image, imageYV12, COLOR_BGR2YUV_YV12); break; + case CV_8UC1: imageYV12.create(image.rows + image.rows/2, image.cols, CV_8UC1); + image.copyTo(imageYV12.rowRange(0, image.rows)); + imageYV12.rowRange(image.rows, imageYV12.rows) = 128; + break; + } //convert from YV12 to NV12 - size_t yPlaneSize = width * height; - size_t vPlaneSize = yPlaneSize / 4; + if (image.type() != CV_8UC1) { + size_t yPlaneSize = width * height; + size_t vPlaneSize = yPlaneSize / 4; - Mat channels[2] = { - Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize + vPlaneSize ).clone(), - Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize ).clone() - }; - Mat vuMat( vPlaneSize, 1, CV_8UC2, imageYV12.ptr() + yPlaneSize ); - merge(channels, 2, vuMat); + Mat channels[2] = { + Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize + vPlaneSize ).clone(), + Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize ).clone() + }; + Mat vuMat( vPlaneSize, 1, CV_8UC2, imageYV12.ptr() + yPlaneSize ); + merge(channels, 2, vuMat); + } writeBytes( imageYV12.ptr(), imageYV12.rows * imageYV12.cols ); #endif @@ -668,7 +748,7 @@ const AndroidMediaNdkVideoWriter::FourCCInfo AndroidMediaNdkVideoWriter::FOURCC_ /****************** Implementation of interface functions ********************/ -Ptr cv::createAndroidCapture_file(const std::string &filename) { +Ptr cv::createAndroidCapture_file(const std::string &filename, const VideoCaptureParameters& ) { Ptr res = makePtr(); if (res && res->initCapture(filename.c_str())) return res; diff --git a/modules/videoio/src/cap_ffmpeg.cpp b/modules/videoio/src/cap_ffmpeg.cpp index 753d3f793d..a35f305ea9 100644 --- a/modules/videoio/src/cap_ffmpeg.cpp +++ b/modules/videoio/src/cap_ffmpeg.cpp @@ -74,6 +74,11 @@ public: { open(filename, params); } + CvCapture_FFMPEG_proxy(const Ptr& stream, const cv::VideoCaptureParameters& params) + : ffmpegCapture(NULL) + { + open(stream, params); + } virtual ~CvCapture_FFMPEG_proxy() { close(); } virtual double getProperty_(int propId) const CV_OVERRIDE @@ -122,6 +127,14 @@ public: ffmpegCapture = cvCreateFileCaptureWithParams_FFMPEG(filename.c_str(), params); return ffmpegCapture != 0; } + bool open(const Ptr& stream, const cv::VideoCaptureParameters& params) + { + close(); + + readStream = stream; // Increase counter + ffmpegCapture = cvCreateStreamCaptureWithParams_FFMPEG(stream, params); + return ffmpegCapture != 0; + } void close() { if (ffmpegCapture) @@ -135,6 +148,7 @@ public: protected: CvCapture_FFMPEG* ffmpegCapture; + Ptr readStream; }; } // namespace @@ -147,6 +161,14 @@ cv::Ptr cvCreateFileCapture_FFMPEG_proxy(const std::string &f return cv::Ptr(); } +cv::Ptr cvCreateStreamCapture_FFMPEG_proxy(const Ptr& stream, const cv::VideoCaptureParameters& params) +{ + cv::Ptr capture = std::make_shared(stream, params); + if (capture && capture->isOpened()) + return capture; + return cv::Ptr(); +} + namespace { class CvVideoWriter_FFMPEG_proxy CV_FINAL : @@ -234,7 +256,7 @@ cv::Ptr cvCreateVideoWriter_FFMPEG_proxy(const std::string& fi #include "plugin_api.hpp" #else #define CAPTURE_ABI_VERSION 1 -#define CAPTURE_API_VERSION 1 +#define CAPTURE_API_VERSION 2 #include "plugin_capture_api.hpp" #define WRITER_ABI_VERSION 1 #define WRITER_API_VERSION 1 @@ -255,7 +277,7 @@ CvResult CV_API_CALL cv_capture_open(const char* filename, int camera_index, CV_ CvCapture_FFMPEG_proxy *cap = 0; try { - cap = new CvCapture_FFMPEG_proxy(filename, cv::VideoCaptureParameters()); + cap = new CvCapture_FFMPEG_proxy(String(filename), cv::VideoCaptureParameters()); if (cap->isOpened()) { *handle = (CvPluginCapture)cap; @@ -292,7 +314,43 @@ CvResult CV_API_CALL cv_capture_open_with_params( try { cv::VideoCaptureParameters parameters(params, n_params); - cap = new CvCapture_FFMPEG_proxy(filename, parameters); + cap = new CvCapture_FFMPEG_proxy(String(filename), parameters); + if (cap->isOpened()) + { + *handle = (CvPluginCapture)cap; + return CV_ERROR_OK; + } + } + catch (const std::exception& e) + { + CV_LOG_WARNING(NULL, "FFmpeg: Exception is raised: " << e.what()); + } + catch (...) + { + CV_LOG_WARNING(NULL, "FFmpeg: Unknown C++ exception is raised"); + } + if (cap) + delete cap; + return CV_ERROR_FAIL; +} + +static +CvResult CV_API_CALL cv_capture_open_buffer( + void* opaque, + long long(*read)(void* opaque, char* buffer, long long size), + long long(*seek)(void* opaque, long long offset, int way), + int* params, unsigned n_params, + CV_OUT CvPluginCapture* handle +) +{ + if (!handle) + return CV_ERROR_FAIL; + *handle = NULL; + CvCapture_FFMPEG_proxy *cap = 0; + try + { + cv::VideoCaptureParameters parameters(params, n_params); + cap = new CvCapture_FFMPEG_proxy(makePtr(opaque, read, seek), parameters); if (cap->isOpened()) { *handle = (CvPluginCapture)cap; @@ -636,6 +694,9 @@ static const OpenCV_VideoIO_Capture_Plugin_API capture_plugin_api = }, { /* 8*/cv_capture_open_with_params, + }, + { + /* 9*/cv_capture_open_buffer, } }; diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index f13cb8462b..d2359b066e 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -526,7 +526,7 @@ inline static std::string _opencv_ffmpeg_get_error_string(int error_code) struct CvCapture_FFMPEG { - bool open(const char* filename, const VideoCaptureParameters& params); + bool open(const char* filename, const Ptr& stream, const VideoCaptureParameters& params); void close(); double getProperty(int) const; @@ -563,6 +563,8 @@ struct CvCapture_FFMPEG int64_t pts_in_fps_time_base; int64_t dts_delay_in_fps_time_base; + AVIOContext * avio_context; + AVPacket packet; Image_FFMPEG frame; struct SwsContext *img_convert_ctx; @@ -580,6 +582,8 @@ struct CvCapture_FFMPEG */ char * filename; + Ptr readStream; + AVDictionary *dict; #if USE_AV_INTERRUPT_CALLBACK int open_timeout; @@ -628,11 +632,14 @@ void CvCapture_FFMPEG::init() avcodec = 0; context = 0; + avio_context = 0; frame_number = 0; eps_zero = 0.000025; rotation_angle = 0; + readStream.reset(); + dict = NULL; #if USE_AV_INTERRUPT_CALLBACK @@ -730,6 +737,13 @@ void CvCapture_FFMPEG::close() #endif } + if (avio_context) + { + av_free(avio_context->buffer); + av_freep(&avio_context); + } + readStream.reset(); + init(); } @@ -1019,7 +1033,7 @@ static bool isThreadSafe() { return threadSafe; } -bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& params) +bool CvCapture_FFMPEG::open(const char* _filename, const Ptr& stream, const VideoCaptureParameters& params) { const bool threadSafe = isThreadSafe(); InternalFFMpegRegister::init(threadSafe); @@ -1034,6 +1048,8 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& close(); + readStream = stream; + if (!params.empty()) { convertRGB = params.get(CAP_PROP_CONVERT_RGB, true); @@ -1145,6 +1161,56 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& input_format = av_find_input_format(entry->value); } + if (!_filename) + { + size_t avio_ctx_buffer_size = 4096; + uint8_t* avio_ctx_buffer = (uint8_t*)av_malloc(avio_ctx_buffer_size); + CV_Assert(avio_ctx_buffer); + avio_context = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, + [](void *opaque, uint8_t *buf, int buf_size) -> int { + try { + auto capture = reinterpret_cast(opaque); + auto is = capture->readStream; + int result = (int)is->read(reinterpret_cast(buf), buf_size); + + // https://github.com/FFmpeg/FFmpeg/commit/858db4b01fa2b55ee55056c033054ca54ac9b0fd#diff-863c87afc9bb02fe42d071015fc8218972c80b146d603239f20b483ad0988ae9R394 + // https://github.com/FFmpeg/FFmpeg/commit/a606f27f4c610708fa96e35eed7b7537d3d8f712 + // https://github.com/FFmpeg/FFmpeg/blob/n4.0/libavformat/version.h#L83C41-L83C73 +#if (LIBAVFORMAT_VERSION_MAJOR >= 58) && (LIBAVFORMAT_VERSION_MICRO >= 100) // FFmpeg n4.0+ + if (result == 0 && buf_size > 0) + { + result = AVERROR_EOF; + } +#endif + + CV_LOG_VERBOSE(NULL, 0, "FFMPEG: IStreamReader::read(" << buf_size << ") = " << result); + return result; + } catch (...) { + CV_LOG_WARNING(NULL, "FFMPEG: IStreamReader::read(" << buf_size << ") failed"); + return 0; + } + }, + NULL, + [](void *opaque, int64_t offset, int whence) -> int64_t { + try { + int64_t result = -1; + auto capture = reinterpret_cast(opaque); + auto is = capture->readStream; + int origin = whence & (~AVSEEK_FORCE); + if (origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END) + { + result = is->seek(offset, origin); + } + CV_LOG_VERBOSE(NULL, 0, "FFMPEG: IStreamReader::seek(" << offset << ", whence=" << whence << ") = " << result); + return result; + } catch (...) { + CV_LOG_WARNING(NULL, "FFMPEG: IStreamReader::seek(" << offset << ", whence=" << whence << ") failed"); + return -1; + } + }); + CV_Assert(avio_context); + ic->pb = avio_context; + } int err = avformat_open_input(&ic, _filename, input_format, &dict); if (err < 0) @@ -3292,16 +3358,30 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, static CvCapture_FFMPEG* cvCreateFileCaptureWithParams_FFMPEG(const char* filename, const VideoCaptureParameters& params) { - // FIXIT: remove unsafe malloc() approach - CvCapture_FFMPEG* capture = (CvCapture_FFMPEG*)malloc(sizeof(*capture)); + CvCapture_FFMPEG* capture = new CvCapture_FFMPEG(); if (!capture) return 0; capture->init(); - if (capture->open(filename, params)) + if (capture->open(filename, nullptr, params)) return capture; capture->close(); - free(capture); + delete capture; + return 0; +} + +static +CvCapture_FFMPEG* cvCreateStreamCaptureWithParams_FFMPEG(const Ptr& stream, const VideoCaptureParameters& params) +{ + CvCapture_FFMPEG* capture = new CvCapture_FFMPEG(); + if (!capture) + return 0; + capture->init(); + if (capture->open(nullptr, stream, params)) + return capture; + + capture->close(); + delete capture; return 0; } @@ -3310,7 +3390,7 @@ void cvReleaseCapture_FFMPEG(CvCapture_FFMPEG** capture) if( capture && *capture ) { (*capture)->close(); - free(*capture); + delete *capture; *capture = 0; } } @@ -3344,14 +3424,14 @@ int cvRetrieveFrame2_FFMPEG(CvCapture_FFMPEG* capture, unsigned char** data, int static CvVideoWriter_FFMPEG* cvCreateVideoWriterWithParams_FFMPEG( const char* filename, int fourcc, double fps, int width, int height, const VideoWriterParameters& params ) { - CvVideoWriter_FFMPEG* writer = (CvVideoWriter_FFMPEG*)malloc(sizeof(*writer)); + CvVideoWriter_FFMPEG* writer = new CvVideoWriter_FFMPEG(); if (!writer) return 0; writer->init(); if( writer->open( filename, fourcc, fps, width, height, params )) return writer; writer->close(); - free(writer); + delete writer; return 0; } @@ -3368,7 +3448,7 @@ void cvReleaseVideoWriter_FFMPEG( CvVideoWriter_FFMPEG** writer ) if( writer && *writer ) { (*writer)->close(); - free(*writer); + delete *writer; *writer = 0; } } diff --git a/modules/videoio/src/cap_gstreamer.cpp b/modules/videoio/src/cap_gstreamer.cpp index dd0f9c00ea..e4a325041c 100644 --- a/modules/videoio/src/cap_gstreamer.cpp +++ b/modules/videoio/src/cap_gstreamer.cpp @@ -961,7 +961,7 @@ bool GStreamerCapture::retrieveVideoFrame(int, OutputArray dst) // regular strides/offsets if no meta is present. GstVideoFrame frame = {}; #if FULL_GST_VERSION >= VERSION_NUM(1,6,0) - GstMapFlags flags = static_cast(GST_MAP_READ | GST_VIDEO_FRAME_MAP_FLAG_NO_REF); + GstMapFlags flags = static_cast(+GST_MAP_READ | GST_VIDEO_FRAME_MAP_FLAG_NO_REF); #else GstMapFlags flags = static_cast(GST_MAP_READ); #endif diff --git a/modules/videoio/src/cap_images.cpp b/modules/videoio/src/cap_images.cpp index 6b30c5c4b6..f472ff711b 100644 --- a/modules/videoio/src/cap_images.cpp +++ b/modules/videoio/src/cap_images.cpp @@ -113,16 +113,9 @@ void CvCapture_Images::close() bool CvCapture_Images::grabFrame() { - cv::String filename; - if (length == 1) - if (currentframe < length) - filename = filename_pattern; - else - { - return false; - } - else - filename = cv::format(filename_pattern.c_str(), (int)(firstframe + currentframe)); + if (length == 1 && currentframe >= length) + return false; + const cv::String filename = cv::format(filename_pattern.c_str(), (int)(firstframe + currentframe)); CV_Assert(!filename.empty()); if (grabbedInOpen) diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index a2301e4fdf..d26c089777 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -7,6 +7,7 @@ #include "opencv2/core.hpp" #include "opencv2/videoio.hpp" +#include "opencv2/videoio/utils.private.hpp" //=================================================== @@ -302,6 +303,7 @@ protected: //================================================================================================== Ptr cvCreateFileCapture_FFMPEG_proxy(const std::string &filename, const VideoCaptureParameters& params); +Ptr cvCreateStreamCapture_FFMPEG_proxy(const Ptr& stream, const VideoCaptureParameters& params); Ptr cvCreateVideoWriter_FFMPEG_proxy(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params); @@ -327,6 +329,7 @@ Ptr create_WRT_capture(int device); Ptr cvCreateCapture_MSMF(int index, const VideoCaptureParameters& params); Ptr cvCreateCapture_MSMF(const std::string& filename, const VideoCaptureParameters& params); +Ptr cvCreateCapture_MSMF(const Ptr& stream, const VideoCaptureParameters& params); Ptr cvCreateVideoWriter_MSMF(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params); @@ -367,8 +370,8 @@ Ptr createGPhoto2Capture(const std::string& deviceName); Ptr createXINECapture(const std::string &filename); -Ptr createAndroidCapture_cam( int index ); -Ptr createAndroidCapture_file(const std::string &filename); +Ptr createAndroidCapture_cam(int index, const VideoCaptureParameters& params); +Ptr createAndroidCapture_file(const std::string &filename, const VideoCaptureParameters& params); Ptr createAndroidVideoWriter(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params); diff --git a/modules/videoio/src/cap_msmf.cpp b/modules/videoio/src/cap_msmf.cpp index 138e26e87a..5c2121498d 100644 --- a/modules/videoio/src/cap_msmf.cpp +++ b/modules/videoio/src/cap_msmf.cpp @@ -746,7 +746,7 @@ public: virtual ~CvCapture_MSMF(); bool configureHW(const cv::VideoCaptureParameters& params); virtual bool open(int, const cv::VideoCaptureParameters* params); - virtual bool open(const cv::String&, const cv::VideoCaptureParameters* params); + virtual bool open(const cv::String&, const Ptr&, const cv::VideoCaptureParameters* params); virtual void close(); virtual double getProperty(int) const CV_OVERRIDE; virtual bool setProperty(int, double) CV_OVERRIDE; @@ -789,6 +789,7 @@ protected: _ComPtr D3DDev; _ComPtr D3DMgr; #endif + _ComPtr byteStream; _ComPtr videoFileSource; _ComPtr readCallback; // non-NULL for "live" streams (camera capture) std::vector dwStreamIndices; @@ -1034,7 +1035,7 @@ bool CvCapture_MSMF::configureHW(bool enable) } } // Reopen if needed - return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), NULL)) : true; + return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), nullptr, NULL)) : true; } D3DMgr.Release(); } @@ -1050,7 +1051,7 @@ bool CvCapture_MSMF::configureHW(bool enable) if (D3DDev) D3DDev.Release(); captureMode = MODE_SW; - return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), NULL)) : true; + return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), nullptr, NULL)) : true; } #else return !enable; @@ -1249,10 +1250,10 @@ bool CvCapture_MSMF::open(int index, const cv::VideoCaptureParameters* params) return isOpen; } -bool CvCapture_MSMF::open(const cv::String& _filename, const cv::VideoCaptureParameters* params) +bool CvCapture_MSMF::open(const cv::String& _filename, const Ptr& stream, const cv::VideoCaptureParameters* params) { close(); - if (_filename.empty()) + if (_filename.empty() && !stream) return false; if (params) @@ -1263,9 +1264,34 @@ bool CvCapture_MSMF::open(const cv::String& _filename, const cv::VideoCapturePar } // Set source reader parameters _ComPtr attr = getDefaultSourceConfig(); - cv::AutoBuffer unicodeFileName(_filename.length() + 1); - MultiByteToWideChar(CP_ACP, 0, _filename.c_str(), -1, unicodeFileName.data(), (int)_filename.length() + 1); - if (SUCCEEDED(MFCreateSourceReaderFromURL(unicodeFileName.data(), attr.Get(), &videoFileSource))) + bool succeeded = false; + if (!_filename.empty()) + { + cv::AutoBuffer unicodeFileName(_filename.length() + 1); + MultiByteToWideChar(CP_ACP, 0, _filename.c_str(), -1, unicodeFileName.data(), (int)_filename.length() + 1); + succeeded = SUCCEEDED(MFCreateSourceReaderFromURL(unicodeFileName.data(), attr.Get(), &videoFileSource)); + } + else if (stream) + { + // TODO: implement read by chunks + // FIXIT: save stream in field + std::vector data; + data.resize((size_t)stream->seek(0, SEEK_END)); + stream->seek(0, SEEK_SET); + stream->read(data.data(), data.size()); + IStream* s = SHCreateMemStream(reinterpret_cast(data.data()), static_cast(data.size())); + if (!s) + return false; + + succeeded = SUCCEEDED(MFCreateMFByteStreamOnStream(s, &byteStream)); + if (!succeeded) + return false; + if (!SUCCEEDED(MFStartup(MF_VERSION))) + return false; + succeeded = SUCCEEDED(MFCreateSourceReaderFromByteStream(byteStream.Get(), attr.Get(), &videoFileSource)); + } + + if (succeeded) { isOpen = true; usedVideoSampleTime = 0; @@ -2375,12 +2401,24 @@ cv::Ptr cv::cvCreateCapture_MSMF( int index, const cv::VideoC return cv::Ptr(); } -cv::Ptr cv::cvCreateCapture_MSMF (const cv::String& filename, const cv::VideoCaptureParameters& params) +cv::Ptr cv::cvCreateCapture_MSMF(const cv::String& filename, const cv::VideoCaptureParameters& params) { cv::Ptr capture = cv::makePtr(); if (capture) { - capture->open(filename, ¶ms); + capture->open(filename, nullptr, ¶ms); + if (capture->isOpened()) + return capture; + } + return cv::Ptr(); +} + +cv::Ptr cv::cvCreateCapture_MSMF(const Ptr& stream, const cv::VideoCaptureParameters& params) +{ + cv::Ptr capture = cv::makePtr(); + if (capture) + { + capture->open(std::string(), stream, ¶ms); if (capture->isOpened()) return capture; } @@ -2707,7 +2745,7 @@ cv::Ptr cv::cvCreateVideoWriter_MSMF( const std::string& filen #include "plugin_api.hpp" #else #define CAPTURE_ABI_VERSION 1 -#define CAPTURE_API_VERSION 1 +#define CAPTURE_API_VERSION 2 #include "plugin_capture_api.hpp" #define WRITER_ABI_VERSION 1 #define WRITER_API_VERSION 1 @@ -2736,7 +2774,9 @@ CvResult CV_API_CALL cv_capture_open_with_params( cap = new CaptureT(); bool res; if (filename) - res = cap->open(std::string(filename), ¶meters); + { + res = cap->open(std::string(filename), nullptr, ¶meters); + } else res = cap->open(camera_index, ¶meters); if (res) @@ -2758,6 +2798,44 @@ CvResult CV_API_CALL cv_capture_open_with_params( return CV_ERROR_FAIL; } +static +CvResult CV_API_CALL cv_capture_open_buffer( + void* opaque, + long long(*read)(void* opaque, char* buffer, long long size), + long long(*seek)(void* opaque, long long offset, int way), + int* params, unsigned n_params, + CV_OUT CvPluginCapture* handle +) +{ + if (!handle) + return CV_ERROR_FAIL; + + *handle = NULL; + CaptureT* cap = 0; + try + { + cv::VideoCaptureParameters parameters(params, n_params); + cap = new CaptureT(); + bool res = cap->open(std::string(), makePtr(opaque, read, seek), ¶meters); + if (res) + { + *handle = (CvPluginCapture)cap; + return CV_ERROR_OK; + } + } + catch (const std::exception& e) + { + CV_LOG_WARNING(NULL, "MSMF: Exception is raised: " << e.what()); + } + catch (...) + { + CV_LOG_WARNING(NULL, "MSMF: Unknown C++ exception is raised"); + } + if (cap) + delete cap; + return CV_ERROR_FAIL; +} + static CvResult CV_API_CALL cv_capture_open(const char* filename, int camera_index, CV_OUT CvPluginCapture* handle) { @@ -3027,6 +3105,9 @@ static const OpenCV_VideoIO_Capture_Plugin_API capture_plugin_api = }, { /* 8*/cv::cv_capture_open_with_params, + }, + { + /* 9*/cv::cv_capture_open_buffer, } }; diff --git a/modules/videoio/src/cap_v4l.cpp b/modules/videoio/src/cap_v4l.cpp index 47b6181598..b7cc1e423d 100644 --- a/modules/videoio/src/cap_v4l.cpp +++ b/modules/videoio/src/cap_v4l.cpp @@ -919,8 +919,8 @@ bool CvCaptureCAM_V4L::open(const std::string & _deviceName) { CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << _deviceName << "): opening..."); FirstCapture = true; - width = DEFAULT_V4L_WIDTH; - height = DEFAULT_V4L_HEIGHT; + width = utils::getConfigurationParameterSizeT("OPENCV_VIDEOIO_V4L_DEFAULT_WIDTH", DEFAULT_V4L_WIDTH); + height = utils::getConfigurationParameterSizeT("OPENCV_VIDEOIO_V4L_DEFAULT_HEIGHT", DEFAULT_V4L_HEIGHT); width_set = height_set = 0; bufferSize = DEFAULT_V4L_BUFFERS; fps = DEFAULT_V4L_FPS; diff --git a/modules/videoio/src/plugin_capture_api.hpp b/modules/videoio/src/plugin_capture_api.hpp index 8f33d40219..c616bbc928 100644 --- a/modules/videoio/src/plugin_capture_api.hpp +++ b/modules/videoio/src/plugin_capture_api.hpp @@ -13,7 +13,7 @@ /// increased for backward-compatible changes, e.g. add new function /// Caller API <= Plugin API -> plugin is fully compatible /// Caller API > Plugin API -> plugin is not fully compatible, caller should use extra checks to use plugins with older API -#define CAPTURE_API_VERSION 1 +#define CAPTURE_API_VERSION 2 /// increased for incompatible changes, e.g. remove function argument /// Caller ABI == Plugin ABI -> plugin is compatible @@ -121,6 +121,29 @@ struct OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries CV_OUT CvPluginCapture* handle); }; // OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries +struct OpenCV_VideoIO_Capture_Plugin_API_v1_2_api_entries +{ + /** @brief Open video capture from buffer with parameters + + @param opaque A pointer to user data + @param read A pointer to a function that is called to reads @p size bytes to allocated @p buffer. Returns a number of bytes that were actually read + @param seek A pointer to a function that is called to move starting position inside the stream buffer. + @p offset is a number of bytes and @p way is one of the markers SEEK_SET, SEEK_CUR, SEEK_END. + Function returns an absolute current position in bytes. + @param params pointer on 2*n_params array of 'key,value' pairs + @param n_params number of passed parameters + @param[out] handle pointer on Capture handle + + @note API-CALL 9, API-Version == 2 + */ + CvResult (CV_API_CALL *Capture_open_stream)( + void* opaque, + long long(*read)(void* opaque, char* buffer, long long size), + long long(*seek)(void* opaque, long long offset, int way), + int* params, unsigned n_params, + CV_OUT CvPluginCapture* handle); +}; // OpenCV_VideoIO_Capture_Plugin_API_v1_2_api_entries + typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_0 { OpenCV_API_Header api_header; @@ -134,7 +157,17 @@ typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_1 struct OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries v1; } OpenCV_VideoIO_Capture_Plugin_API_v1_1; -#if CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 1 +typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_2 +{ + OpenCV_API_Header api_header; + struct OpenCV_VideoIO_Capture_Plugin_API_v1_0_api_entries v0; + struct OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries v1; + struct OpenCV_VideoIO_Capture_Plugin_API_v1_2_api_entries v2; +} OpenCV_VideoIO_Capture_Plugin_API_v1_2; + +#if CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 2 +typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_2 OpenCV_VideoIO_Capture_Plugin_API; +#elif CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 1 typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_1 OpenCV_VideoIO_Capture_Plugin_API; #elif CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 0 typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_0 OpenCV_VideoIO_Capture_Plugin_API; diff --git a/modules/videoio/src/videoio_registry.cpp b/modules/videoio/src/videoio_registry.cpp index 641c1f6850..af43122e99 100644 --- a/modules/videoio/src/videoio_registry.cpp +++ b/modules/videoio/src/videoio_registry.cpp @@ -47,7 +47,12 @@ namespace { #define DECLARE_STATIC_BACKEND(cap, name, mode, createCaptureFile, createCaptureCamera, createWriter) \ { \ - cap, (BackendMode)(mode), 1000, name, createBackendFactory(createCaptureFile, createCaptureCamera, createWriter) \ + cap, (BackendMode)(mode), 1000, name, createBackendFactory(createCaptureFile, createCaptureCamera, 0, createWriter) \ +}, + +#define DECLARE_STATIC_BACKEND_WITH_STREAM_SUPPORT(cap, name, mode, createCaptureStream) \ +{ \ + cap, (BackendMode)(mode), 1000, name, createBackendFactory(0, 0, createCaptureStream, 0) \ }, /** Ordering guidelines: @@ -62,8 +67,9 @@ static const struct VideoBackendInfo builtin_backends[] = { #ifdef HAVE_FFMPEG DECLARE_STATIC_BACKEND(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_FILENAME | MODE_WRITER, cvCreateFileCapture_FFMPEG_proxy, 0, cvCreateVideoWriter_FFMPEG_proxy) + DECLARE_STATIC_BACKEND_WITH_STREAM_SUPPORT(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_STREAM, cvCreateStreamCapture_FFMPEG_proxy) #elif defined(ENABLE_PLUGINS) || defined(HAVE_FFMPEG_WRAPPER) - DECLARE_DYNAMIC_BACKEND(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_FILENAME | MODE_WRITER) + DECLARE_DYNAMIC_BACKEND(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_FILENAME | MODE_CAPTURE_BY_STREAM | MODE_WRITER) #endif #ifdef HAVE_GSTREAMER @@ -90,8 +96,9 @@ static const struct VideoBackendInfo builtin_backends[] = #ifdef HAVE_MSMF DECLARE_STATIC_BACKEND(CAP_MSMF, "MSMF", MODE_CAPTURE_ALL | MODE_WRITER, cvCreateCapture_MSMF, cvCreateCapture_MSMF, cvCreateVideoWriter_MSMF) + DECLARE_STATIC_BACKEND_WITH_STREAM_SUPPORT(CAP_MSMF, "MSMF", MODE_CAPTURE_BY_STREAM, cvCreateCapture_MSMF) #elif defined(ENABLE_PLUGINS) && defined(_WIN32) - DECLARE_DYNAMIC_BACKEND(CAP_MSMF, "MSMF", MODE_CAPTURE_ALL | MODE_WRITER) + DECLARE_DYNAMIC_BACKEND(CAP_MSMF, "MSMF", MODE_CAPTURE_ALL | MODE_CAPTURE_BY_STREAM | MODE_WRITER) #endif #ifdef HAVE_DSHOW @@ -324,6 +331,17 @@ public: } return result; } + inline std::vector getAvailableBackends_CaptureByStream() const + { + std::vector result; + for (size_t i = 0; i < enabledBackends.size(); i++) + { + const VideoBackendInfo& info = enabledBackends[i]; + if (info.mode & MODE_CAPTURE_BY_STREAM) + result.push_back(info); + } + return result; + } inline std::vector getAvailableBackends_Writer() const { std::vector result; @@ -351,6 +369,11 @@ std::vector getAvailableBackends_CaptureByFilename() const std::vector result = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByFilename(); return result; } +std::vector getAvailableBackends_CaptureByStream() +{ + const std::vector result = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByStream(); + return result; +} std::vector getAvailableBackends_Writer() { const std::vector result = VideoBackendRegistry::getInstance().getAvailableBackends_Writer(); @@ -421,6 +444,15 @@ std::vector getStreamBackends() } +std::vector getStreamBufferedBackends() +{ + const std::vector backends = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByStream(); + std::vector result; + for (size_t i = 0; i < backends.size(); i++) + result.push_back((VideoCaptureAPIs)backends[i].id); + return result; +} + std::vector getWriterBackends() { const std::vector backends = VideoBackendRegistry::getInstance().getAvailableBackends_Writer(); @@ -498,6 +530,24 @@ std::string getStreamBackendPluginVersion(VideoCaptureAPIs api, CV_Error(Error::StsError, "Unknown or wrong backend ID"); } +std::string getStreamBufferedBackendPluginVersion(VideoCaptureAPIs api, + CV_OUT int& version_ABI, + CV_OUT int& version_API +) +{ + const std::vector backends = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByStream(); + for (size_t i = 0; i < backends.size(); i++) + { + const VideoBackendInfo& info = backends[i]; + if (api == info.id) + { + CV_Assert(!info.backendFactory.empty()); + CV_Assert(!info.backendFactory->isBuiltIn()); + return getCapturePluginVersion(info.backendFactory, version_ABI, version_API); + } + } + CV_Error(Error::StsError, "Unknown or wrong backend ID"); +} /** @brief Returns description and ABI/API version of videoio plugin's writer interface */ std::string getWriterBackendPluginVersion(VideoCaptureAPIs api, diff --git a/modules/videoio/src/videoio_registry.hpp b/modules/videoio/src/videoio_registry.hpp index 3a81df84b5..e2fe579a2f 100644 --- a/modules/videoio/src/videoio_registry.hpp +++ b/modules/videoio/src/videoio_registry.hpp @@ -14,6 +14,7 @@ namespace cv enum BackendMode { MODE_CAPTURE_BY_INDEX = 1 << 0, //!< device index MODE_CAPTURE_BY_FILENAME = 1 << 1, //!< filename or device path (v4l2) + MODE_CAPTURE_BY_STREAM = 1 << 2, //!< data stream MODE_WRITER = 1 << 4, //!< writer MODE_CAPTURE_ALL = MODE_CAPTURE_BY_INDEX + MODE_CAPTURE_BY_FILENAME, @@ -38,6 +39,7 @@ namespace videoio_registry { std::vector getAvailableBackends_CaptureByIndex(); std::vector getAvailableBackends_CaptureByFilename(); +std::vector getAvailableBackends_CaptureByStream(); std::vector getAvailableBackends_Writer(); bool checkDeprecatedBackend(int api); diff --git a/modules/videoio/test/test_images.cpp b/modules/videoio/test/test_images.cpp index ccf507a50d..c3782f2c5d 100644 --- a/modules/videoio/test/test_images.cpp +++ b/modules/videoio/test/test_images.cpp @@ -285,6 +285,21 @@ TEST(videoio_images, extract_pattern) EXPECT_THROW(cv::icvExtractPattern("1.png", NULL), cv::Exception); } +TEST(videoio_images, bug_26457) +{ + ImageCollection col; + col.generate(1u); + ASSERT_EQ(col.getCount(), 1u); + + VideoCapture cap(col.getFirstFilename(), CAP_IMAGES); + ASSERT_TRUE(cap.isOpened()); + + Mat img; + const bool read_res = cap.read(img); + EXPECT_TRUE(read_res); + EXPECT_MAT_N_DIFF(img, col.getFirstFrame(), 0); +} + // TODO: should writer overwrite files? // TODO: is clamping good for seeking? // TODO: missing files? E.g. 3, 4, 6, 7, 8 (should it finish OR jump over OR return empty frame?) diff --git a/modules/videoio/test/test_video_io.cpp b/modules/videoio/test/test_video_io.cpp index 370531770f..4efbdc308d 100644 --- a/modules/videoio/test/test_video_io.cpp +++ b/modules/videoio/test/test_video_io.cpp @@ -3,6 +3,7 @@ // of this distribution and at http://opencv.org/license.html. #include "test_precomp.hpp" +#include "opencv2/core/utils/filesystem.hpp" namespace opencv_test { @@ -47,6 +48,14 @@ public: throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref)); if (cvtest::skipUnstableTests && apiPref == CAP_MSMF && (ext == "h264" || ext == "h265" || ext == "mpg")) throw SkipTestException("Unstable MSMF test"); +#ifdef __linux__ + if (cvtest::skipUnstableTests && apiPref == CAP_GSTREAMER && + (ext == "avi" || ext == "mkv") && + (video_file.find("MPEG") != std::string::npos)) + { + throw SkipTestException("Unstable GSTREAMER test"); + } +#endif writeVideo(); VideoCapture cap; ASSERT_NO_THROW(cap.open(video_file, apiPref)); @@ -850,6 +859,13 @@ TEST_P(videowriter_acceleration, write) std::string backend_name = cv::videoio_registry::getBackendName(backend); if (!videoio_registry::hasBackend(backend)) throw SkipTestException(cv::String("Backend is not available/disabled: ") + backend_name); +#ifdef __linux__ + if (cvtest::skipUnstableTests && backend == CAP_GSTREAMER && + (extension == "mkv") && (codecid == "MPEG")) + { + throw SkipTestException("Unstable GSTREAMER test"); + } +#endif const Size sz(640, 480); const int frameNum = 15; @@ -1007,4 +1023,133 @@ INSTANTIATE_TEST_CASE_P(videoio, videowriter_acceleration, testing::Combine( testing::ValuesIn(hw_use_umat) )); +class BufferStream : public cv::IStreamReader +{ +public: + BufferStream(const std::string& filename) + { + Ptr file = makePtr(); + file->open(filename.c_str(), std::ios::in | std::ios::binary); + stream = file; + } + + BufferStream(const Ptr& _stream) : stream(_stream) {} + + long long read(char* buffer, long long size) CV_OVERRIDE + { + auto result = stream->sgetn(buffer, size); + return result; + } + + long long seek(long long offset, int way) CV_OVERRIDE + { + auto result = stream->pubseekoff(offset, way == SEEK_SET ? std::ios_base::beg : (way == SEEK_END ? std::ios_base::end : std::ios_base::cur)); + return result; + } + +private: + Ptr stream; +}; + +typedef testing::TestWithParam> stream_capture; +TEST_P(stream_capture, read) +{ + std::string ext = get<0>(GetParam()); + VideoCaptureAPIs apiPref = get<1>(GetParam()); + std::vector supportedAPIs = videoio_registry::getStreamBufferedBackends(); + if (!videoio_registry::hasBackend(apiPref)) + throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref)); + if (std::find(supportedAPIs.begin(), supportedAPIs.end(), apiPref) == supportedAPIs.end()) + throw SkipTestException(cv::String("Backend is not supported: ") + cv::videoio_registry::getBackendName(apiPref)); + if (cvtest::skipUnstableTests && apiPref == CAP_MSMF && (ext == "h264" || ext == "h265" || ext == "mpg")) + throw SkipTestException("Unstable MSMF test"); + + if (!videoio_registry::isBackendBuiltIn(apiPref)) + { + int pluginABI, pluginAPI; + videoio_registry::getStreamBufferedBackendPluginVersion(apiPref, pluginABI, pluginAPI); + if (pluginABI < 1 || (pluginABI == 1 && pluginAPI < 2)) + throw SkipTestException(format("Buffer capture supported since ABI/API = 1/2. %s plugin is %d/%d", + cv::videoio_registry::getBackendName(apiPref).c_str(), pluginABI, pluginAPI)); + } + + VideoCapture cap; + String video_file = BunnyParameters::getFilename(String(".") + ext); + ASSERT_TRUE(utils::fs::exists(video_file)); + EXPECT_NO_THROW(cap.open(makePtr(video_file), apiPref, {})); + ASSERT_TRUE(cap.isOpened()); + + const int numFrames = 10; + Mat frames[numFrames]; + Mat hardCopies[numFrames]; + for(int i = 0; i < numFrames; i++) + { + ASSERT_NO_THROW(cap >> frames[i]); + EXPECT_FALSE(frames[i].empty()); + hardCopies[i] = frames[i].clone(); + } + + for(int i = 0; i < numFrames; i++) + EXPECT_EQ(0, cv::norm(frames[i], hardCopies[i], NORM_INF)) << i; +} +INSTANTIATE_TEST_CASE_P(videoio, stream_capture, + testing::Combine( + testing::ValuesIn(bunny_params), + testing::ValuesIn(backend_params))); + +// This test for stream input for container format (See test_ffmpeg/videoio_container.read test) +typedef testing::TestWithParam stream_capture_ffmpeg; +TEST_P(stream_capture_ffmpeg, raw) +{ + std::string ext = GetParam(); + VideoCaptureAPIs apiPref = CAP_FFMPEG; + std::vector supportedAPIs = videoio_registry::getStreamBufferedBackends(); + if (!videoio_registry::hasBackend(apiPref)) + throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref)); + if (std::find(supportedAPIs.begin(), supportedAPIs.end(), apiPref) == supportedAPIs.end()) + throw SkipTestException(cv::String("Backend is not supported: ") + cv::videoio_registry::getBackendName(apiPref)); + + if (!videoio_registry::isBackendBuiltIn(apiPref)) + { + int pluginABI, pluginAPI; + videoio_registry::getStreamBufferedBackendPluginVersion(apiPref, pluginABI, pluginAPI); + if (pluginABI < 1 || (pluginABI == 1 && pluginAPI < 2)) + throw SkipTestException(format("Buffer capture supported since ABI/API = 1/2. %s plugin is %d/%d", + cv::videoio_registry::getBackendName(apiPref).c_str(), pluginABI, pluginAPI)); + } + + VideoCapture container; + String video_file = BunnyParameters::getFilename(String(".") + ext); + ASSERT_TRUE(utils::fs::exists(video_file)); + EXPECT_NO_THROW(container.open(video_file, apiPref, {CAP_PROP_FORMAT, -1})); + ASSERT_TRUE(container.isOpened()); + ASSERT_EQ(-1.f, container.get(CAP_PROP_FORMAT)); + + auto stream = std::make_shared(); + Mat keyFrame; + while (true) + { + container >> keyFrame; + if (keyFrame.empty()) + break; + stream->sputn(keyFrame.ptr(), keyFrame.total()); + } + + VideoCapture capRef(video_file); + VideoCapture capStream; + EXPECT_NO_THROW(capStream.open(makePtr(stream), apiPref, {})); + ASSERT_TRUE(capStream.isOpened()); + + const int numFrames = 10; + Mat frameRef, frame; + for (int i = 0; i < numFrames; ++i) + { + capRef >> frameRef; + ASSERT_NO_THROW(capStream >> frame); + EXPECT_FALSE(frame.empty()); + EXPECT_EQ(0, cv::norm(frame, frameRef, NORM_INF)) << i; + } +} +INSTANTIATE_TEST_CASE_P(videoio, stream_capture_ffmpeg, testing::Values("h264", "h265", "mjpg.avi")); + } // namespace diff --git a/platforms/android/aar-template/OpenCV/build.gradle.template b/platforms/android/aar-template/OpenCV/build.gradle.template index b12802063b..192a46877a 100644 --- a/platforms/android/aar-template/OpenCV/build.gradle.template +++ b/platforms/android/aar-template/OpenCV/build.gradle.template @@ -7,6 +7,7 @@ plugins { android { namespace 'org.opencv' compileSdk ${COMPILE_SDK} + ndkVersion "${NDK_VERSION}" defaultConfig { minSdk ${MIN_SDK} diff --git a/platforms/android/aar-template/build.gradle b/platforms/android/aar-template/build.gradle index 280dab5da7..538635ace5 100644 --- a/platforms/android/aar-template/build.gradle +++ b/platforms/android/aar-template/build.gradle @@ -1,5 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.library' version '7.3.0' apply false + id 'com.android.library' version '8.6.0' apply false id 'org.jetbrains.kotlin.android' version '1.8.20' apply false } diff --git a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties index c4f47b9a2e..9300a54eba 100644 --- a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties +++ b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Jul 10 11:57:38 SGT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/platforms/android/build_java_shared_aar.py b/platforms/android/build_java_shared_aar.py index 8e17a7a4d6..5452ac5d2c 100755 --- a/platforms/android/build_java_shared_aar.py +++ b/platforms/android/build_java_shared_aar.py @@ -47,6 +47,14 @@ def get_opencv_version(opencv_sdk_path): revision = re.search(r'^#define\W+CV_VERSION_REVISION\W+(\d+)$', data, re.MULTILINE).group(1) return "%(major)s.%(minor)s.%(revision)s" % locals() +def get_ndk_version(ndk_path): + props_path = path.join(ndk_path, "source.properties") + with open(props_path, "rt") as f: + data = f.read() + version = re.search(r'Pkg\.Revision\W+=\W+(\d+\.\d+\.\d+)', data).group(1) + return version.strip() + + def get_compiled_aar_path(path1, path2): if path.exists(path1): return path1 @@ -70,6 +78,8 @@ def cleanup(paths_to_remove): def main(args): opencv_version = get_opencv_version(args.opencv_sdk_path) + ndk_version = get_ndk_version(args.ndk_location) + print("Detected ndk_version:", ndk_version) abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs")) lib_name = "opencv_java" + opencv_version.split(".")[0] final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version) @@ -90,6 +100,7 @@ def main(args): "LIB_TYPE": "c++_shared", "PACKAGE_NAME": MAVEN_PACKAGE_NAME, "OPENCV_VERSION": opencv_version, + "NDK_VERSION": ndk_version, "COMPILE_SDK": args.android_compile_sdk, "MIN_SDK": args.android_min_sdk, "TARGET_SDK": args.android_target_sdk, @@ -170,10 +181,10 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Builds AAR with Java and shared C++ libs from OpenCV SDK") parser.add_argument('opencv_sdk_path') - parser.add_argument('--android_compile_sdk', default="31") + parser.add_argument('--android_compile_sdk', default="34") parser.add_argument('--android_min_sdk', default="21") - parser.add_argument('--android_target_sdk', default="31") - parser.add_argument('--java_version', default="1_8") + parser.add_argument('--android_target_sdk', default="34") + parser.add_argument('--java_version', default="17") parser.add_argument('--ndk_location', default="") parser.add_argument('--cmake_location', default="") parser.add_argument('--offline', action="store_true", help="Force Gradle use offline mode") diff --git a/platforms/android/build_sdk.py b/platforms/android/build_sdk.py index 83f40904d5..3a920c78f1 100755 --- a/platforms/android/build_sdk.py +++ b/platforms/android/build_sdk.py @@ -285,7 +285,10 @@ class Builder: cmd.append(self.opencvdir) execute(cmd) # full parallelism for C++ compilation tasks - execute([self.ninja_path, "opencv_modules"]) + build_targets = ["opencv_modules"] + if do_install: + build_targets.append("opencv_tests") + execute([self.ninja_path, *build_targets]) # limit parallelism for building samples (avoid huge memory consumption) if self.no_samples_build: execute([self.ninja_path, "install" if (self.debug_info or self.debug) else "install/strip"]) @@ -300,6 +303,14 @@ class Builder: classpaths.append(os.path.join(dir, f)) srcdir = os.path.join(self.resultdest, 'sdk', 'java', 'src') dstdir = self.docdest + # HACK: create stubs for auto-generated files to satisfy imports + with open(os.path.join(srcdir, 'org', 'opencv', 'BuildConfig.java'), 'wt') as fs: + fs.write("package org.opencv;\n public class BuildConfig {\n}") + fs.close() + with open(os.path.join(srcdir, 'org', 'opencv', 'R.java'), 'wt') as fs: + fs.write("package org.opencv;\n public class R {\n}") + fs.close() + # synchronize with modules/java/jar/build.xml.in shutil.copy2(os.path.join(SCRIPT_DIR, '../../doc/mymath.js'), dstdir) cmd = [ @@ -327,9 +338,12 @@ class Builder: '-bottom', 'Generated on %s / OpenCV %s' % (time.strftime("%Y-%m-%d %H:%M:%S"), self.opencv_version), "-d", dstdir, "-classpath", ":".join(classpaths), - '-subpackages', 'org.opencv', + '-subpackages', 'org.opencv' ] execute(cmd) + # HACK: remove temporary files needed to satisfy javadoc imports + os.remove(os.path.join(srcdir, 'org', 'opencv', 'BuildConfig.java')) + os.remove(os.path.join(srcdir, 'org', 'opencv', 'R.java')) def gather_results(self): # Copy all files diff --git a/platforms/android/build_static_aar.py b/platforms/android/build_static_aar.py index 0ab90b66a8..6345aecf0e 100755 --- a/platforms/android/build_static_aar.py +++ b/platforms/android/build_static_aar.py @@ -7,7 +7,7 @@ import os import shutil import subprocess -from build_java_shared_aar import cleanup, fill_template, get_compiled_aar_path, get_opencv_version +from build_java_shared_aar import cleanup, fill_template, get_compiled_aar_path, get_opencv_version, get_ndk_version ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template") @@ -103,6 +103,8 @@ def convert_deps_list_to_prefab(linked_libs, opencv_libs, external_libs): def main(args): opencv_version = get_opencv_version(args.opencv_sdk_path) + ndk_version = get_ndk_version(args.ndk_location) + print("Detected ndk_version:", ndk_version) abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs")) final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version) sdk_dir = args.opencv_sdk_path @@ -121,6 +123,7 @@ def main(args): "LIB_TYPE": "c++_static", "PACKAGE_NAME": MAVEN_PACKAGE_NAME, "OPENCV_VERSION": opencv_version, + "NDK_VERSION": ndk_version, "COMPILE_SDK": args.android_compile_sdk, "MIN_SDK": args.android_min_sdk, "TARGET_SDK": args.android_target_sdk, @@ -245,9 +248,9 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Builds AAR with static C++ libs from OpenCV SDK") parser.add_argument('opencv_sdk_path') - parser.add_argument('--android_compile_sdk', default="31") + parser.add_argument('--android_compile_sdk', default="34") parser.add_argument('--android_min_sdk', default="21") - parser.add_argument('--android_target_sdk', default="31") + parser.add_argument('--android_target_sdk', default="34") parser.add_argument('--java_version', default="1_8") parser.add_argument('--ndk_location', default="") parser.add_argument('--cmake_location', default="") diff --git a/platforms/android/default.config.py b/platforms/android/default.config.py new file mode 100644 index 0000000000..9c7b9ad0ef --- /dev/null +++ b/platforms/android/default.config.py @@ -0,0 +1,6 @@ +ABIs = [ + ABI("2", "armeabi-v7a", None, 21, cmake_vars=dict(ANDROID_ABI='armeabi-v7a with NEON')), + ABI("3", "arm64-v8a", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON')), + ABI("5", "x86_64", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON')), + ABI("4", "x86", None, 21), +] diff --git a/platforms/js/opencv_js.config.py b/platforms/js/opencv_js.config.py index 7298c76c34..18891f573f 100644 --- a/platforms/js/opencv_js.config.py +++ b/platforms/js/opencv_js.config.py @@ -16,84 +16,105 @@ core = { imgproc = { '': [ - 'Canny', - 'GaussianBlur', - 'Laplacian', - 'HoughLines', - 'HoughLinesP', - 'HoughCircles', - 'Scharr', - 'Sobel', 'adaptiveThreshold', + 'applyColorMap', 'approxPolyDP', + 'approxPolyN', 'arcLength', + 'arrowedLine', 'bilateralFilter', + 'blendLinear', 'blur', 'boundingRect', 'boxFilter', 'calcBackProject', 'calcHist', + 'Canny', 'circle', + 'clipLine', 'compareHist', 'connectedComponents', 'connectedComponentsWithStats', 'contourArea', + 'convertMaps', 'convexHull', 'convexityDefects', 'cornerHarris', 'cornerMinEigenVal', 'createCLAHE', + 'createHanningWindow', 'createLineSegmentDetector', 'cvtColor', 'demosaicing', 'dilate', 'distanceTransform', 'distanceTransformWithLabels', + 'divSpectrums', 'drawContours', + 'drawMarker', 'ellipse', 'ellipse2Poly', 'equalizeHist', 'erode', + 'fillConvexPoly', + 'fillPoly', 'filter2D', 'findContours', + 'findContoursLinkRuns', 'fitEllipse', + 'fitEllipseAMS', + 'fitEllipseDirect', 'fitLine', 'floodFill', + 'GaussianBlur', 'getAffineTransform', + 'getFontScaleFromHeight', 'getPerspectiveTransform', + 'getRectSubPix', 'getRotationMatrix2D', 'getStructuringElement', 'goodFeaturesToTrack', 'grabCut', - #'initUndistortRectifyMap', # 4.x: moved to calib3d + 'HoughLines', + 'HoughLinesP', + 'HoughCircles', + 'HuMoments', 'integral', 'integral2', + 'intersectConvexConvex', + 'invertAffineTransform', 'isContourConvex', + 'Laplacian', 'line', 'matchShapes', 'matchTemplate', 'medianBlur', 'minAreaRect', 'minEnclosingCircle', + 'minEnclosingTriangle', 'moments', 'morphologyEx', 'pointPolygonTest', + 'polylines', + 'preCornerDetect', 'putText', 'pyrDown', 'pyrUp', 'rectangle', 'remap', 'resize', + 'rotatedRectangleIntersection', + 'Scharr', 'sepFilter2D', + 'Sobel', + 'spatialGradient', + 'sqrBoxFilter', + 'stackBlur', 'threshold', - #'undistort', # 4.x: moved to calib3d 'warpAffine', 'warpPerspective', 'warpPolar', 'watershed', - 'fillPoly', - 'fillConvexPoly', - 'polylines', ], 'CLAHE': ['apply', 'collectGarbage', 'getClipLimit', 'getTilesGridSize', 'setClipLimit', 'setTilesGridSize'], 'segmentation_IntelligentScissorsMB': [ @@ -166,6 +187,7 @@ photo = {'': ['createAlignMTB', 'createCalibrateDebevec', 'createCalibrateRobert 'createMergeDebevec', 'createMergeMertens', 'createMergeRobertson', \ 'createTonemapDrago', 'createTonemapMantiuk', 'createTonemapReinhard', 'inpaint'], 'CalibrateCRF': ['process'], + 'AlignExposures': ['process'], 'AlignMTB' : ['calculateShift', 'shiftMat', 'computeBitmaps', 'getMaxBits', 'setMaxBits', \ 'getExcludeRange', 'setExcludeRange', 'getCut', 'setCut'], 'CalibrateDebevec' : ['getLambda', 'setLambda', 'getSamples', 'setSamples', 'getRandom', 'setRandom'], @@ -207,6 +229,7 @@ calib = { 'fisheye_initUndistortRectifyMap', 'fisheye_projectPoints', ], + 'UsacParams': ['UsacParams'] } diff --git a/samples/android/build.gradle.in b/samples/android/build.gradle.in index d159899132..a06c42187d 100644 --- a/samples/android/build.gradle.in +++ b/samples/android/build.gradle.in @@ -99,7 +99,7 @@ gradle.afterProject { project -> // Android Gradle Plugin (AGP) 3.5+ is required // https://github.com/android/ndk-samples/wiki/Configure-NDK-Path def isNdkVersionSupported = project.android.metaClass.getProperties().find { it.name == 'ndkVersion' } != null - if ((false || opencv_strict_build_configuration) && isNdkVersionSupported) { + if (opencv_strict_build_configuration && isNdkVersionSupported) { gradle.println("Override ndkVersion for the project ${project.name}") project.android { ndkVersion '@ANDROID_NDK_REVISION@' diff --git a/samples/android/tutorial-2-mixedprocessing/build.gradle.in b/samples/android/tutorial-2-mixedprocessing/build.gradle.in index a156f42240..0e8f5b2a9f 100644 --- a/samples/android/tutorial-2-mixedprocessing/build.gradle.in +++ b/samples/android/tutorial-2-mixedprocessing/build.gradle.in @@ -14,10 +14,11 @@ android { cmake { if (gradle.opencv_source == "sdk_path") { arguments "-DOpenCV_DIR=" + project(':opencv').projectDir + "/@ANDROID_PROJECT_JNI_PATH@", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DOPENCV_FROM_SDK=TRUE"@OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ - } else { arguments "-DOPENCV_VERSION_MAJOR=@OPENCV_VERSION_MAJOR@", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DOPENCV_FROM_SDK=FALSE"@OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ } targets "mixed_sample" diff --git a/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt b/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt index 63356c3f10..6cfb1ca981 100644 --- a/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt +++ b/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt @@ -14,6 +14,14 @@ endif() message(STATUS "ANDROID_ABI=${ANDROID_ABI}") find_package(OpenCV REQUIRED COMPONENTS ${ANDROID_OPENCV_COMPONENTS}) +# For 16k pages support with NDK prior 27 +# Details: https://developer.android.com/guide/practices/page-sizes?hl=en +if(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES) + if(ANDROID_ABI STREQUAL arm64-v8a OR ANDROID_ABI STREQUAL x86_64) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + endif() +endif() + file(GLOB srcs *.cpp *.c) file(GLOB hdrs *.hpp *.h) diff --git a/samples/android/tutorial-4-opencl/build.gradle.in b/samples/android/tutorial-4-opencl/build.gradle.in index 8eeb12b17d..8118bd1d19 100644 --- a/samples/android/tutorial-4-opencl/build.gradle.in +++ b/samples/android/tutorial-4-opencl/build.gradle.in @@ -15,11 +15,13 @@ android { if (gradle.opencv_source == "sdk_path") { arguments "-DOpenCV_DIR=" + project(':opencv').projectDir + "/@ANDROID_PROJECT_JNI_PATH@", "-DOPENCV_FROM_SDK=TRUE", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DANDROID_OPENCL_SDK=@ANDROID_OPENCL_SDK@" @OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ } else { arguments "-DOPENCV_VERSION_MAJOR=@OPENCV_VERSION_MAJOR@", "-DOPENCV_FROM_SDK=FALSE", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DANDROID_OPENCL_SDK=@ANDROID_OPENCL_SDK@" @OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ } targets "JNIpart" diff --git a/samples/android/tutorial-4-opencl/jni/CMakeLists.txt b/samples/android/tutorial-4-opencl/jni/CMakeLists.txt index 1cc2115579..5eab6fe4f3 100644 --- a/samples/android/tutorial-4-opencl/jni/CMakeLists.txt +++ b/samples/android/tutorial-4-opencl/jni/CMakeLists.txt @@ -18,6 +18,14 @@ find_package(OpenCL QUIET) file(GLOB srcs *.cpp *.c) file(GLOB hdrs *.hpp *.h) +# For 16k pages support with NDK prior 27 +# Details: https://developer.android.com/guide/practices/page-sizes?hl=en +if(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES) + if(ANDROID_ABI STREQUAL arm64-v8a OR ANDROID_ABI STREQUAL x86_64) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + endif() +endif() + include_directories("${CMAKE_CURRENT_LIST_DIR}") add_library(${target} SHARED ${srcs} ${hdrs}) diff --git a/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java b/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java index d4faea51b2..598d52362b 100644 --- a/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java +++ b/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java @@ -57,7 +57,6 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList private VideoWriter mVideoWriter = null; private VideoCapture mVideoCapture = null; private Mat mVideoFrame; - private Mat mRenderFrame; public RecorderActivity() { Log.i(TAG, "Instantiated new " + this.getClass()); @@ -122,7 +121,6 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList mTriggerButton.setText("Start Camera"); mVideoFrame.release(); - mRenderFrame.release(); } @Override @@ -132,7 +130,6 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList super.onResume(); mVideoFrame = new Mat(); - mRenderFrame = new Mat(); changeStatus(); } @@ -167,16 +164,15 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList { Log.d(TAG, "Camera frame arrived"); - Mat rgbMat = inputFrame.rgba(); + mVideoFrame = inputFrame.rgba(); - Log.d(TAG, "Size: " + rgbMat.width() + "x" + rgbMat.height()); + Log.d(TAG, "Size: " + mVideoFrame.width() + "x" + mVideoFrame.height()); if (mVideoWriter != null && mVideoWriter.isOpened()) { - Imgproc.cvtColor(rgbMat, mVideoFrame, Imgproc.COLOR_RGBA2BGR); mVideoWriter.write(mVideoFrame); } - return rgbMat; + return mVideoFrame; } @Override @@ -294,12 +290,16 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList mVideoCapture = new VideoCapture(mVideoFilename, Videoio.CAP_OPENCV_MJPEG); } - if (!mVideoCapture.isOpened()) { + if (mVideoCapture == null || !mVideoCapture.isOpened()) { Log.e(TAG, "Can't open video"); Toast.makeText(this, "Can't open file " + mVideoFilename, Toast.LENGTH_SHORT).show(); return false; } + if (!mUseBuiltInMJPG){ + mVideoCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','4')); + } + Toast.makeText(this, "Starting playback from file " + mVideoFilename, Toast.LENGTH_SHORT).show(); mPlayerThread = new Runnable() { @@ -315,11 +315,14 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList } return; } - // VideoCapture with CAP_ANDROID generates RGB frames instead of BGR - // https://github.com/opencv/opencv/issues/24687 - Imgproc.cvtColor(mVideoFrame, mRenderFrame, mUseBuiltInMJPG ? Imgproc.COLOR_BGR2RGBA: Imgproc.COLOR_RGB2RGBA); - Bitmap bmp = Bitmap.createBitmap(mRenderFrame.cols(), mRenderFrame.rows(), Bitmap.Config.ARGB_8888); - Utils.matToBitmap(mRenderFrame, bmp); + + // MJPEG codec will output BGR only. So we need to convert to RGBA. + if (mUseBuiltInMJPG) { + Imgproc.cvtColor(mVideoFrame, mVideoFrame, Imgproc.COLOR_BGR2RGBA); + } + + Bitmap bmp = Bitmap.createBitmap(mVideoFrame.cols(), mVideoFrame.rows(), Bitmap.Config.ARGB_8888); + Utils.matToBitmap(mVideoFrame, bmp); mImageView.setImageBitmap(bmp); Handler h = new Handler(); h.postDelayed(this, 33); diff --git a/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp b/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp index 82bbb3743a..3650b15dea 100644 --- a/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp +++ b/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp @@ -27,12 +27,12 @@ Mat dst; * @function on_trackbar * @brief Callback for trackbar */ -static void on_trackbar( int, void* ) -{ - alpha = (double) alpha_slider/alpha_slider_max ; - beta = ( 1.0 - alpha ); - addWeighted( src1, alpha, src2, beta, 0.0, dst); - imshow( "Linear Blend", dst ); +static void on_trackbar(int pos, void* userdata) { + (void) userdata; + alpha = (double)pos / alpha_slider_max; + beta = (1.0 - alpha); + addWeighted(src1, alpha, src2, beta, 0.0, dst); + imshow("Linear Blend", dst); } //![on_trackbar] @@ -40,34 +40,35 @@ static void on_trackbar( int, void* ) * @function main * @brief Main function */ -int main( void ) +int main(void) { - //![load] - /// Read images ( both have to be of the same size and type ) - src1 = imread( samples::findFile("LinuxLogo.jpg") ); - src2 = imread( samples::findFile("WindowsLogo.jpg") ); - //![load] + //![load] + /// Read images (both must be of the same size and type) + src1 = imread(samples::findFile("LinuxLogo.jpg")); + src2 = imread(samples::findFile("WindowsLogo.jpg")); + //![load] - if( src1.empty() ) { cout << "Error loading src1 \n"; return -1; } - if( src2.empty() ) { cout << "Error loading src2 \n"; return -1; } + if (src1.empty()) { cout << "Error loading src1 \n"; return -1; } + if (src2.empty()) { cout << "Error loading src2 \n"; return -1; } - /// Initialize values - alpha_slider = 0; + // Initialize trackbar value + alpha_slider = 0; - //![window] - namedWindow("Linear Blend", WINDOW_AUTOSIZE); // Create Window - //![window] + //![window] + namedWindow("Linear Blend", WINDOW_AUTOSIZE); //Create Window + //![window] - //![create_trackbar] - char TrackbarName[50]; - snprintf( TrackbarName, sizeof(TrackbarName), "Alpha x %d", alpha_slider_max ); - createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar ); - //![create_trackbar] + //![create_trackbar] + char TrackbarName[50]; + snprintf(TrackbarName, sizeof(TrackbarName), "Alpha x %d", alpha_slider_max); + // Example userdata: Pass a pointer to an integer as userdata + createTrackbar(TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar); + //![create_trackbar] - /// Show some stuff - on_trackbar( alpha_slider, 0 ); + /// Show initial result + on_trackbar(alpha_slider, nullptr); - /// Wait until user press some key - waitKey(0); - return 0; + /// Wait for user input + waitKey(0); + return 0; } diff --git a/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp b/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp index 5d1736a77a..91d4266c5a 100644 --- a/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp +++ b/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp @@ -15,22 +15,27 @@ using namespace std; /// Global variables /** General variables */ -Mat src, edges; +Mat src, canny_edge, sobel_edge; Mat src_gray; -Mat standard_hough, probabilistic_hough; +Mat standard_hough, probabilistic_hough, weighted_hough; int min_threshold = 50; int max_trackbar = 150; +int weightedhough_max_trackbar = 100000; const char* standard_name = "Standard Hough Lines Demo"; const char* probabilistic_name = "Probabilistic Hough Lines Demo"; +const char* weighted_name = "Weighted Hough Lines Demo"; int s_trackbar = max_trackbar; int p_trackbar = max_trackbar; +int e_trackbar = 60; +int w_trackbar = 60000; /// Function Headers void help(); void Standard_Hough( int, void* ); void Probabilistic_Hough( int, void* ); +void Weighted_Hough( int, void* ); /** * @function main @@ -53,22 +58,29 @@ int main( int argc, char** argv ) /// Pass the image to gray cvtColor( src, src_gray, COLOR_RGB2GRAY ); - /// Apply Canny edge detector - Canny( src_gray, edges, 50, 200, 3 ); + /// Apply Canny/Sobel edge detector + Canny( src_gray, canny_edge, 50, 200, 3 ); + Sobel( src_gray, sobel_edge, CV_16S, 1, 0 ); // dx(order of the derivative x)=1,dy=0 /// Create Trackbars for Thresholds char thresh_label[50]; snprintf( thresh_label, sizeof(thresh_label), "Thres: %d + input", min_threshold ); - namedWindow( standard_name, WINDOW_AUTOSIZE ); - createTrackbar( thresh_label, standard_name, &s_trackbar, max_trackbar, Standard_Hough); + createTrackbar( thresh_label, standard_name, &s_trackbar, max_trackbar, Standard_Hough ); namedWindow( probabilistic_name, WINDOW_AUTOSIZE ); - createTrackbar( thresh_label, probabilistic_name, &p_trackbar, max_trackbar, Probabilistic_Hough); + createTrackbar( thresh_label, probabilistic_name, &p_trackbar, max_trackbar, Probabilistic_Hough ); + + char edge_thresh_label[50]; + sprintf( edge_thresh_label, "Edge Thres: input" ); + namedWindow( weighted_name, WINDOW_AUTOSIZE); + createTrackbar( edge_thresh_label, weighted_name, &e_trackbar, max_trackbar, Weighted_Hough); + createTrackbar( thresh_label, weighted_name, &w_trackbar, weightedhough_max_trackbar, Weighted_Hough); /// Initialize Standard_Hough(0, 0); Probabilistic_Hough(0, 0); + Weighted_Hough(0, 0); waitKey(0); return 0; } @@ -90,10 +102,10 @@ void help() void Standard_Hough( int, void* ) { vector s_lines; - cvtColor( edges, standard_hough, COLOR_GRAY2BGR ); + cvtColor( canny_edge, standard_hough, COLOR_GRAY2BGR ); /// 1. Use Standard Hough Transform - HoughLines( edges, s_lines, 1, CV_PI/180, min_threshold + s_trackbar, 0, 0 ); + HoughLines( canny_edge, s_lines, 1, CV_PI/180, min_threshold + s_trackbar, 0, 0 ); /// Show the result for( size_t i = 0; i < s_lines.size(); i++ ) @@ -117,10 +129,10 @@ void Standard_Hough( int, void* ) void Probabilistic_Hough( int, void* ) { vector p_lines; - cvtColor( edges, probabilistic_hough, COLOR_GRAY2BGR ); + cvtColor( canny_edge, probabilistic_hough, COLOR_GRAY2BGR ); /// 2. Use Probabilistic Hough Transform - HoughLinesP( edges, p_lines, 1, CV_PI/180, min_threshold + p_trackbar, 30, 10 ); + HoughLinesP( canny_edge, p_lines, 1, CV_PI/180, min_threshold + p_trackbar, 30, 10 ); /// Show the result for( size_t i = 0; i < p_lines.size(); i++ ) @@ -131,3 +143,38 @@ void Probabilistic_Hough( int, void* ) imshow( probabilistic_name, probabilistic_hough ); } + +/** + * @function Weighted_Hough + * This can detect lines based on the edge intensities. + */ +void Weighted_Hough( int, void* ) +{ + vector s_lines; + + /// prepare + Mat edge_img; + convertScaleAbs(sobel_edge, edge_img ); + // use same threshold for edge with Hough. + threshold( edge_img, edge_img, e_trackbar, 255, cv::THRESH_TOZERO); + cvtColor( edge_img, weighted_hough, COLOR_GRAY2BGR ); + + /// 3. Use Weighted Hough Transform + const bool use_edgeval{true}; + HoughLines( edge_img, s_lines, 1, CV_PI/180, min_threshold + w_trackbar, 0, 0, 0, CV_PI, use_edgeval); + + /// Show the result + for( size_t i = 0; i < s_lines.size(); i++ ) + { + float r = s_lines[i][0], t = s_lines[i][1]; + double cos_t = cos(t), sin_t = sin(t); + double x0 = r*cos_t, y0 = r*sin_t; + double alpha = 1000; + + Point pt1( cvRound(x0 + alpha*(-sin_t)), cvRound(y0 + alpha*cos_t) ); + Point pt2( cvRound(x0 - alpha*(-sin_t)), cvRound(y0 - alpha*cos_t) ); + line( weighted_hough, pt1, pt2, Scalar(255,0,0), 3, LINE_AA ); + } + + imshow( weighted_name, weighted_hough ); +} diff --git a/samples/cpp/tutorial_code/imgcodecs/animations.cpp b/samples/cpp/tutorial_code/imgcodecs/animations.cpp new file mode 100644 index 0000000000..2f56a9f6ea --- /dev/null +++ b/samples/cpp/tutorial_code/imgcodecs/animations.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +using namespace cv; + +int main( int argc, const char** argv ) +{ + std::string filename = argc > 1 ? argv[1] : "animated_image.webp"; + + //! [write_animation] + if (argc == 1) + { + Animation animation_to_save; + Mat image(128, 256, CV_8UC4, Scalar(150, 150, 150, 255)); + int duration = 200; + + for (int i = 0; i < 10; ++i) { + animation_to_save.frames.push_back(image.clone()); + putText(animation_to_save.frames[i], format("Frame %d", i), Point(30, 80), FONT_HERSHEY_SIMPLEX, 1.5, Scalar(255, 100, 0, 255), 2); + animation_to_save.durations.push_back(duration); + } + imwriteanimation("animated_image.webp", animation_to_save, { IMWRITE_WEBP_QUALITY, 100 }); + } + //! [write_animation] + + //! [init_animation] + Animation animation; + //! [init_animation] + + //! [read_animation] + bool success = imreadanimation(filename, animation); + if (!success) { + std::cerr << "Failed to load animation frames\n"; + return -1; + } + //! [read_animation] + + //! [show_animation] + while (true) + for (size_t i = 0; i < animation.frames.size(); ++i) { + imshow("Animation", animation.frames[i]); + int key_code = waitKey(animation.durations[i]); // Delay between frames + if (key_code == 27) + exit(0); + } + //! [show_animation] + + return 0; +} diff --git a/samples/python/tutorial_code/imgcodecs/animations.py b/samples/python/tutorial_code/imgcodecs/animations.py new file mode 100644 index 0000000000..162450eb12 --- /dev/null +++ b/samples/python/tutorial_code/imgcodecs/animations.py @@ -0,0 +1,52 @@ +import cv2 as cv +import numpy as np + +def main(filename): + ## [write_animation] + if filename == "animated_image.webp": + # Create an Animation instance to save + animation_to_save = cv.Animation() + + # Generate a base image with a specific color + image = np.full((128, 256, 4), (150, 150, 150, 255), dtype=np.uint8) + duration = 200 + frames = [] + durations = [] + + # Populate frames and durations in the Animation object + for i in range(10): + frame = image.copy() + cv.putText(frame, f"Frame {i}", (30, 80), cv.FONT_HERSHEY_SIMPLEX, 1.5, (255, 100, 0, 255), 2) + frames.append(frame) + durations.append(duration) + + animation_to_save.frames = frames + animation_to_save.durations = durations + + # Write the animation to file + cv.imwriteanimation(filename, animation_to_save, [cv.IMWRITE_WEBP_QUALITY, 100]) + ## [write_animation] + + ## [init_animation] + animation = cv.Animation() + ## [init_animation] + + ## [read_animation] + success, animation = cv.imreadanimation(filename) + if not success: + print("Failed to load animation frames") + return + ## [read_animation] + + ## [show_animation] + while True: + for i, frame in enumerate(animation.frames): + cv.imshow("Animation", frame) + key_code = cv.waitKey(animation.durations[i]) + if key_code == 27: # Exit if 'Esc' key is pressed + return + ## [show_animation] + +if __name__ == "__main__": + import sys + main(sys.argv[1] if len(sys.argv) > 1 else "animated_image.webp")