diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..dd53e9508c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: Bug Report +description: Create a report to help us reproduce and fix the bug +labels: ["bug"] + +body: +- type: markdown + attributes: + value: > + #### Thank you for contributing! Before reporting a bug, please have a look at the [FAQ](https://github.com/opencv/opencv/wiki/FAQ), make sure the issue has no duplicate and hasn't been already addressed by searching through [the existing and past issues](https://github.com/opencv/opencv/issues?page=1&q=is%3Aissue+sort%3Acreated-desc). + +- type: textarea + attributes: + label: System Information + description: | + Please provide the following system information to help us diagnose the bug. For example: + + // example for c++ user + OpenCV version: 4.6.0 + Operating System / Platform: Ubuntu 20.04 + Compiler & compiler version: GCC 9.3.0 + + // example for python user + OpenCV python version: 4.6.0.66 + Operating System / Platform: Ubuntu 20.04 + Python version: 3.9.6 + validations: + required: true +- type: textarea + attributes: + label: Detailed description + description: | + Please provide a clear and concise description of what the bug is and paste the error log below. It helps improving readability if the error log is wrapped in ```` ```triple quotes blocks``` ````. + placeholder: | + A clear and concise description of what the bug is. + + ``` + # error log + ``` + validations: + required: true +- type: textarea + attributes: + label: Steps to reproduce + description: | + Please provide a minimal example to help us reproduce the bug. Code should be wrapped with ```` ```triple quotes blocks``` ```` to improve readability. If the code is too long, please attach as a file or create and link a public gist: https://gist.github.com. + + Related data files (images, onnx, etc) should be attached below as well. If the data files are too big, feel free to upload them to a online drive, share them and put the link below. + placeholder: | + ```cpp (replace cpp with python if python code) + # sample code to reproduce the bug + ``` + + Test data: [image](https://link/to/the/image), [model.onnx](htts://link/to/the/onnx/model) + validations: + required: true +- type: checkboxes + attributes: + label: Issue submission checklist + options: + - label: I report the issue, it's not a question + required: true + - label: I checked the problem with documentation, FAQ, open issues, forum.opencv.org, Stack Overflow, etc and have not found any solution + - label: I updated to the latest OpenCV version and the issue is still there + - label: There is reproducer code and related data files (videos, images, onnx, etc) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..56200aa0a3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Questions + url: https://forum.opencv.org/ + about: Ask questions and discuss with OpenCV community members diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml new file mode 100644 index 0000000000..62d105ea69 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -0,0 +1,26 @@ +name: Documentation +description: Report an issue related to https://docs.opencv.org/ +labels: ["category: documentation"] + +body: +- type: markdown + attributes: + value: > + #### Thank you for contributing! Before submitting a doc issue, please make sure it has no duplicate by searching through [the existing and past issues](https://github.com/opencv/opencv/issues?page=1&q=is%3Aissue+sort%3Acreated-desc) + +- type: textarea + attributes: + label: Descripe the doc issue + description: > + Please provide a clear and concise description of what content in https://docs.opencv.org/ is an issue. Note that there are multiple active branches, such as 3.4, 4.x and 5.x, so please specify the branch with the problem. + placeholder: | + A clear and concise description of what content in https://docs.opencv.org/ is an issue. + + Link to the doc: https://docs.opencv.org/4.x/d3/d63/classcv_1_1Mat.html + validations: + required: true +- type: textarea + attributes: + label: Fix suggestion + description: > + Tell us how we could improve the documentation in this regard. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..ded0d0d025 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,22 @@ +name: Feature request +description: Submit a request for a new OpenCV feature +labels: ["feature"] + +body: +- type: markdown + attributes: + value: > + #### Thank you for contributing! Before submitting a feature request, please make sure the request has no duplicate by searching through [the existing and past issues](https://github.com/opencv/opencv/issues?page=1&q=is%3Aissue+sort%3Acreated-desc) + +- type: textarea + attributes: + label: Descripe the feature and motivation + description: | + Please provide a clear and concise proposal of the feature and outline the motivation. + validations: + required: true +- type: textarea + attributes: + label: Additional context + description: | + Add any other context, such as pseudo code, links, diagram, screenshots, to help the community better understand the feature request. diff --git a/.github/workflows/arm64-build-checks.yml b/.github/workflows/arm64-build-checks.yml index d3cf532d59..e0d374ea77 100644 --- a/.github/workflows/arm64-build-checks.yml +++ b/.github/workflows/arm64-build-checks.yml @@ -2,6 +2,9 @@ name: arm64 build checks on: workflow_dispatch +permissions: + contents: read # to fetch code (actions/checkout) + jobs: build: diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index cbc27489db..b21191934c 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -1,5 +1,7 @@ name: lint_python on: workflow_dispatch +permissions: + contents: read # to fetch code (actions/checkout) jobs: lint_python: runs-on: ubuntu-latest diff --git a/3rdparty/carotene/CMakeLists.txt b/3rdparty/carotene/CMakeLists.txt index 3d49a2def6..ebcdf1a9f6 100644 --- a/3rdparty/carotene/CMakeLists.txt +++ b/3rdparty/carotene/CMakeLists.txt @@ -27,6 +27,10 @@ if(CMAKE_COMPILER_IS_GNUCC) endif() endif() +if(APPLE AND CV_CLANG AND WITH_NEON) + ocv_warnings_disable(CMAKE_CXX_FLAGS -Wno-unused-function) +endif() + add_library(carotene_objs OBJECT EXCLUDE_FROM_ALL ${carotene_headers} ${carotene_sources} diff --git a/3rdparty/carotene/hal/tegra_hal.hpp b/3rdparty/carotene/hal/tegra_hal.hpp index c2ae0c0d87..ce8fa90982 100644 --- a/3rdparty/carotene/hal/tegra_hal.hpp +++ b/3rdparty/carotene/hal/tegra_hal.hpp @@ -1296,13 +1296,13 @@ struct MorphCtx CAROTENE_NS::BORDER_MODE border; uchar borderValues[4]; }; -inline int TEGRA_MORPHINIT(cvhalFilter2D **context, int operation, int src_type, int dst_type, int, int, +inline int TEGRA_MORPHINIT(cvhalFilter2D **context, int operation, int src_type, int dst_type, int width, int height, int kernel_type, uchar *kernel_data, size_t kernel_step, int kernel_width, int kernel_height, int anchor_x, int anchor_y, int borderType, const double borderValue[4], int iterations, bool allowSubmatrix, bool allowInplace) { if(!context || !kernel_data || src_type != dst_type || CV_MAT_DEPTH(src_type) != CV_8U || src_type < 0 || (src_type >> CV_CN_SHIFT) > 3 || - + width < kernel_width || height < kernel_height || allowSubmatrix || allowInplace || iterations != 1 || !CAROTENE_NS::isSupportedConfiguration()) return CV_HAL_ERROR_NOT_IMPLEMENTED; diff --git a/3rdparty/carotene/src/add_weighted.cpp b/3rdparty/carotene/src/add_weighted.cpp index 1f89fb5372..6559b9fe53 100644 --- a/3rdparty/carotene/src/add_weighted.cpp +++ b/3rdparty/carotene/src/add_weighted.cpp @@ -109,9 +109,9 @@ template <> struct wAdd vgamma = vdupq_n_f32(_gamma + 0.5); } - void operator() (const typename VecTraits::vec128 & v_src0, - const typename VecTraits::vec128 & v_src1, - typename VecTraits::vec128 & v_dst) const + void operator() (const VecTraits::vec128 & v_src0, + const VecTraits::vec128 & v_src1, + VecTraits::vec128 & v_dst) const { float32x4_t vs1 = vcvtq_f32_s32(v_src0); float32x4_t vs2 = vcvtq_f32_s32(v_src1); @@ -121,9 +121,9 @@ template <> struct wAdd v_dst = vcvtq_s32_f32(vs1); } - void operator() (const typename VecTraits::vec64 & v_src0, - const typename VecTraits::vec64 & v_src1, - typename VecTraits::vec64 & v_dst) const + void operator() (const VecTraits::vec64 & v_src0, + const VecTraits::vec64 & v_src1, + VecTraits::vec64 & v_dst) const { float32x2_t vs1 = vcvt_f32_s32(v_src0); float32x2_t vs2 = vcvt_f32_s32(v_src1); @@ -153,9 +153,9 @@ template <> struct wAdd vgamma = vdupq_n_f32(_gamma + 0.5); } - void operator() (const typename VecTraits::vec128 & v_src0, - const typename VecTraits::vec128 & v_src1, - typename VecTraits::vec128 & v_dst) const + void operator() (const VecTraits::vec128 & v_src0, + const VecTraits::vec128 & v_src1, + VecTraits::vec128 & v_dst) const { float32x4_t vs1 = vcvtq_f32_u32(v_src0); float32x4_t vs2 = vcvtq_f32_u32(v_src1); @@ -165,9 +165,9 @@ template <> struct wAdd v_dst = vcvtq_u32_f32(vs1); } - void operator() (const typename VecTraits::vec64 & v_src0, - const typename VecTraits::vec64 & v_src1, - typename VecTraits::vec64 & v_dst) const + void operator() (const VecTraits::vec64 & v_src0, + const VecTraits::vec64 & v_src1, + VecTraits::vec64 & v_dst) const { float32x2_t vs1 = vcvt_f32_u32(v_src0); float32x2_t vs2 = vcvt_f32_u32(v_src1); @@ -197,17 +197,17 @@ template <> struct wAdd vgamma = vdupq_n_f32(_gamma + 0.5); } - void operator() (const typename VecTraits::vec128 & v_src0, - const typename VecTraits::vec128 & v_src1, - typename VecTraits::vec128 & v_dst) const + void operator() (const VecTraits::vec128 & v_src0, + const VecTraits::vec128 & v_src1, + VecTraits::vec128 & v_dst) const { float32x4_t vs1 = vmlaq_f32(vgamma, v_src0, valpha); v_dst = vmlaq_f32(vs1, v_src1, vbeta); } - void operator() (const typename VecTraits::vec64 & v_src0, - const typename VecTraits::vec64 & v_src1, - typename VecTraits::vec64 & v_dst) const + void operator() (const VecTraits::vec64 & v_src0, + const VecTraits::vec64 & v_src1, + VecTraits::vec64 & v_dst) const { float32x2_t vs1 = vmla_f32(vget_low(vgamma), v_src0, vget_low(valpha)); v_dst = vmla_f32(vs1, v_src1, vget_low(vbeta)); diff --git a/3rdparty/carotene/src/blur.cpp b/3rdparty/carotene/src/blur.cpp index 798cce5a71..21689a2bd3 100644 --- a/3rdparty/carotene/src/blur.cpp +++ b/3rdparty/carotene/src/blur.cpp @@ -391,9 +391,9 @@ void blur3x3(const Size2D &size, s32 cn, } else if (borderType == BORDER_MODE_REFLECT101) { - tcurr = vsetq_lane_u16(vgetq_lane_u16(tcurr, 3),tcurr, 5); tcurr = vsetq_lane_u16(vgetq_lane_u16(tcurr, 4),tcurr, 6); tcurr = vsetq_lane_u16(vgetq_lane_u16(tcurr, 5),tcurr, 7); + tcurr = vsetq_lane_u16(vgetq_lane_u16(tcurr, 3),tcurr, 5); } else { diff --git a/3rdparty/ffmpeg/ffmpeg.cmake b/3rdparty/ffmpeg/ffmpeg.cmake index 6dcb24db6b..f3ad63770a 100644 --- a/3rdparty/ffmpeg/ffmpeg.cmake +++ b/3rdparty/ffmpeg/ffmpeg.cmake @@ -1,8 +1,8 @@ -# Binaries branch name: ffmpeg/4.x_20220524 -# Binaries were created for OpenCV: d6e9616256b46bd59be0a93d397f6ab958d39cd2 -ocv_update(FFMPEG_BINARIES_COMMIT "65ec04d4573dcdfa4531f0b9e67f35d8ffff873e") -ocv_update(FFMPEG_FILE_HASH_BIN32 "5573e2262ad1298e603122b7759fc2f6") -ocv_update(FFMPEG_FILE_HASH_BIN64 "5f9e2b2e04c15f080f40e844de80c867") +# Binaries branch name: ffmpeg/4.x_20221225 +# Binaries were created for OpenCV: 4abe6dc48d4ec6229f332cc6cf6c7e234ac8027e +ocv_update(FFMPEG_BINARIES_COMMIT "7dd0d4f1d6fe75f05f3d3b5e38cbc96c1a2d2809") +ocv_update(FFMPEG_FILE_HASH_BIN32 "e598ae2ece1ddf310bc49b58202fd87a") +ocv_update(FFMPEG_FILE_HASH_BIN64 "b2a40c142c20aef9fd663fc8f85c2971") ocv_update(FFMPEG_FILE_HASH_CMAKE "8862c87496e2e8c375965e1277dee1c7") function(download_win_ffmpeg script_var) diff --git a/3rdparty/libjpeg-turbo/CMakeLists.txt b/3rdparty/libjpeg-turbo/CMakeLists.txt index 4dd3095f94..ac0aaf63e1 100644 --- a/3rdparty/libjpeg-turbo/CMakeLists.txt +++ b/3rdparty/libjpeg-turbo/CMakeLists.txt @@ -15,8 +15,53 @@ endif() message(STATUS "libjpeg-turbo: VERSION = ${VERSION}, BUILD = ${BUILD}") +math(EXPR BITS "${CMAKE_SIZEOF_VOID_P} * 8") +string(TOLOWER ${CMAKE_SYSTEM_PROCESSOR} CMAKE_SYSTEM_PROCESSOR_LC) + +if(CMAKE_SYSTEM_PROCESSOR_LC MATCHES "x86_64" OR + CMAKE_SYSTEM_PROCESSOR_LC MATCHES "amd64" OR + CMAKE_SYSTEM_PROCESSOR_LC MATCHES "i[0-9]86" OR + CMAKE_SYSTEM_PROCESSOR_LC MATCHES "x86" OR + CMAKE_SYSTEM_PROCESSOR_LC MATCHES "ia32") + if(BITS EQUAL 64 OR CMAKE_C_COMPILER_ABI MATCHES "ELF X32") + set(CPU_TYPE x86_64) + else() + set(CPU_TYPE i386) + endif() + if(NOT CMAKE_SYSTEM_PROCESSOR STREQUAL ${CPU_TYPE}) + set(CMAKE_SYSTEM_PROCESSOR ${CPU_TYPE}) + endif() +elseif(CMAKE_SYSTEM_PROCESSOR_LC STREQUAL "aarch64" OR + CMAKE_SYSTEM_PROCESSOR_LC MATCHES "^arm") + if(BITS EQUAL 64) + set(CPU_TYPE arm64) + else() + set(CPU_TYPE arm) + endif() +elseif(CMAKE_SYSTEM_PROCESSOR_LC MATCHES "^ppc" OR + CMAKE_SYSTEM_PROCESSOR_LC MATCHES "^powerpc") + set(CPU_TYPE powerpc) +else() + set(CPU_TYPE ${CMAKE_SYSTEM_PROCESSOR_LC}) +endif() +if(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" OR + CMAKE_OSX_ARCHITECTURES MATCHES "arm64" OR + CMAKE_OSX_ARCHITECTURES MATCHES "i386") + set(CPU_TYPE ${CMAKE_OSX_ARCHITECTURES}) +endif() +if(CMAKE_OSX_ARCHITECTURES MATCHES "ppc") + set(CPU_TYPE powerpc) +endif() +if(MSVC_IDE AND CMAKE_GENERATOR_PLATFORM MATCHES "arm64") + set(CPU_TYPE arm64) +endif() + +OCV_OPTION(ENABLE_LIBJPEG_TURBO_SIMD "Include SIMD extensions for libjpeg-turbo, if available for this platform" (NOT CV_DISABLE_OPTIMIZATION) + VISIBLE_IF BUILD_JPEG) option(WITH_ARITH_ENC "Include arithmetic encoding support when emulating the libjpeg v6b API/ABI" TRUE) option(WITH_ARITH_DEC "Include arithmetic decoding support when emulating the libjpeg v6b API/ABI" TRUE) +set(WITH_SIMD 1) +set(HAVE_LIBJPEG_TURBO_SIMD 0 PARENT_SCOPE) include(CheckCSourceCompiles) include(CheckIncludeFiles) @@ -99,12 +144,57 @@ if(WITH_ARITH_DEC) set(JPEG_SOURCES ${JPEG_SOURCES} jdarith.c) endif() -# No SIMD -set(JPEG_SOURCES ${JPEG_SOURCES} jsimd_none.c) +if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + # Use the maximum optimization level for release builds + foreach(var CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO) + if(${var} MATCHES "-O2") + string(REGEX REPLACE "-O2" "-O3" ${var} "${${var}}") + endif() + endforeach() +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") + if(CMAKE_C_COMPILER_ID MATCHES "SunPro") + # Use the maximum optimization level for release builds + foreach(var CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO) + if(${var} MATCHES "-xO3") + string(REGEX REPLACE "-xO3" "-xO5" ${var} "${${var}}") + endif() + if(${var} MATCHES "-xO2") + string(REGEX REPLACE "-xO2" "-xO5" ${var} "${${var}}") + endif() + endforeach() + endif() +endif() + +if(ENABLE_LIBJPEG_TURBO_SIMD) + add_subdirectory(src/simd) + if(NEON_INTRINSICS) + add_definitions(-DNEON_INTRINSICS) + endif() +else() + set(WITH_SIMD 0) +endif() + +if(WITH_SIMD) + message(STATUS "SIMD extensions: ${CPU_TYPE} (WITH_SIMD = ${WITH_SIMD})") + set(HAVE_LIBJPEG_TURBO_SIMD 1 PARENT_SCOPE) + if(MSVC_IDE OR XCODE) + set_source_files_properties(${SIMD_OBJS} PROPERTIES GENERATED 1) + endif() +else() + add_library(jsimd OBJECT src/jsimd_none.c) + set_target_properties(jsimd PROPERTIES FOLDER "3rdparty") + if(NOT WIN32 AND (CMAKE_POSITION_INDEPENDENT_CODE OR ENABLE_SHARED)) + set_target_properties(jsimd PROPERTIES POSITION_INDEPENDENT_CODE 1) + endif() +endif() ocv_list_add_prefix(JPEG_SOURCES src/) -add_library(${JPEG_LIBRARY} STATIC ${OPENCV_3RDPARTY_EXCLUDE_FROM_ALL} ${JPEG_SOURCES} ${SIMD_OBJS}) +set(JPEG_SOURCES ${JPEG_SOURCES} ${SIMD_OBJS}) + +add_library(${JPEG_LIBRARY} STATIC ${OPENCV_3RDPARTY_EXCLUDE_FROM_ALL} ${JPEG_SOURCES} $ ${SIMD_OBJS}) set_target_properties(${JPEG_LIBRARY} PROPERTIES OUTPUT_NAME ${JPEG_LIBRARY} diff --git a/3rdparty/libjpeg-turbo/src/simd/CMakeLists.txt b/3rdparty/libjpeg-turbo/src/simd/CMakeLists.txt index 8521e42b44..5055302004 100644 --- a/3rdparty/libjpeg-turbo/src/simd/CMakeLists.txt +++ b/3rdparty/libjpeg-turbo/src/simd/CMakeLists.txt @@ -1,9 +1,13 @@ macro(simd_fail message) - if(REQUIRE_SIMD) - message(FATAL_ERROR "${message}.") - else() - message(WARNING "${message}. Performance will suffer.") + message(STATUS "libjpeg-turbo(SIMD): ${message}. Performance will suffer.") set(WITH_SIMD 0 PARENT_SCOPE) +endmacro() + +macro(boolean_number var) + if(${var}) + set(${var} 1 ${ARGN}) + else() + set(${var} 0 ${ARGN}) endif() endmacro() @@ -41,14 +45,14 @@ elseif(CPU_TYPE STREQUAL "i386") endif() endif() -if(NOT REQUIRE_SIMD) - include(CheckLanguage) - check_language(ASM_NASM) - if(NOT CMAKE_ASM_NASM_COMPILER) - simd_fail("SIMD extensions disabled: could not find NASM compiler") - return() - endif() + +include(CheckLanguage) +check_language(ASM_NASM) +if(NOT CMAKE_ASM_NASM_COMPILER) + simd_fail("SIMD extensions disabled: could not find NASM compiler") + return() endif() + enable_language(ASM_NASM) message(STATUS "CMAKE_ASM_NASM_COMPILER = ${CMAKE_ASM_NASM_COMPILER}") @@ -71,8 +75,6 @@ elseif(CPU_TYPE STREQUAL "i386") endif() endif() -message(STATUS "CMAKE_ASM_NASM_OBJECT_FORMAT = ${CMAKE_ASM_NASM_OBJECT_FORMAT}") - if(NOT CMAKE_ASM_NASM_OBJECT_FORMAT) simd_fail("SIMD extensions disabled: could not determine NASM object format") return() @@ -98,7 +100,6 @@ endif() string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UC) set(EFFECTIVE_ASM_NASM_FLAGS "${CMAKE_ASM_NASM_FLAGS} ${CMAKE_ASM_NASM_FLAGS_${CMAKE_BUILD_TYPE_UC}}") -message(STATUS "CMAKE_ASM_NASM_FLAGS = ${EFFECTIVE_ASM_NASM_FLAGS}") set(CMAKE_ASM_NASM_FLAGS "${CMAKE_ASM_NASM_FLAGS} -I\"${CMAKE_CURRENT_SOURCE_DIR}/nasm/\" -I\"${CMAKE_CURRENT_SOURCE_DIR}/${CPU_TYPE}/\"") @@ -112,6 +113,7 @@ add_custom_target(jsimdcfg COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/nasm/jsimdcfg.inc.h | ${GREP} -E '^[\;%]|^\ %' | sed 's%_cpp_protection_%%' | sed 's@% define@%define@g' >${CMAKE_CURRENT_SOURCE_DIR}/nasm/jsimdcfg.inc) +set_target_properties(jsimdcfg PROPERTIES FOLDER "3rdparty") if(CPU_TYPE STREQUAL "x86_64") set(SIMD_SOURCES x86_64/jsimdcpu.asm x86_64/jfdctflt-sse.asm @@ -196,14 +198,16 @@ endforeach() if(MSVC_IDE OR XCODE) set(SIMD_OBJS ${SIMD_OBJS} PARENT_SCOPE) - add_library(simd OBJECT ${CPU_TYPE}/jsimd.c) - add_custom_target(simd-objs DEPENDS ${SIMD_OBJS}) - add_dependencies(simd simd-objs) + add_library(jsimd OBJECT ${CPU_TYPE}/jsimd.c) + add_custom_target(jsimd-objs DEPENDS ${SIMD_OBJS}) + add_dependencies(jsimd jsimd-objs) + set_target_properties(jsimd PROPERTIES FOLDER "3rdparty") + set_target_properties(jsimd-objs PROPERTIES FOLDER "3rdparty") else() - add_library(simd OBJECT ${SIMD_SOURCES} ${CPU_TYPE}/jsimd.c) + add_library(jsimd OBJECT ${SIMD_SOURCES} ${CPU_TYPE}/jsimd.c) endif() if(NOT WIN32 AND (CMAKE_POSITION_INDEPENDENT_CODE OR ENABLE_SHARED)) - set_target_properties(simd PROPERTIES POSITION_INDEPENDENT_CODE 1) + set_target_properties(jsimd PROPERTIES POSITION_INDEPENDENT_CODE 1) endif() @@ -224,6 +228,7 @@ elseif(CPU_TYPE STREQUAL "arm64" OR CPU_TYPE STREQUAL "arm") # following test determines whether -mfloat-abi=softfp should be explicitly # added to the compile flags for the intrinsics implementation of the Neon SIMD # extensions. + if(BITS EQUAL 32) check_c_source_compiles(" #if defined(__ARM_NEON__) || (!defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__)) @@ -356,7 +361,7 @@ if(NOT NEON_INTRINSICS) -x assembler-with-cpp -c ${CMAKE_CURRENT_BINARY_DIR}/gastest.S RESULT_VARIABLE RESULT OUTPUT_VARIABLE OUTPUT ERROR_VARIABLE ERROR) if(NOT RESULT EQUAL 0) - message(WARNING "GAS appears to be broken. Using the full Neon SIMD intrinsics implementation.") + message(STATUS "libjpeg-turbo(SIMD): GAS appears to be broken. Using the full Neon SIMD intrinsics implementation.") set(NEON_INTRINSICS 1 CACHE INTERNAL "" FORCE) endif() endif() @@ -392,10 +397,10 @@ if(NOT NEON_INTRINSICS) set(SIMD_SOURCES ${SIMD_SOURCES} arm/aarch${BITS}/jsimd_neon.S) endif() -add_library(simd OBJECT ${SIMD_SOURCES} arm/aarch${BITS}/jsimd.c) +add_library(jsimd OBJECT ${SIMD_SOURCES} arm/aarch${BITS}/jsimd.c) if(CMAKE_POSITION_INDEPENDENT_CODE OR ENABLE_SHARED) - set_target_properties(simd PROPERTIES POSITION_INDEPENDENT_CODE 1) + set_target_properties(jsimd PROPERTIES POSITION_INDEPENDENT_CODE 1) endif() @@ -434,10 +439,10 @@ if(NOT HAVE_DSPR2) return() endif() -add_library(simd OBJECT mips/jsimd_dspr2.S mips/jsimd.c) +add_library(jsimd OBJECT mips/jsimd_dspr2.S mips/jsimd.c) if(CMAKE_POSITION_INDEPENDENT_CODE OR ENABLE_SHARED) - set_target_properties(simd PROPERTIES POSITION_INDEPENDENT_CODE 1) + set_target_properties(jsimd PROPERTIES POSITION_INDEPENDENT_CODE 1) endif() ############################################################################### @@ -482,10 +487,10 @@ foreach(file ${SIMD_SOURCES}) " -Wa,-mloongson-mmi,-mloongson-ext") endforeach() -add_library(simd OBJECT ${SIMD_SOURCES} mips64/jsimd.c) +add_library(jsimd OBJECT ${SIMD_SOURCES} mips64/jsimd.c) if(CMAKE_POSITION_INDEPENDENT_CODE OR ENABLE_SHARED) - set_target_properties(simd PROPERTIES POSITION_INDEPENDENT_CODE 1) + set_target_properties(jsimd PROPERTIES POSITION_INDEPENDENT_CODE 1) endif() ############################################################################### @@ -522,10 +527,10 @@ set(SIMD_SOURCES powerpc/jccolor-altivec.c powerpc/jcgray-altivec.c set_source_files_properties(${SIMD_SOURCES} PROPERTIES COMPILE_FLAGS -maltivec) -add_library(simd OBJECT ${SIMD_SOURCES} powerpc/jsimd.c) +add_library(jsimd OBJECT ${SIMD_SOURCES} powerpc/jsimd.c) if(CMAKE_POSITION_INDEPENDENT_CODE OR ENABLE_SHARED) - set_target_properties(simd PROPERTIES POSITION_INDEPENDENT_CODE 1) + set_target_properties(jsimd PROPERTIES POSITION_INDEPENDENT_CODE 1) endif() diff --git a/3rdparty/libspng/CMakeLists.txt b/3rdparty/libspng/CMakeLists.txt new file mode 100644 index 0000000000..afd6d5fe40 --- /dev/null +++ b/3rdparty/libspng/CMakeLists.txt @@ -0,0 +1,47 @@ +# ---------------------------------------------------------------------------- +# CMake file for libspng. See root CMakeLists.txt +# +# ---------------------------------------------------------------------------- + +project(${SPNG_LIBRARY}) + +set(CURR_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set_property(GLOBAL PROPERTY SPNG_INCLUDE_DIR ${CURR_INCLUDE_DIR}) +ocv_include_directories(${ZLIB_INCLUDE_DIRS}) + +file(GLOB_RECURSE spng_headers RELATIVE "${CMAKE_CURRENT_LIST_DIR}" "*.h") +file(GLOB_RECURSE spng_sources RELATIVE "${CMAKE_CURRENT_LIST_DIR}" "*.c") + +message(STATUS "libspng will be used as PNG codec") + +# ---------------------------------------------------------------------------------- +# Define the library target: +# ---------------------------------------------------------------------------------- + +if(MSVC) + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) +endif(MSVC) + +add_library(${SPNG_LIBRARY} STATIC ${OPENCV_3RDPARTY_EXCLUDE_FROM_ALL} ${spng_headers} ${spng_sources}) +ocv_warnings_disable(CMAKE_C_FLAGS -Wunused-variable) +target_link_libraries(${SPNG_LIBRARY} ${ZLIB_LIBRARIES}) + +set_target_properties(${SPNG_LIBRARY} + PROPERTIES OUTPUT_NAME ${SPNG_LIBRARY} + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + COMPILE_PDB_NAME ${SPNG_LIBRARY} + COMPILE_PDB_NAME_DEBUG "${SPNG_LIBRARY}${OPENCV_DEBUG_POSTFIX}" + ARCHIVE_OUTPUT_DIRECTORY ${3P_LIBRARY_OUTPUT_PATH} + ) + +target_compile_definitions(${SPNG_LIBRARY} PUBLIC SPNG_STATIC) + +if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${SPNG_LIBRARY} PROPERTIES FOLDER "3rdparty") +endif() + +if(NOT BUILD_SHARED_LIBS) + ocv_install_target(${SPNG_LIBRARY} EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev OPTIONAL) +endif() + +ocv_install_3rdparty_licenses(${SPNG_LIBRARY} LICENSE) diff --git a/3rdparty/libspng/LICENSE b/3rdparty/libspng/LICENSE new file mode 100644 index 0000000000..f96574b80d --- /dev/null +++ b/3rdparty/libspng/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2018-2022, Randy +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT HOLDER OR 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. \ No newline at end of file diff --git a/3rdparty/libspng/spng.c b/3rdparty/libspng/spng.c new file mode 100644 index 0000000000..6ed60f2d6c --- /dev/null +++ b/3rdparty/libspng/spng.c @@ -0,0 +1,6978 @@ +/* SPDX-License-Identifier: (BSD-2-Clause AND libpng-2.0) */ + +#define SPNG__BUILD + +#include "spng.h" + +#include +#include +#include +#include + +#define ZLIB_CONST + +#ifdef __FRAMAC__ + #define SPNG_DISABLE_OPT + #include "tests/framac_stubs.h" +#else + #ifdef SPNG_USE_MINIZ + #include + #else + #include + #endif +#endif + +#ifdef SPNG_MULTITHREADING + #include +#endif + +/* Not build options, edit at your own risk! */ +#define SPNG_READ_SIZE (8192) +#define SPNG_WRITE_SIZE SPNG_READ_SIZE +#define SPNG_MAX_CHUNK_COUNT (1000) + +#define SPNG_TARGET_CLONES(x) + +#ifndef SPNG_DISABLE_OPT + + #if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) + #define SPNG_X86 + + #if defined(__x86_64__) || defined(_M_X64) + #define SPNG_X86_64 + #endif + + #elif defined(__aarch64__) || defined(_M_ARM64) /* || defined(__ARM_NEON) */ + #define SPNG_ARM /* NOTE: only arm64 builds are tested! */ + #else + #pragma message "disabling SIMD optimizations for unknown target" + #define SPNG_DISABLE_OPT + #endif + + #if defined(SPNG_X86_64) && defined(SPNG_ENABLE_TARGET_CLONES) + #undef SPNG_TARGET_CLONES + #define SPNG_TARGET_CLONES(x) __attribute__((target_clones(x))) + #else + #define SPNG_TARGET_CLONES(x) + #endif + + #ifndef SPNG_DISABLE_OPT + static void defilter_sub3(size_t rowbytes, unsigned char *row); + static void defilter_sub4(size_t rowbytes, unsigned char *row); + static void defilter_avg3(size_t rowbytes, unsigned char *row, const unsigned char *prev); + static void defilter_avg4(size_t rowbytes, unsigned char *row, const unsigned char *prev); + static void defilter_paeth3(size_t rowbytes, unsigned char *row, const unsigned char *prev); + static void defilter_paeth4(size_t rowbytes, unsigned char *row, const unsigned char *prev); + + #if defined(SPNG_ARM) + static uint32_t expand_palette_rgba8_neon(unsigned char *row, const unsigned char *scanline, const unsigned char *plte, uint32_t width); + static uint32_t expand_palette_rgb8_neon(unsigned char *row, const unsigned char *scanline, const unsigned char *plte, uint32_t width); + #endif + #endif +#endif + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable: 4244) +#endif + +#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || defined(__BIG_ENDIAN__) + #define SPNG_BIG_ENDIAN +#else + #define SPNG_LITTLE_ENDIAN +#endif + +enum spng_state +{ + SPNG_STATE_INVALID = 0, + SPNG_STATE_INIT = 1, /* No PNG buffer/stream is set */ + SPNG_STATE_INPUT, /* Decoder input PNG was set */ + SPNG_STATE_OUTPUT = SPNG_STATE_INPUT, /* Encoder output was set */ + SPNG_STATE_IHDR, /* IHDR was read/written */ + SPNG_STATE_FIRST_IDAT, /* Encoded up to / reached first IDAT */ + SPNG_STATE_DECODE_INIT, /* Decoder is ready for progressive reads */ + SPNG_STATE_ENCODE_INIT = SPNG_STATE_DECODE_INIT, + SPNG_STATE_EOI, /* Reached the last scanline/row */ + SPNG_STATE_LAST_IDAT, /* Reached last IDAT, set at end of decode_image() */ + SPNG_STATE_AFTER_IDAT, /* */ + SPNG_STATE_IEND, /* Reached IEND */ +}; + +enum spng__internal +{ + SPNG__IO_SIGNAL = 1 << 9, + SPNG__CTX_FLAGS_ALL = (SPNG_CTX_IGNORE_ADLER32 | SPNG_CTX_ENCODER) +}; + +#define SPNG_STR(x) _SPNG_STR(x) +#define _SPNG_STR(x) #x + +#define SPNG_VERSION_STRING SPNG_STR(SPNG_VERSION_MAJOR) "." \ + SPNG_STR(SPNG_VERSION_MINOR) "." \ + SPNG_STR(SPNG_VERSION_PATCH) + +#define SPNG_GET_CHUNK_BOILERPLATE(chunk) \ + if(ctx == NULL) return 1; \ + int ret = read_chunks(ctx, 0); \ + if(ret) return ret; \ + if(!ctx->stored.chunk) return SPNG_ECHUNKAVAIL; \ + if(chunk == NULL) return 1 + +#define SPNG_SET_CHUNK_BOILERPLATE(chunk) \ + if(ctx == NULL || chunk == NULL) return 1; \ + if(ctx->data == NULL && !ctx->encode_only) return SPNG_ENOSRC; \ + int ret = read_chunks(ctx, 0); \ + if(ret) return ret + +/* Determine if the spng_option can be overriden/optimized */ +#define spng__optimize(option) (ctx->optimize_option & (1 << option)) + +struct spng_subimage +{ + uint32_t width; + uint32_t height; + size_t out_width; /* byte width based on output format */ + size_t scanline_width; +}; + +struct spng_text2 +{ + int type; + char *keyword; + char *text; + + size_t text_length; + + uint8_t compression_flag; /* iTXt only */ + char *language_tag; /* iTXt only */ + char *translated_keyword; /* iTXt only */ + + size_t cache_usage; + char user_keyword_storage[80]; +}; + +struct decode_flags +{ + unsigned apply_trns: 1; + unsigned apply_gamma: 1; + unsigned use_sbit: 1; + unsigned indexed: 1; + unsigned do_scaling: 1; + unsigned interlaced: 1; + unsigned same_layout: 1; + unsigned zerocopy: 1; + unsigned unpack: 1; +}; + +struct encode_flags +{ + unsigned interlace: 1; + unsigned same_layout: 1; + unsigned to_bigendian: 1; + unsigned progressive: 1; + unsigned finalize: 1; + + enum spng_filter_choice filter_choice; +}; + +struct spng_chunk_bitfield +{ + unsigned ihdr: 1; + unsigned plte: 1; + unsigned chrm: 1; + unsigned iccp: 1; + unsigned gama: 1; + unsigned sbit: 1; + unsigned srgb: 1; + unsigned text: 1; + unsigned bkgd: 1; + unsigned hist: 1; + unsigned trns: 1; + unsigned phys: 1; + unsigned splt: 1; + unsigned time: 1; + unsigned offs: 1; + unsigned exif: 1; + unsigned unknown: 1; +}; + +/* Packed sample iterator */ +struct spng__iter +{ + const uint8_t mask; + unsigned shift_amount; + const unsigned initial_shift, bit_depth; + const unsigned char *samples; +}; + +union spng__decode_plte +{ + struct spng_plte_entry rgba[256]; + unsigned char rgb[256 * 3]; + unsigned char raw[256 * 4]; + uint32_t align_this; +}; + +struct spng__zlib_options +{ + int compression_level; + int window_bits; + int mem_level; + int strategy; + int data_type; +}; + +typedef void spng__undo(spng_ctx *ctx); + +struct spng_ctx +{ + size_t data_size; + size_t bytes_read; + size_t stream_buf_size; + unsigned char *stream_buf; + const unsigned char *data; + + /* User-defined pointers for streaming */ + spng_read_fn *read_fn; + spng_write_fn *write_fn; + void *stream_user_ptr; + + /* Used for buffer reads */ + const unsigned char *png_base; + size_t bytes_left; + size_t last_read_size; + + /* Used for encoding */ + int user_owns_out_png; + unsigned char *out_png; + unsigned char *write_ptr; + size_t out_png_size; + size_t bytes_encoded; + + /* These are updated by read/write_header()/read_chunk_bytes() */ + struct spng_chunk current_chunk; + uint32_t cur_chunk_bytes_left; + uint32_t cur_actual_crc; + + struct spng_alloc alloc; + + enum spng_ctx_flags flags; + enum spng_format fmt; + + enum spng_state state; + + unsigned streaming: 1; + unsigned internal_buffer: 1; /* encoding to internal buffer */ + + unsigned inflate: 1; + unsigned deflate: 1; + unsigned encode_only: 1; + unsigned strict: 1; + unsigned discard: 1; + unsigned skip_crc: 1; + unsigned keep_unknown: 1; + unsigned prev_was_idat: 1; + + struct spng__zlib_options image_options; + struct spng__zlib_options text_options; + + spng__undo *undo; + + /* input file contains this chunk */ + struct spng_chunk_bitfield file; + + /* chunk was stored with spng_set_*() */ + struct spng_chunk_bitfield user; + + /* chunk was stored by reading or with spng_set_*() */ + struct spng_chunk_bitfield stored; + + /* used to reset the above in case of an error */ + struct spng_chunk_bitfield prev_stored; + + struct spng_chunk first_idat, last_idat; + + uint32_t max_width, max_height; + + size_t max_chunk_size; + size_t chunk_cache_limit; + size_t chunk_cache_usage; + uint32_t chunk_count_limit; + uint32_t chunk_count_total; + + int crc_action_critical; + int crc_action_ancillary; + + uint32_t optimize_option; + + struct spng_ihdr ihdr; + + struct spng_plte plte; + + struct spng_chrm_int chrm_int; + struct spng_iccp iccp; + + uint32_t gama; + + struct spng_sbit sbit; + + uint8_t srgb_rendering_intent; + + uint32_t n_text; + struct spng_text2 *text_list; + + struct spng_bkgd bkgd; + struct spng_hist hist; + struct spng_trns trns; + struct spng_phys phys; + + uint32_t n_splt; + struct spng_splt *splt_list; + + struct spng_time time; + struct spng_offs offs; + struct spng_exif exif; + + uint32_t n_chunks; + struct spng_unknown_chunk *chunk_list; + + struct spng_subimage subimage[7]; + + z_stream zstream; + unsigned char *scanline_buf, *prev_scanline_buf, *row_buf, *filtered_scanline_buf; + unsigned char *scanline, *prev_scanline, *row, *filtered_scanline; + + /* based on fmt */ + size_t image_size; /* may be zero */ + size_t image_width; + + unsigned bytes_per_pixel; /* derived from ihdr */ + unsigned pixel_size; /* derived from spng_format+ihdr */ + int widest_pass; + int last_pass; /* last non-empty pass */ + + uint16_t *gamma_lut; /* points to either _lut8 or _lut16 */ + uint16_t *gamma_lut16; + uint16_t gamma_lut8[256]; + unsigned char trns_px[8]; + union spng__decode_plte decode_plte; + struct spng_sbit decode_sb; + struct decode_flags decode_flags; + struct spng_row_info row_info; + + struct encode_flags encode_flags; +}; + +static const uint32_t spng_u32max = INT32_MAX; + +static const uint32_t adam7_x_start[7] = { 0, 4, 0, 2, 0, 1, 0 }; +static const uint32_t adam7_y_start[7] = { 0, 0, 4, 0, 2, 0, 1 }; +static const uint32_t adam7_x_delta[7] = { 8, 8, 4, 4, 2, 2, 1 }; +static const uint32_t adam7_y_delta[7] = { 8, 8, 8, 4, 4, 2, 2 }; + +static const uint8_t spng_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + +static const uint8_t type_ihdr[4] = { 73, 72, 68, 82 }; +static const uint8_t type_plte[4] = { 80, 76, 84, 69 }; +static const uint8_t type_idat[4] = { 73, 68, 65, 84 }; +static const uint8_t type_iend[4] = { 73, 69, 78, 68 }; + +static const uint8_t type_trns[4] = { 116, 82, 78, 83 }; +static const uint8_t type_chrm[4] = { 99, 72, 82, 77 }; +static const uint8_t type_gama[4] = { 103, 65, 77, 65 }; +static const uint8_t type_iccp[4] = { 105, 67, 67, 80 }; +static const uint8_t type_sbit[4] = { 115, 66, 73, 84 }; +static const uint8_t type_srgb[4] = { 115, 82, 71, 66 }; +static const uint8_t type_text[4] = { 116, 69, 88, 116 }; +static const uint8_t type_ztxt[4] = { 122, 84, 88, 116 }; +static const uint8_t type_itxt[4] = { 105, 84, 88, 116 }; +static const uint8_t type_bkgd[4] = { 98, 75, 71, 68 }; +static const uint8_t type_hist[4] = { 104, 73, 83, 84 }; +static const uint8_t type_phys[4] = { 112, 72, 89, 115 }; +static const uint8_t type_splt[4] = { 115, 80, 76, 84 }; +static const uint8_t type_time[4] = { 116, 73, 77, 69 }; + +static const uint8_t type_offs[4] = { 111, 70, 70, 115 }; +static const uint8_t type_exif[4] = { 101, 88, 73, 102 }; + +static inline void *spng__malloc(spng_ctx *ctx, size_t size) +{ + return ctx->alloc.malloc_fn(size); +} + +static inline void *spng__calloc(spng_ctx *ctx, size_t nmemb, size_t size) +{ + return ctx->alloc.calloc_fn(nmemb, size); +} + +static inline void *spng__realloc(spng_ctx *ctx, void *ptr, size_t size) +{ + return ctx->alloc.realloc_fn(ptr, size); +} + +static inline void spng__free(spng_ctx *ctx, void *ptr) +{ + ctx->alloc.free_fn(ptr); +} + +#if defined(SPNG_USE_MINIZ) +static void *spng__zalloc(void *opaque, size_t items, size_t size) +#else +static void *spng__zalloc(void *opaque, uInt items, uInt size) +#endif +{ + spng_ctx *ctx = opaque; + + if(size > SIZE_MAX / items) return NULL; + + size_t len = (size_t)items * size; + + return spng__malloc(ctx, len); +} + +static void spng__zfree(void *opqaue, void *ptr) +{ + spng_ctx *ctx = opqaue; + spng__free(ctx, ptr); +} + +static inline uint16_t read_u16(const void *src) +{ + const unsigned char *data = src; + + return (data[0] & 0xFFU) << 8 | (data[1] & 0xFFU); +} + +static inline uint32_t read_u32(const void *src) +{ + const unsigned char *data = src; + + return (data[0] & 0xFFUL) << 24 | (data[1] & 0xFFUL) << 16 | + (data[2] & 0xFFUL) << 8 | (data[3] & 0xFFUL); +} + +static inline int32_t read_s32(const void *src) +{ + int32_t ret = (int32_t)read_u32(src); + + return ret; +} + +static inline void write_u16(void *dest, uint16_t x) +{ + unsigned char *data = dest; + + data[0] = x >> 8; + data[1] = x & 0xFF; +} + +static inline void write_u32(void *dest, uint32_t x) +{ + unsigned char *data = dest; + + data[0] = (x >> 24); + data[1] = (x >> 16) & 0xFF; + data[2] = (x >> 8) & 0xFF; + data[3] = x & 0xFF; +} + +static inline void write_s32(void *dest, int32_t x) +{ + uint32_t n = x; + write_u32(dest, n); +} + +/* Returns an iterator for 1,2,4,8-bit samples */ +static struct spng__iter spng__iter_init(unsigned bit_depth, const unsigned char *samples) +{ + struct spng__iter iter = + { + .mask = (uint32_t)(1 << bit_depth) - 1, + .shift_amount = 8 - bit_depth, + .initial_shift = 8 - bit_depth, + .bit_depth = bit_depth, + .samples = samples + }; + + return iter; +} + +/* Returns the current sample unpacked, iterates to the next one */ +static inline uint8_t get_sample(struct spng__iter *iter) +{ + uint8_t x = (iter->samples[0] >> iter->shift_amount) & iter->mask; + + iter->shift_amount -= iter->bit_depth; + + if(iter->shift_amount > 7) + { + iter->shift_amount = iter->initial_shift; + iter->samples++; + } + + return x; +} + +static void u16_row_to_host(void *row, size_t size) +{ + uint16_t *px = row; + size_t i, n = size / 2; + + for(i=0; i < n; i++) + { + px[i] = read_u16(&px[i]); + } +} + +static void u16_row_to_bigendian(void *row, size_t size) +{ + uint16_t *px = (uint16_t*)row; + size_t i, n = size / 2; + + for(i=0; i < n; i++) + { + write_u16(&px[i], px[i]); + } +} + +static void rgb8_row_to_rgba8(const unsigned char *row, unsigned char *out, uint32_t n) +{ + uint32_t i; + for(i=0; i < n; i++) + { + memcpy(out + i * 4, row + i * 3, 3); + out[i*4+3] = 255; + } +} + +static unsigned num_channels(const struct spng_ihdr *ihdr) +{ + switch(ihdr->color_type) + { + case SPNG_COLOR_TYPE_TRUECOLOR: return 3; + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: return 2; + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: return 4; + case SPNG_COLOR_TYPE_GRAYSCALE: + case SPNG_COLOR_TYPE_INDEXED: + return 1; + default: return 0; + } +} + +/* Calculate scanline width in bits, round up to the nearest byte */ +static int calculate_scanline_width(const struct spng_ihdr *ihdr, uint32_t width, size_t *scanline_width) +{ + if(ihdr == NULL || !width) return SPNG_EINTERNAL; + + size_t res = num_channels(ihdr) * ihdr->bit_depth; + + if(res > SIZE_MAX / width) return SPNG_EOVERFLOW; + res = res * width; + + res += 15; /* Filter byte + 7 for rounding */ + + if(res < 15) return SPNG_EOVERFLOW; + + res /= 8; + + if(res > UINT32_MAX) return SPNG_EOVERFLOW; + + *scanline_width = res; + + return 0; +} + +static int calculate_subimages(struct spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + struct spng_ihdr *ihdr = &ctx->ihdr; + struct spng_subimage *sub = ctx->subimage; + + if(ihdr->interlace_method == 1) + { + sub[0].width = (ihdr->width + 7) >> 3; + sub[0].height = (ihdr->height + 7) >> 3; + sub[1].width = (ihdr->width + 3) >> 3; + sub[1].height = (ihdr->height + 7) >> 3; + sub[2].width = (ihdr->width + 3) >> 2; + sub[2].height = (ihdr->height + 3) >> 3; + sub[3].width = (ihdr->width + 1) >> 2; + sub[3].height = (ihdr->height + 3) >> 2; + sub[4].width = (ihdr->width + 1) >> 1; + sub[4].height = (ihdr->height + 1) >> 2; + sub[5].width = ihdr->width >> 1; + sub[5].height = (ihdr->height + 1) >> 1; + sub[6].width = ihdr->width; + sub[6].height = ihdr->height >> 1; + } + else + { + sub[0].width = ihdr->width; + sub[0].height = ihdr->height; + } + + int i; + for(i=0; i < 7; i++) + { + if(sub[i].width == 0 || sub[i].height == 0) continue; + + int ret = calculate_scanline_width(ihdr, sub[i].width, &sub[i].scanline_width); + if(ret) return ret; + + if(sub[ctx->widest_pass].scanline_width < sub[i].scanline_width) ctx->widest_pass = i; + + ctx->last_pass = i; + } + + return 0; +} + +static int check_decode_fmt(const struct spng_ihdr *ihdr, const int fmt) +{ + switch(fmt) + { + case SPNG_FMT_RGBA8: + case SPNG_FMT_RGBA16: + case SPNG_FMT_RGB8: + case SPNG_FMT_PNG: + case SPNG_FMT_RAW: + return 0; + case SPNG_FMT_G8: + case SPNG_FMT_GA8: + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth <= 8) return 0; + else return SPNG_EFMT; + case SPNG_FMT_GA16: + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth == 16) return 0; + else return SPNG_EFMT; + default: return SPNG_EFMT; + } +} + +static int calculate_image_width(const struct spng_ihdr *ihdr, int fmt, size_t *len) +{ + if(ihdr == NULL || len == NULL) return SPNG_EINTERNAL; + + size_t res = ihdr->width; + unsigned bytes_per_pixel; + + switch(fmt) + { + case SPNG_FMT_RGBA8: + case SPNG_FMT_GA16: + bytes_per_pixel = 4; + break; + case SPNG_FMT_RGBA16: + bytes_per_pixel = 8; + break; + case SPNG_FMT_RGB8: + bytes_per_pixel = 3; + break; + case SPNG_FMT_PNG: + case SPNG_FMT_RAW: + { + int ret = calculate_scanline_width(ihdr, ihdr->width, &res); + if(ret) return ret; + + res -= 1; /* exclude filter byte */ + bytes_per_pixel = 1; + break; + } + case SPNG_FMT_G8: + bytes_per_pixel = 1; + break; + case SPNG_FMT_GA8: + bytes_per_pixel = 2; + break; + default: return SPNG_EINTERNAL; + } + + if(res > SIZE_MAX / bytes_per_pixel) return SPNG_EOVERFLOW; + res = res * bytes_per_pixel; + + *len = res; + + return 0; +} + +static int calculate_image_size(const struct spng_ihdr *ihdr, int fmt, size_t *len) +{ + if(ihdr == NULL || len == NULL) return SPNG_EINTERNAL; + + size_t res = 0; + + int ret = calculate_image_width(ihdr, fmt, &res); + if(ret) return ret; + + if(res > SIZE_MAX / ihdr->height) return SPNG_EOVERFLOW; + res = res * ihdr->height; + + *len = res; + + return 0; +} + +static int increase_cache_usage(spng_ctx *ctx, size_t bytes, int new_chunk) +{ + if(ctx == NULL || !bytes) return SPNG_EINTERNAL; + + if(new_chunk) + { + ctx->chunk_count_total++; + if(ctx->chunk_count_total < 1) return SPNG_EOVERFLOW; + + if(ctx->chunk_count_total > ctx->chunk_count_limit) return SPNG_ECHUNK_LIMITS; + } + + size_t new_usage = ctx->chunk_cache_usage + bytes; + + if(new_usage < ctx->chunk_cache_usage) return SPNG_EOVERFLOW; + + if(new_usage > ctx->chunk_cache_limit) return SPNG_ECHUNK_LIMITS; + + ctx->chunk_cache_usage = new_usage; + + return 0; +} + +static int decrease_cache_usage(spng_ctx *ctx, size_t usage) +{ + if(ctx == NULL || !usage) return SPNG_EINTERNAL; + if(usage > ctx->chunk_cache_usage) return SPNG_EINTERNAL; + + ctx->chunk_cache_usage -= usage; + + return 0; +} + +static int is_critical_chunk(struct spng_chunk *chunk) +{ + if(chunk == NULL) return 0; + if((chunk->type[0] & (1 << 5)) == 0) return 1; + + return 0; +} + +static int decode_err(spng_ctx *ctx, int err) +{ + ctx->state = SPNG_STATE_INVALID; + + return err; +} + +static int encode_err(spng_ctx *ctx, int err) +{ + ctx->state = SPNG_STATE_INVALID; + + return err; +} + +static inline int read_data(spng_ctx *ctx, size_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!bytes) return 0; + + if(ctx->streaming && (bytes > SPNG_READ_SIZE)) return SPNG_EINTERNAL; + + int ret = ctx->read_fn(ctx, ctx->stream_user_ptr, ctx->stream_buf, bytes); + + if(ret) + { + if(ret > 0 || ret < SPNG_IO_ERROR) ret = SPNG_IO_ERROR; + + return ret; + } + + ctx->bytes_read += bytes; + if(ctx->bytes_read < bytes) return SPNG_EOVERFLOW; + + return 0; +} + +/* Ensure there is enough space for encoding starting at ctx->write_ptr */ +static int require_bytes(spng_ctx *ctx, size_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + if(ctx->streaming) + { + if(bytes > ctx->stream_buf_size) + { + size_t new_size = ctx->stream_buf_size; + + /* Start at default IDAT size + header + crc */ + if(new_size < (SPNG_WRITE_SIZE + 12)) new_size = SPNG_WRITE_SIZE + 12; + + if(new_size < bytes) new_size = bytes; + + void *temp = spng__realloc(ctx, ctx->stream_buf, new_size); + + if(temp == NULL) return encode_err(ctx, SPNG_EMEM); + + ctx->stream_buf = temp; + ctx->stream_buf_size = bytes; + ctx->write_ptr = ctx->stream_buf; + } + + return 0; + } + + if(!ctx->internal_buffer) return SPNG_ENODST; + + size_t required = ctx->bytes_encoded + bytes; + if(required < bytes) return SPNG_EOVERFLOW; + + if(required > ctx->out_png_size) + { + size_t new_size = ctx->out_png_size; + + /* Start with a size that doesn't require a realloc() 100% of the time */ + if(new_size < (SPNG_WRITE_SIZE * 2)) new_size = SPNG_WRITE_SIZE * 2; + + /* Prefer the next power of two over the requested size */ + while(new_size < required) + { + if(new_size / SIZE_MAX > 2) return encode_err(ctx, SPNG_EOVERFLOW); + + new_size *= 2; + } + + void *temp = spng__realloc(ctx, ctx->out_png, new_size); + + if(temp == NULL) return encode_err(ctx, SPNG_EMEM); + + ctx->out_png = temp; + ctx->out_png_size = new_size; + ctx->write_ptr = ctx->out_png + ctx->bytes_encoded; + } + + return 0; +} + +static int write_data(spng_ctx *ctx, const void *data, size_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!bytes) return 0; + + if(ctx->streaming) + { + if(bytes > SPNG_WRITE_SIZE) return SPNG_EINTERNAL; + + int ret = ctx->write_fn(ctx, ctx->stream_user_ptr, (void*)data, bytes); + + if(ret) + { + if(ret > 0 || ret < SPNG_IO_ERROR) ret = SPNG_IO_ERROR; + + return encode_err(ctx, ret); + } + } + else + { + int ret = require_bytes(ctx, bytes); + if(ret) return encode_err(ctx, ret); + + memcpy(ctx->write_ptr, data, bytes); + + ctx->write_ptr += bytes; + } + + ctx->bytes_encoded += bytes; + if(ctx->bytes_encoded < bytes) return SPNG_EOVERFLOW; + + return 0; +} + +static int write_header(spng_ctx *ctx, const uint8_t chunk_type[4], size_t chunk_length, unsigned char **data) +{ + if(ctx == NULL || chunk_type == NULL) return SPNG_EINTERNAL; + if(chunk_length > spng_u32max) return SPNG_EINTERNAL; + + size_t total = chunk_length + 12; + + int ret = require_bytes(ctx, total); + if(ret) return ret; + + uint32_t crc = crc32(0, NULL, 0); + ctx->current_chunk.crc = crc32(crc, chunk_type, 4); + + memcpy(&ctx->current_chunk.type, chunk_type, 4); + ctx->current_chunk.length = (uint32_t)chunk_length; + + if(!data) return SPNG_EINTERNAL; + + if(ctx->streaming) *data = ctx->stream_buf + 8; + else *data = ctx->write_ptr + 8; + + return 0; +} + +static int trim_chunk(spng_ctx *ctx, uint32_t length) +{ + if(length > spng_u32max) return SPNG_EINTERNAL; + if(length > ctx->current_chunk.length) return SPNG_EINTERNAL; + + ctx->current_chunk.length = length; + + return 0; +} + +static int finish_chunk(spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + struct spng_chunk *chunk = &ctx->current_chunk; + + unsigned char *header; + unsigned char *chunk_data; + + if(ctx->streaming) + { + chunk_data = ctx->stream_buf + 8; + header = ctx->stream_buf; + } + else + { + chunk_data = ctx->write_ptr + 8; + header = ctx->write_ptr; + } + + write_u32(header, chunk->length); + memcpy(header + 4, chunk->type, 4); + + chunk->crc = crc32(chunk->crc, chunk_data, chunk->length); + + write_u32(chunk_data + chunk->length, chunk->crc); + + if(ctx->streaming) + { + const unsigned char *ptr = ctx->stream_buf; + uint32_t bytes_left = chunk->length + 12; + uint32_t len = 0; + + while(bytes_left) + { + ptr += len; + len = SPNG_WRITE_SIZE; + + if(len > bytes_left) len = bytes_left; + + int ret = write_data(ctx, ptr, len); + if(ret) return ret; + + bytes_left -= len; + } + } + else + { + ctx->bytes_encoded += chunk->length; + if(ctx->bytes_encoded < chunk->length) return SPNG_EOVERFLOW; + + ctx->bytes_encoded += 12; + if(ctx->bytes_encoded < 12) return SPNG_EOVERFLOW; + + ctx->write_ptr += chunk->length + 12; + } + + return 0; +} + +static int write_chunk(spng_ctx *ctx, const uint8_t type[4], const void *data, size_t length) +{ + if(ctx == NULL || type == NULL) return SPNG_EINTERNAL; + if(length && data == NULL) return SPNG_EINTERNAL; + + unsigned char *write_ptr; + + int ret = write_header(ctx, type, length, &write_ptr); + if(ret) return ret; + + if(length) memcpy(write_ptr, data, length); + + return finish_chunk(ctx); +} + +static int write_iend(spng_ctx *ctx) +{ + unsigned char iend_chunk[12] = { 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 }; + return write_data(ctx, iend_chunk, 12); +} + +static int write_unknown_chunks(spng_ctx *ctx, enum spng_location location) +{ + if(!ctx->stored.unknown) return 0; + + const struct spng_unknown_chunk *chunk = ctx->chunk_list; + + uint32_t i; + for(i=0; i < ctx->n_chunks; i++, chunk++) + { + if(chunk->location != location) continue; + + int ret = write_chunk(ctx, chunk->type, chunk->data, chunk->length); + if(ret) return ret; + } + + return 0; +} + +/* Read and check the current chunk's crc, + returns -SPNG_CRC_DISCARD if the chunk should be discarded */ +static inline int read_and_check_crc(spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + int ret; + ret = read_data(ctx, 4); + if(ret) return ret; + + ctx->current_chunk.crc = read_u32(ctx->data); + + if(ctx->skip_crc) return 0; + + if(ctx->cur_actual_crc != ctx->current_chunk.crc) + { + if(is_critical_chunk(&ctx->current_chunk)) + { + if(ctx->crc_action_critical == SPNG_CRC_USE) return 0; + } + else + { + if(ctx->crc_action_ancillary == SPNG_CRC_USE) return 0; + if(ctx->crc_action_ancillary == SPNG_CRC_DISCARD) return -SPNG_CRC_DISCARD; + } + + return SPNG_ECHUNK_CRC; + } + + return 0; +} + +/* Read and validate the current chunk's crc and the next chunk header */ +static inline int read_header(spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + int ret; + struct spng_chunk chunk = { 0 }; + + ret = read_and_check_crc(ctx); + if(ret) + { + if(ret == -SPNG_CRC_DISCARD) + { + ctx->discard = 1; + } + else return ret; + } + + ret = read_data(ctx, 8); + if(ret) return ret; + + chunk.offset = ctx->bytes_read - 8; + + chunk.length = read_u32(ctx->data); + + memcpy(&chunk.type, ctx->data + 4, 4); + + if(chunk.length > spng_u32max) return SPNG_ECHUNK_STDLEN; + + ctx->cur_chunk_bytes_left = chunk.length; + + if(is_critical_chunk(&chunk) && ctx->crc_action_critical == SPNG_CRC_USE) ctx->skip_crc = 1; + else if(ctx->crc_action_ancillary == SPNG_CRC_USE) ctx->skip_crc = 1; + else ctx->skip_crc = 0; + + if(!ctx->skip_crc) + { + ctx->cur_actual_crc = crc32(0, NULL, 0); + ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, chunk.type, 4); + } + + ctx->current_chunk = chunk; + + return 0; +} + +/* Read chunk bytes and update crc */ +static int read_chunk_bytes(spng_ctx *ctx, uint32_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!ctx->cur_chunk_bytes_left || !bytes) return SPNG_EINTERNAL; + if(bytes > ctx->cur_chunk_bytes_left) return SPNG_EINTERNAL; /* XXX: more specific error? */ + + int ret; + + ret = read_data(ctx, bytes); + if(ret) return ret; + + if(!ctx->skip_crc) ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, ctx->data, bytes); + + ctx->cur_chunk_bytes_left -= bytes; + + return ret; +} + +/* read_chunk_bytes() + read_data() with custom output buffer */ +static int read_chunk_bytes2(spng_ctx *ctx, void *out, uint32_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!ctx->cur_chunk_bytes_left || !bytes) return SPNG_EINTERNAL; + if(bytes > ctx->cur_chunk_bytes_left) return SPNG_EINTERNAL; /* XXX: more specific error? */ + + int ret; + uint32_t len = bytes; + + if(ctx->streaming && len > SPNG_READ_SIZE) len = SPNG_READ_SIZE; + + while(bytes) + { + if(len > bytes) len = bytes; + + ret = ctx->read_fn(ctx, ctx->stream_user_ptr, out, len); + if(ret) return ret; + + if(!ctx->streaming) memcpy(out, ctx->data, len); + + ctx->bytes_read += len; + if(ctx->bytes_read < len) return SPNG_EOVERFLOW; + + if(!ctx->skip_crc) ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, out, len); + + ctx->cur_chunk_bytes_left -= len; + + out = (char*)out + len; + bytes -= len; + len = SPNG_READ_SIZE; + } + + return 0; +} + +static int discard_chunk_bytes(spng_ctx *ctx, uint32_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!bytes) return 0; + + int ret; + + if(ctx->streaming) /* Do small, consecutive reads */ + { + while(bytes) + { + uint32_t len = SPNG_READ_SIZE; + + if(len > bytes) len = bytes; + + ret = read_chunk_bytes(ctx, len); + if(ret) return ret; + + bytes -= len; + } + } + else + { + ret = read_chunk_bytes(ctx, bytes); + if(ret) return ret; + } + + return 0; +} + +static int spng__inflate_init(spng_ctx *ctx, int window_bits) +{ + if(ctx->zstream.state) inflateEnd(&ctx->zstream); + + ctx->inflate = 1; + + ctx->zstream.zalloc = spng__zalloc; + ctx->zstream.zfree = spng__zfree; + ctx->zstream.opaque = ctx; + + if(inflateInit2(&ctx->zstream, window_bits) != Z_OK) return SPNG_EZLIB_INIT; + +#if ZLIB_VERNUM >= 0x1290 && !defined(SPNG_USE_MINIZ) + + int validate = 1; + + if(ctx->flags & SPNG_CTX_IGNORE_ADLER32) validate = 0; + + if(is_critical_chunk(&ctx->current_chunk)) + { + if(ctx->crc_action_critical == SPNG_CRC_USE) validate = 0; + } + else /* ancillary */ + { + if(ctx->crc_action_ancillary == SPNG_CRC_USE) validate = 0; + } + + if(inflateValidate(&ctx->zstream, validate)) return SPNG_EZLIB_INIT; + +#else /* This requires zlib >= 1.2.11 */ + #pragma message ("inflateValidate() not available, SPNG_CTX_IGNORE_ADLER32 will be ignored") +#endif + + return 0; +} + +static int spng__deflate_init(spng_ctx *ctx, struct spng__zlib_options *options) +{ + if(ctx->zstream.state) deflateEnd(&ctx->zstream); + + ctx->deflate = 1; + + z_stream *zstream = &ctx->zstream; + zstream->zalloc = spng__zalloc; + zstream->zfree = spng__zfree; + zstream->opaque = ctx; + zstream->data_type = options->data_type; + + int ret = deflateInit2(zstream, options->compression_level, Z_DEFLATED, options->window_bits, options->mem_level, options->strategy); + + if(ret != Z_OK) return SPNG_EZLIB_INIT; + + return 0; +} + +/* Inflate a zlib stream starting with start_buf if non-NULL, + continuing from the datastream till an end marker, + allocating and writing the inflated stream to *out, + leaving "extra" bytes at the end, final buffer length is *len. + + Takes into account the chunk size and cache limits. +*/ +static int spng__inflate_stream(spng_ctx *ctx, char **out, size_t *len, size_t extra, const void *start_buf, size_t start_len) +{ + int ret = spng__inflate_init(ctx, 15); + if(ret) return ret; + + size_t max = ctx->chunk_cache_limit - ctx->chunk_cache_usage; + + if(ctx->max_chunk_size < max) max = ctx->max_chunk_size; + + if(extra > max) return SPNG_ECHUNK_LIMITS; + max -= extra; + + uint32_t read_size; + size_t size = 8 * 1024; + void *t, *buf = spng__malloc(ctx, size); + + if(buf == NULL) return SPNG_EMEM; + + z_stream *stream = &ctx->zstream; + + if(start_buf != NULL && start_len) + { + stream->avail_in = (uInt)start_len; + stream->next_in = start_buf; + } + else + { + stream->avail_in = 0; + stream->next_in = NULL; + } + + stream->avail_out = (uInt)size; + stream->next_out = buf; + + while(ret != Z_STREAM_END) + { + ret = inflate(stream, Z_NO_FLUSH); + + if(ret == Z_STREAM_END) break; + + if(ret != Z_OK && ret != Z_BUF_ERROR) + { + ret = SPNG_EZLIB; + goto err; + } + + if(!stream->avail_out) /* Resize buffer */ + { + /* overflow or reached chunk/cache limit */ + if( (2 > SIZE_MAX / size) || (size > max / 2) ) + { + ret = SPNG_ECHUNK_LIMITS; + goto err; + } + + size *= 2; + + t = spng__realloc(ctx, buf, size); + if(t == NULL) goto mem; + + buf = t; + + stream->avail_out = (uInt)size / 2; + stream->next_out = (unsigned char*)buf + size / 2; + } + else if(!stream->avail_in) /* Read more chunk bytes */ + { + read_size = ctx->cur_chunk_bytes_left; + if(ctx->streaming && read_size > SPNG_READ_SIZE) read_size = SPNG_READ_SIZE; + + ret = read_chunk_bytes(ctx, read_size); + + if(ret) + { + if(!read_size) ret = SPNG_EZLIB; + + goto err; + } + + stream->avail_in = read_size; + stream->next_in = ctx->data; + } + } + + size = stream->total_out; + + if(!size) + { + ret = SPNG_EZLIB; + goto err; + } + + size += extra; + if(size < extra) goto mem; + + t = spng__realloc(ctx, buf, size); + if(t == NULL) goto mem; + + buf = t; + + (void)increase_cache_usage(ctx, size, 0); + + *out = buf; + *len = size; + + return 0; + +mem: + ret = SPNG_EMEM; +err: + spng__free(ctx, buf); + return ret; +} + +/* Read at least one byte from the IDAT stream */ +static int read_idat_bytes(spng_ctx *ctx, uint32_t *bytes_read) +{ + if(ctx == NULL || bytes_read == NULL) return SPNG_EINTERNAL; + if(memcmp(ctx->current_chunk.type, type_idat, 4)) return SPNG_EIDAT_TOO_SHORT; + + int ret; + uint32_t len; + + while(!ctx->cur_chunk_bytes_left) + { + ret = read_header(ctx); + if(ret) return ret; + + if(memcmp(ctx->current_chunk.type, type_idat, 4)) return SPNG_EIDAT_TOO_SHORT; + } + + if(ctx->streaming) + {/* TODO: estimate bytes to read for progressive reads */ + len = SPNG_READ_SIZE; + if(len > ctx->cur_chunk_bytes_left) len = ctx->cur_chunk_bytes_left; + } + else len = ctx->current_chunk.length; + + ret = read_chunk_bytes(ctx, len); + + *bytes_read = len; + + return ret; +} + +static int read_scanline_bytes(spng_ctx *ctx, unsigned char *dest, size_t len) +{ + if(ctx == NULL || dest == NULL) return SPNG_EINTERNAL; + + int ret = Z_OK; + uint32_t bytes_read; + + z_stream *zstream = &ctx->zstream; + + zstream->avail_out = (uInt)len; + zstream->next_out = dest; + + while(zstream->avail_out != 0) + { + ret = inflate(zstream, Z_NO_FLUSH); + + if(ret == Z_OK) continue; + + if(ret == Z_STREAM_END) /* Reached an end-marker */ + { + if(zstream->avail_out != 0) return SPNG_EIDAT_TOO_SHORT; + } + else if(ret == Z_BUF_ERROR) /* Read more IDAT bytes */ + { + ret = read_idat_bytes(ctx, &bytes_read); + if(ret) return ret; + + zstream->avail_in = bytes_read; + zstream->next_in = ctx->data; + } + else return SPNG_EIDAT_STREAM; + } + + return 0; +} + +static uint8_t paeth(uint8_t a, uint8_t b, uint8_t c) +{ + int16_t p = a + b - c; + int16_t pa = abs(p - a); + int16_t pb = abs(p - b); + int16_t pc = abs(p - c); + + if(pa <= pb && pa <= pc) return a; + else if(pb <= pc) return b; + + return c; +} + +SPNG_TARGET_CLONES("default,avx2") +static void defilter_up(size_t bytes, unsigned char *row, const unsigned char *prev) +{ + size_t i; + for(i=0; i < bytes; i++) + { + row[i] += prev[i]; + } +} + +/* Defilter *scanline in-place. + *prev_scanline and *scanline should point to the first pixel, + scanline_width is the width of the scanline including the filter byte. +*/ +static int defilter_scanline(const unsigned char *prev_scanline, unsigned char *scanline, + size_t scanline_width, unsigned bytes_per_pixel, unsigned filter) +{ + if(prev_scanline == NULL || scanline == NULL || !scanline_width) return SPNG_EINTERNAL; + + size_t i; + scanline_width--; + + if(filter == 0) return 0; + +#ifndef SPNG_DISABLE_OPT + if(filter == SPNG_FILTER_UP) goto no_opt; + + if(bytes_per_pixel == 4) + { + if(filter == SPNG_FILTER_SUB) + defilter_sub4(scanline_width, scanline); + else if(filter == SPNG_FILTER_AVERAGE) + defilter_avg4(scanline_width, scanline, prev_scanline); + else if(filter == SPNG_FILTER_PAETH) + defilter_paeth4(scanline_width, scanline, prev_scanline); + else return SPNG_EFILTER; + + return 0; + } + else if(bytes_per_pixel == 3) + { + if(filter == SPNG_FILTER_SUB) + defilter_sub3(scanline_width, scanline); + else if(filter == SPNG_FILTER_AVERAGE) + defilter_avg3(scanline_width, scanline, prev_scanline); + else if(filter == SPNG_FILTER_PAETH) + defilter_paeth3(scanline_width, scanline, prev_scanline); + else return SPNG_EFILTER; + + return 0; + } +no_opt: +#endif + + if(filter == SPNG_FILTER_UP) + { + defilter_up(scanline_width, scanline, prev_scanline); + return 0; + } + + for(i=0; i < scanline_width; i++) + { + uint8_t x, a, b, c; + + if(i >= bytes_per_pixel) + { + a = scanline[i - bytes_per_pixel]; + b = prev_scanline[i]; + c = prev_scanline[i - bytes_per_pixel]; + } + else /* First pixel in row */ + { + a = 0; + b = prev_scanline[i]; + c = 0; + } + + x = scanline[i]; + + switch(filter) + { + case SPNG_FILTER_SUB: + { + x = x + a; + break; + } + case SPNG_FILTER_AVERAGE: + { + uint16_t avg = (a + b) / 2; + x = x + avg; + break; + } + case SPNG_FILTER_PAETH: + { + x = x + paeth(a,b,c); + break; + } + } + + scanline[i] = x; + } + + return 0; +} + +static int filter_scanline(unsigned char *filtered, const unsigned char *prev_scanline, const unsigned char *scanline, + size_t scanline_width, unsigned bytes_per_pixel, const unsigned filter) +{ + if(prev_scanline == NULL || scanline == NULL || scanline_width <= 1) return SPNG_EINTERNAL; + + if(filter > 4) return SPNG_EFILTER; + if(filter == 0) return 0; + + scanline_width--; + + uint32_t i; + for(i=0; i < scanline_width; i++) + { + uint8_t x, a, b, c; + + if(i >= bytes_per_pixel) + { + a = scanline[i - bytes_per_pixel]; + b = prev_scanline[i]; + c = prev_scanline[i - bytes_per_pixel]; + } + else /* first pixel in row */ + { + a = 0; + b = prev_scanline[i]; + c = 0; + } + + x = scanline[i]; + + switch(filter) + { + case SPNG_FILTER_SUB: + { + x = x - a; + break; + } + case SPNG_FILTER_UP: + { + x = x - b; + break; + } + case SPNG_FILTER_AVERAGE: + { + uint16_t avg = (a + b) / 2; + x = x - avg; + break; + } + case SPNG_FILTER_PAETH: + { + x = x - paeth(a,b,c); + break; + } + } + + filtered[i] = x; + } + + return 0; +} + +static int32_t filter_sum(const unsigned char *prev_scanline, const unsigned char *scanline, + size_t size, unsigned bytes_per_pixel, const unsigned filter) +{ + /* prevent potential over/underflow, bails out at a width of ~8M pixels for RGBA8 */ + if(size > (INT32_MAX / 128)) return INT32_MAX; + + uint32_t i; + int32_t sum = 0; + uint8_t x, a, b, c; + + for(i=0; i < size; i++) + { + if(i >= bytes_per_pixel) + { + a = scanline[i - bytes_per_pixel]; + b = prev_scanline[i]; + c = prev_scanline[i - bytes_per_pixel]; + } + else /* first pixel in row */ + { + a = 0; + b = prev_scanline[i]; + c = 0; + } + + x = scanline[i]; + + switch(filter) + { + case SPNG_FILTER_NONE: + { + break; + } + case SPNG_FILTER_SUB: + { + x = x - a; + break; + } + case SPNG_FILTER_UP: + { + x = x - b; + break; + } + case SPNG_FILTER_AVERAGE: + { + uint16_t avg = (a + b) / 2; + x = x - avg; + break; + } + case SPNG_FILTER_PAETH: + { + x = x - paeth(a,b,c); + break; + } + } + + sum += 128 - abs((int)x - 128); + } + + return sum; +} + +static unsigned get_best_filter(const unsigned char *prev_scanline, const unsigned char *scanline, + size_t scanline_width, unsigned bytes_per_pixel, const int choices) +{ + if(!choices) return SPNG_FILTER_NONE; + + scanline_width--; + + int i; + unsigned int best_filter = 0; + enum spng_filter_choice flag; + int32_t sum, best_score = INT32_MAX; + int32_t filter_scores[5] = { INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX }; + + if( !(choices & (choices - 1)) ) + {/* only one choice/bit is set */ + for(i=0; i < 5; i++) + { + if(choices == 1 << (i + 3)) return i; + } + } + + for(i=0; i < 5; i++) + { + flag = 1 << (i + 3); + + if(choices & flag) sum = filter_sum(prev_scanline, scanline, scanline_width, bytes_per_pixel, i); + else continue; + + filter_scores[i] = abs(sum); + + if(filter_scores[i] < best_score) + { + best_score = filter_scores[i]; + best_filter = i; + } + } + + return best_filter; +} + +/* Scale "sbits" significant bits in "sample" from "bit_depth" to "target" + + "bit_depth" must be a valid PNG depth + "sbits" must be less than or equal to "bit_depth" + "target" must be between 1 and 16 +*/ +static uint16_t sample_to_target(uint16_t sample, unsigned bit_depth, unsigned sbits, unsigned target) +{ + if(bit_depth == sbits) + { + if(target == sbits) return sample; /* No scaling */ + }/* bit_depth > sbits */ + else sample = sample >> (bit_depth - sbits); /* Shift significant bits to bottom */ + + /* Downscale */ + if(target < sbits) return sample >> (sbits - target); + + /* Upscale using left bit replication */ + int8_t shift_amount = target - sbits; + uint16_t sample_bits = sample; + sample = 0; + + while(shift_amount >= 0) + { + sample = sample | (sample_bits << shift_amount); + shift_amount -= sbits; + } + + int8_t partial = shift_amount + (int8_t)sbits; + + if(partial != 0) sample = sample | (sample_bits >> abs(shift_amount)); + + return sample; +} + +static inline void gamma_correct_row(unsigned char *row, uint32_t pixels, int fmt, const uint16_t *gamma_lut) +{ + uint32_t i; + + if(fmt == SPNG_FMT_RGBA8) + { + unsigned char *px; + for(i=0; i < pixels; i++) + { + px = row + i * 4; + + px[0] = gamma_lut[px[0]]; + px[1] = gamma_lut[px[1]]; + px[2] = gamma_lut[px[2]]; + } + } + else if(fmt == SPNG_FMT_RGBA16) + { + for(i=0; i < pixels; i++) + { + uint16_t px[4]; + memcpy(px, row + i * 8, 8); + + px[0] = gamma_lut[px[0]]; + px[1] = gamma_lut[px[1]]; + px[2] = gamma_lut[px[2]]; + + memcpy(row + i * 8, px, 8); + } + } + else if(fmt == SPNG_FMT_RGB8) + { + unsigned char *px; + for(i=0; i < pixels; i++) + { + px = row + i * 3; + + px[0] = gamma_lut[px[0]]; + px[1] = gamma_lut[px[1]]; + px[2] = gamma_lut[px[2]]; + } + } +} + +/* Apply transparency to output row */ +static inline void trns_row(unsigned char *row, + const unsigned char *scanline, + const unsigned char *trns, + unsigned scanline_stride, + struct spng_ihdr *ihdr, + uint32_t pixels, + int fmt) +{ + uint32_t i; + unsigned row_stride; + unsigned depth = ihdr->bit_depth; + + if(fmt == SPNG_FMT_RGBA8) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) return; /* already applied in the decoding loop */ + + row_stride = 4; + for(i=0; i < pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, scanline_stride)) row[3] = 0; + } + } + else if(fmt == SPNG_FMT_RGBA16) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) return; /* already applied in the decoding loop */ + + row_stride = 8; + for(i=0; i < pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, scanline_stride)) memset(row + 6, 0, 2); + } + } + else if(fmt == SPNG_FMT_GA8) + { + row_stride = 2; + + if(depth == 16) + { + for(i=0; i < pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, scanline_stride)) memset(row + 1, 0, 1); + } + } + else /* depth <= 8 */ + { + struct spng__iter iter = spng__iter_init(depth, scanline); + + for(i=0; i < pixels; i++, row+=row_stride) + { + if(trns[0] == get_sample(&iter)) row[1] = 0; + } + } + } + else if(fmt == SPNG_FMT_GA16) + { + row_stride = 4; + + if(depth == 16) + { + for(i=0; i< pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, 2)) memset(row + 2, 0, 2); + } + } + else + { + struct spng__iter iter = spng__iter_init(depth, scanline); + + for(i=0; i< pixels; i++, row+=row_stride) + { + if(trns[0] == get_sample(&iter)) memset(row + 2, 0, 2); + } + } + } + else return; +} + +static inline void scale_row(unsigned char *row, uint32_t pixels, int fmt, unsigned depth, const struct spng_sbit *sbit) +{ + uint32_t i; + + if(fmt == SPNG_FMT_RGBA8) + { + unsigned char px[4]; + for(i=0; i < pixels; i++) + { + memcpy(px, row + i * 4, 4); + + px[0] = sample_to_target(px[0], depth, sbit->red_bits, 8); + px[1] = sample_to_target(px[1], depth, sbit->green_bits, 8); + px[2] = sample_to_target(px[2], depth, sbit->blue_bits, 8); + px[3] = sample_to_target(px[3], depth, sbit->alpha_bits, 8); + + memcpy(row + i * 4, px, 4); + } + } + else if(fmt == SPNG_FMT_RGBA16) + { + uint16_t px[4]; + for(i=0; i < pixels; i++) + { + memcpy(px, row + i * 8, 8); + + px[0] = sample_to_target(px[0], depth, sbit->red_bits, 16); + px[1] = sample_to_target(px[1], depth, sbit->green_bits, 16); + px[2] = sample_to_target(px[2], depth, sbit->blue_bits, 16); + px[3] = sample_to_target(px[3], depth, sbit->alpha_bits, 16); + + memcpy(row + i * 8, px, 8); + } + } + else if(fmt == SPNG_FMT_RGB8) + { + unsigned char px[4]; + for(i=0; i < pixels; i++) + { + memcpy(px, row + i * 3, 3); + + px[0] = sample_to_target(px[0], depth, sbit->red_bits, 8); + px[1] = sample_to_target(px[1], depth, sbit->green_bits, 8); + px[2] = sample_to_target(px[2], depth, sbit->blue_bits, 8); + + memcpy(row + i * 3, px, 3); + } + } + else if(fmt == SPNG_FMT_G8) + { + for(i=0; i < pixels; i++) + { + row[i] = sample_to_target(row[i], depth, sbit->grayscale_bits, 8); + } + } + else if(fmt == SPNG_FMT_GA8) + { + for(i=0; i < pixels; i++) + { + row[i*2] = sample_to_target(row[i*2], depth, sbit->grayscale_bits, 8); + } + } +} + +/* Expand to *row using 8-bit palette indices from *scanline */ +static void expand_row(unsigned char *row, + const unsigned char *scanline, + const union spng__decode_plte *decode_plte, + uint32_t width, + int fmt) +{ + uint32_t i = 0; + unsigned char *px; + unsigned char entry; + const struct spng_plte_entry *plte = decode_plte->rgba; + +#if defined(SPNG_ARM) + if(fmt == SPNG_FMT_RGBA8) i = expand_palette_rgba8_neon(row, scanline, decode_plte->raw, width); + else if(fmt == SPNG_FMT_RGB8) + { + i = expand_palette_rgb8_neon(row, scanline, decode_plte->raw, width); + + for(; i < width; i++) + {/* In this case the LUT is 3 bytes packed */ + px = row + i * 3; + entry = scanline[i]; + px[0] = decode_plte->raw[entry * 3 + 0]; + px[1] = decode_plte->raw[entry * 3 + 1]; + px[2] = decode_plte->raw[entry * 3 + 2]; + } + return; + } +#endif + + if(fmt == SPNG_FMT_RGBA8) + { + for(; i < width; i++) + { + px = row + i * 4; + entry = scanline[i]; + px[0] = plte[entry].red; + px[1] = plte[entry].green; + px[2] = plte[entry].blue; + px[3] = plte[entry].alpha; + } + } + else if(fmt == SPNG_FMT_RGB8) + { + for(; i < width; i++) + { + px = row + i * 3; + entry = scanline[i]; + px[0] = plte[entry].red; + px[1] = plte[entry].green; + px[2] = plte[entry].blue; + } + } +} + +/* Unpack 1/2/4/8-bit samples to G8/GA8/GA16 or G16 -> GA16 */ +static void unpack_scanline(unsigned char *out, const unsigned char *scanline, uint32_t width, unsigned bit_depth, int fmt) +{ + struct spng__iter iter = spng__iter_init(bit_depth, scanline); + uint32_t i; + uint16_t sample, alpha = 65535; + + + if(fmt == SPNG_FMT_GA8) goto ga8; + else if(fmt == SPNG_FMT_GA16) goto ga16; + + /* 1/2/4-bit -> 8-bit */ + for(i=0; i < width; i++) out[i] = get_sample(&iter); + + return; + +ga8: + /* 1/2/4/8-bit -> GA8 */ + for(i=0; i < width; i++) + { + out[i*2] = get_sample(&iter); + out[i*2 + 1] = 255; + } + + return; + +ga16: + + /* 16 -> GA16 */ + if(bit_depth == 16) + { + for(i=0; i < width; i++) + { + memcpy(out + i * 4, scanline + i * 2, 2); + memcpy(out + i * 4 + 2, &alpha, 2); + } + return; + } + + /* 1/2/4/8-bit -> GA16 */ + for(i=0; i < width; i++) + { + sample = get_sample(&iter); + memcpy(out + i * 4, &sample, 2); + memcpy(out + i * 4 + 2, &alpha, 2); + } +} + +static int check_ihdr(const struct spng_ihdr *ihdr, uint32_t max_width, uint32_t max_height) +{ + if(ihdr->width > spng_u32max || !ihdr->width) return SPNG_EWIDTH; + if(ihdr->height > spng_u32max || !ihdr->height) return SPNG_EHEIGHT; + + if(ihdr->width > max_width) return SPNG_EUSER_WIDTH; + if(ihdr->height > max_height) return SPNG_EUSER_HEIGHT; + + switch(ihdr->color_type) + { + case SPNG_COLOR_TYPE_GRAYSCALE: + { + if( !(ihdr->bit_depth == 1 || ihdr->bit_depth == 2 || + ihdr->bit_depth == 4 || ihdr->bit_depth == 8 || + ihdr->bit_depth == 16) ) + return SPNG_EBIT_DEPTH; + + break; + } + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + { + if( !(ihdr->bit_depth == 8 || ihdr->bit_depth == 16) ) + return SPNG_EBIT_DEPTH; + + break; + } + case SPNG_COLOR_TYPE_INDEXED: + { + if( !(ihdr->bit_depth == 1 || ihdr->bit_depth == 2 || + ihdr->bit_depth == 4 || ihdr->bit_depth == 8) ) + return SPNG_EBIT_DEPTH; + + break; + } + default: return SPNG_ECOLOR_TYPE; + } + + if(ihdr->compression_method) return SPNG_ECOMPRESSION_METHOD; + if(ihdr->filter_method) return SPNG_EFILTER_METHOD; + + if(ihdr->interlace_method > 1) return SPNG_EINTERLACE_METHOD; + + return 0; +} + +static int check_plte(const struct spng_plte *plte, const struct spng_ihdr *ihdr) +{ + if(plte == NULL || ihdr == NULL) return 1; + + if(plte->n_entries == 0) return 1; + if(plte->n_entries > 256) return 1; + + if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED) + { + if(plte->n_entries > (1U << ihdr->bit_depth)) return 1; + } + + return 0; +} + +static int check_sbit(const struct spng_sbit *sbit, const struct spng_ihdr *ihdr) +{ + if(sbit == NULL || ihdr == NULL) return 1; + + if(ihdr->color_type == 0) + { + if(sbit->grayscale_bits == 0) return SPNG_ESBIT; + if(sbit->grayscale_bits > ihdr->bit_depth) return SPNG_ESBIT; + } + else if(ihdr->color_type == 2 || ihdr->color_type == 3) + { + if(sbit->red_bits == 0) return SPNG_ESBIT; + if(sbit->green_bits == 0) return SPNG_ESBIT; + if(sbit->blue_bits == 0) return SPNG_ESBIT; + + uint8_t bit_depth; + if(ihdr->color_type == 3) bit_depth = 8; + else bit_depth = ihdr->bit_depth; + + if(sbit->red_bits > bit_depth) return SPNG_ESBIT; + if(sbit->green_bits > bit_depth) return SPNG_ESBIT; + if(sbit->blue_bits > bit_depth) return SPNG_ESBIT; + } + else if(ihdr->color_type == 4) + { + if(sbit->grayscale_bits == 0) return SPNG_ESBIT; + if(sbit->alpha_bits == 0) return SPNG_ESBIT; + + if(sbit->grayscale_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->alpha_bits > ihdr->bit_depth) return SPNG_ESBIT; + } + else if(ihdr->color_type == 6) + { + if(sbit->red_bits == 0) return SPNG_ESBIT; + if(sbit->green_bits == 0) return SPNG_ESBIT; + if(sbit->blue_bits == 0) return SPNG_ESBIT; + if(sbit->alpha_bits == 0) return SPNG_ESBIT; + + if(sbit->red_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->green_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->blue_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->alpha_bits > ihdr->bit_depth) return SPNG_ESBIT; + } + + return 0; +} + +static int check_chrm_int(const struct spng_chrm_int *chrm_int) +{ + if(chrm_int == NULL) return 1; + + if(chrm_int->white_point_x > spng_u32max || + chrm_int->white_point_y > spng_u32max || + chrm_int->red_x > spng_u32max || + chrm_int->red_y > spng_u32max || + chrm_int->green_x > spng_u32max || + chrm_int->green_y > spng_u32max || + chrm_int->blue_x > spng_u32max || + chrm_int->blue_y > spng_u32max) return SPNG_ECHRM; + + return 0; +} + +static int check_phys(const struct spng_phys *phys) +{ + if(phys == NULL) return 1; + + if(phys->unit_specifier > 1) return SPNG_EPHYS; + + if(phys->ppu_x > spng_u32max) return SPNG_EPHYS; + if(phys->ppu_y > spng_u32max) return SPNG_EPHYS; + + return 0; +} + +static int check_time(const struct spng_time *time) +{ + if(time == NULL) return 1; + + if(time->month == 0 || time->month > 12) return 1; + if(time->day == 0 || time->day > 31) return 1; + if(time->hour > 23) return 1; + if(time->minute > 59) return 1; + if(time->second > 60) return 1; + + return 0; +} + +static int check_offs(const struct spng_offs *offs) +{ + if(offs == NULL) return 1; + + if(offs->unit_specifier > 1) return 1; + + return 0; +} + +static int check_exif(const struct spng_exif *exif) +{ + if(exif == NULL) return 1; + if(exif->data == NULL) return 1; + + if(exif->length < 4) return SPNG_ECHUNK_SIZE; + if(exif->length > spng_u32max) return SPNG_ECHUNK_STDLEN; + + const uint8_t exif_le[4] = { 73, 73, 42, 0 }; + const uint8_t exif_be[4] = { 77, 77, 0, 42 }; + + if(memcmp(exif->data, exif_le, 4) && memcmp(exif->data, exif_be, 4)) return 1; + + return 0; +} + +/* Validate PNG keyword */ +static int check_png_keyword(const char *str) +{ + if(str == NULL) return 1; + size_t len = strlen(str); + const char *end = str + len; + + if(!len) return 1; + if(len > 79) return 1; + if(str[0] == ' ') return 1; /* Leading space */ + if(end[-1] == ' ') return 1; /* Trailing space */ + if(strstr(str, " ") != NULL) return 1; /* Consecutive spaces */ + + uint8_t c; + while(str != end) + { + memcpy(&c, str, 1); + + if( (c >= 32 && c <= 126) || (c >= 161) ) str++; + else return 1; /* Invalid character */ + } + + return 0; +} + +/* Validate PNG text *str up to 'len' bytes */ +static int check_png_text(const char *str, size_t len) +{/* XXX: are consecutive newlines permitted? */ + if(str == NULL || len == 0) return 1; + + uint8_t c; + size_t i = 0; + while(i < len) + { + memcpy(&c, str + i, 1); + + if( (c >= 32 && c <= 126) || (c >= 161) || c == 10) i++; + else return 1; /* Invalid character */ + } + + return 0; +} + +/* Returns non-zero for standard chunks which are stored without allocating memory */ +static int is_small_chunk(uint8_t type[4]) +{ + if(!memcmp(type, type_plte, 4)) return 1; + else if(!memcmp(type, type_chrm, 4)) return 1; + else if(!memcmp(type, type_gama, 4)) return 1; + else if(!memcmp(type, type_sbit, 4)) return 1; + else if(!memcmp(type, type_srgb, 4)) return 1; + else if(!memcmp(type, type_bkgd, 4)) return 1; + else if(!memcmp(type, type_trns, 4)) return 1; + else if(!memcmp(type, type_hist, 4)) return 1; + else if(!memcmp(type, type_phys, 4)) return 1; + else if(!memcmp(type, type_time, 4)) return 1; + else if(!memcmp(type, type_offs, 4)) return 1; + else return 0; +} + +static int read_ihdr(spng_ctx *ctx) +{ + int ret; + struct spng_chunk *chunk = &ctx->current_chunk; + const unsigned char *data; + + chunk->offset = 8; + chunk->length = 13; + size_t sizeof_sig_ihdr = 29; + + ret = read_data(ctx, sizeof_sig_ihdr); + if(ret) return ret; + + data = ctx->data; + + if(memcmp(data, spng_signature, sizeof(spng_signature))) return SPNG_ESIGNATURE; + + chunk->length = read_u32(data + 8); + memcpy(&chunk->type, data + 12, 4); + + if(chunk->length != 13) return SPNG_EIHDR_SIZE; + if(memcmp(chunk->type, type_ihdr, 4)) return SPNG_ENOIHDR; + + ctx->cur_actual_crc = crc32(0, NULL, 0); + ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, data + 12, 17); + + ctx->ihdr.width = read_u32(data + 16); + ctx->ihdr.height = read_u32(data + 20); + ctx->ihdr.bit_depth = data[24]; + ctx->ihdr.color_type = data[25]; + ctx->ihdr.compression_method = data[26]; + ctx->ihdr.filter_method = data[27]; + ctx->ihdr.interlace_method = data[28]; + + ret = check_ihdr(&ctx->ihdr, ctx->max_width, ctx->max_height); + if(ret) return ret; + + ctx->file.ihdr = 1; + ctx->stored.ihdr = 1; + + if(ctx->ihdr.bit_depth < 8) ctx->bytes_per_pixel = 1; + else ctx->bytes_per_pixel = num_channels(&ctx->ihdr) * (ctx->ihdr.bit_depth / 8); + + ret = calculate_subimages(ctx); + if(ret) return ret; + + return 0; +} + +static void splt_undo(spng_ctx *ctx) +{ + struct spng_splt *splt = &ctx->splt_list[ctx->n_splt - 1]; + + spng__free(ctx, splt->entries); + + decrease_cache_usage(ctx, sizeof(struct spng_splt)); + decrease_cache_usage(ctx, splt->n_entries * sizeof(struct spng_splt_entry)); + + splt->entries = NULL; + + ctx->n_splt--; +} + +static void text_undo(spng_ctx *ctx) +{ + struct spng_text2 *text = &ctx->text_list[ctx->n_text - 1]; + + spng__free(ctx, text->keyword); + if(text->compression_flag) spng__free(ctx, text->text); + + decrease_cache_usage(ctx, text->cache_usage); + decrease_cache_usage(ctx, sizeof(struct spng_text2)); + + text->keyword = NULL; + text->text = NULL; + + ctx->n_text--; +} + +static void chunk_undo(spng_ctx *ctx) +{ + struct spng_unknown_chunk *chunk = &ctx->chunk_list[ctx->n_chunks - 1]; + + spng__free(ctx, chunk->data); + + decrease_cache_usage(ctx, chunk->length); + decrease_cache_usage(ctx, sizeof(struct spng_unknown_chunk)); + + chunk->data = NULL; + + ctx->n_chunks--; +} + +static int read_non_idat_chunks(spng_ctx *ctx) +{ + int ret; + struct spng_chunk chunk; + const unsigned char *data; + + ctx->discard = 0; + ctx->undo = NULL; + ctx->prev_stored = ctx->stored; + + while( !(ret = read_header(ctx))) + { + if(ctx->discard) + { + if(ctx->undo) ctx->undo(ctx); + + ctx->stored = ctx->prev_stored; + } + + ctx->discard = 0; + ctx->undo = NULL; + + ctx->prev_stored = ctx->stored; + chunk = ctx->current_chunk; + + if(!memcmp(chunk.type, type_idat, 4)) + { + if(ctx->state < SPNG_STATE_FIRST_IDAT) + { + if(ctx->ihdr.color_type == 3 && !ctx->stored.plte) return SPNG_ENOPLTE; + + ctx->first_idat = chunk; + return 0; + } + + if(ctx->prev_was_idat) + { + /* Ignore extra IDAT's */ + ret = discard_chunk_bytes(ctx, chunk.length); + if(ret) return ret; + + continue; + } + else return SPNG_ECHUNK_POS; /* IDAT chunk not at the end of the IDAT sequence */ + } + + ctx->prev_was_idat = 0; + + if(is_small_chunk(chunk.type)) + { + /* None of the known chunks can be zero length */ + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + /* The largest of these chunks is PLTE with 256 entries */ + ret = read_chunk_bytes(ctx, chunk.length > 768 ? 768 : chunk.length); + if(ret) return ret; + } + + data = ctx->data; + + if(is_critical_chunk(&chunk)) + { + if(!memcmp(chunk.type, type_plte, 4)) + { + if(ctx->file.trns || ctx->file.hist || ctx->file.bkgd) return SPNG_ECHUNK_POS; + if(chunk.length % 3 != 0) return SPNG_ECHUNK_SIZE; + + ctx->plte.n_entries = chunk.length / 3; + + if(check_plte(&ctx->plte, &ctx->ihdr)) return SPNG_ECHUNK_SIZE; /* XXX: EPLTE? */ + + size_t i; + for(i=0; i < ctx->plte.n_entries; i++) + { + ctx->plte.entries[i].red = data[i * 3]; + ctx->plte.entries[i].green = data[i * 3 + 1]; + ctx->plte.entries[i].blue = data[i * 3 + 2]; + } + + ctx->file.plte = 1; + ctx->stored.plte = 1; + } + else if(!memcmp(chunk.type, type_iend, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) + { + if(chunk.length) return SPNG_ECHUNK_SIZE; + + ret = read_and_check_crc(ctx); + if(ret == -SPNG_CRC_DISCARD) ret = 0; + + return ret; + } + else return SPNG_ECHUNK_POS; + } + else if(!memcmp(chunk.type, type_ihdr, 4)) return SPNG_ECHUNK_POS; + else return SPNG_ECHUNK_UNKNOWN_CRITICAL; + } + else if(!memcmp(chunk.type, type_chrm, 4)) /* Ancillary chunks */ + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.chrm) return SPNG_EDUP_CHRM; + + if(chunk.length != 32) return SPNG_ECHUNK_SIZE; + + ctx->chrm_int.white_point_x = read_u32(data); + ctx->chrm_int.white_point_y = read_u32(data + 4); + ctx->chrm_int.red_x = read_u32(data + 8); + ctx->chrm_int.red_y = read_u32(data + 12); + ctx->chrm_int.green_x = read_u32(data + 16); + ctx->chrm_int.green_y = read_u32(data + 20); + ctx->chrm_int.blue_x = read_u32(data + 24); + ctx->chrm_int.blue_y = read_u32(data + 28); + + if(check_chrm_int(&ctx->chrm_int)) return SPNG_ECHRM; + + ctx->file.chrm = 1; + ctx->stored.chrm = 1; + } + else if(!memcmp(chunk.type, type_gama, 4)) + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.gama) return SPNG_EDUP_GAMA; + + if(chunk.length != 4) return SPNG_ECHUNK_SIZE; + + ctx->gama = read_u32(data); + + if(!ctx->gama) return SPNG_EGAMA; + if(ctx->gama > spng_u32max) return SPNG_EGAMA; + + ctx->file.gama = 1; + ctx->stored.gama = 1; + } + else if(!memcmp(chunk.type, type_sbit, 4)) + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.sbit) return SPNG_EDUP_SBIT; + + if(ctx->ihdr.color_type == 0) + { + if(chunk.length != 1) return SPNG_ECHUNK_SIZE; + + ctx->sbit.grayscale_bits = data[0]; + } + else if(ctx->ihdr.color_type == 2 || ctx->ihdr.color_type == 3) + { + if(chunk.length != 3) return SPNG_ECHUNK_SIZE; + + ctx->sbit.red_bits = data[0]; + ctx->sbit.green_bits = data[1]; + ctx->sbit.blue_bits = data[2]; + } + else if(ctx->ihdr.color_type == 4) + { + if(chunk.length != 2) return SPNG_ECHUNK_SIZE; + + ctx->sbit.grayscale_bits = data[0]; + ctx->sbit.alpha_bits = data[1]; + } + else if(ctx->ihdr.color_type == 6) + { + if(chunk.length != 4) return SPNG_ECHUNK_SIZE; + + ctx->sbit.red_bits = data[0]; + ctx->sbit.green_bits = data[1]; + ctx->sbit.blue_bits = data[2]; + ctx->sbit.alpha_bits = data[3]; + } + + if(check_sbit(&ctx->sbit, &ctx->ihdr)) return SPNG_ESBIT; + + ctx->file.sbit = 1; + ctx->stored.sbit = 1; + } + else if(!memcmp(chunk.type, type_srgb, 4)) + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.srgb) return SPNG_EDUP_SRGB; + + if(chunk.length != 1) return SPNG_ECHUNK_SIZE; + + ctx->srgb_rendering_intent = data[0]; + + if(ctx->srgb_rendering_intent > 3) return SPNG_ESRGB; + + ctx->file.srgb = 1; + ctx->stored.srgb = 1; + } + else if(!memcmp(chunk.type, type_bkgd, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.bkgd) return SPNG_EDUP_BKGD; + + if(ctx->ihdr.color_type == 0 || ctx->ihdr.color_type == 4) + { + if(chunk.length != 2) return SPNG_ECHUNK_SIZE; + + ctx->bkgd.gray = read_u16(data); + } + else if(ctx->ihdr.color_type == 2 || ctx->ihdr.color_type == 6) + { + if(chunk.length != 6) return SPNG_ECHUNK_SIZE; + + ctx->bkgd.red = read_u16(data); + ctx->bkgd.green = read_u16(data + 2); + ctx->bkgd.blue = read_u16(data + 4); + } + else if(ctx->ihdr.color_type == 3) + { + if(chunk.length != 1) return SPNG_ECHUNK_SIZE; + if(!ctx->file.plte) return SPNG_EBKGD_NO_PLTE; + + ctx->bkgd.plte_index = data[0]; + if(ctx->bkgd.plte_index >= ctx->plte.n_entries) return SPNG_EBKGD_PLTE_IDX; + } + + ctx->file.bkgd = 1; + ctx->stored.bkgd = 1; + } + else if(!memcmp(chunk.type, type_trns, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.trns) return SPNG_EDUP_TRNS; + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + if(ctx->ihdr.color_type == 0) + { + if(chunk.length != 2) return SPNG_ECHUNK_SIZE; + + ctx->trns.gray = read_u16(data); + } + else if(ctx->ihdr.color_type == 2) + { + if(chunk.length != 6) return SPNG_ECHUNK_SIZE; + + ctx->trns.red = read_u16(data); + ctx->trns.green = read_u16(data + 2); + ctx->trns.blue = read_u16(data + 4); + } + else if(ctx->ihdr.color_type == 3) + { + if(chunk.length > ctx->plte.n_entries) return SPNG_ECHUNK_SIZE; + if(!ctx->file.plte) return SPNG_ETRNS_NO_PLTE; + + memcpy(ctx->trns.type3_alpha, data, chunk.length); + ctx->trns.n_type3_entries = chunk.length; + } + + if(ctx->ihdr.color_type == 4 || ctx->ihdr.color_type == 6) return SPNG_ETRNS_COLOR_TYPE; + + ctx->file.trns = 1; + ctx->stored.trns = 1; + } + else if(!memcmp(chunk.type, type_hist, 4)) + { + if(!ctx->file.plte) return SPNG_EHIST_NO_PLTE; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.hist) return SPNG_EDUP_HIST; + + if( (chunk.length / 2) != (ctx->plte.n_entries) ) return SPNG_ECHUNK_SIZE; + + size_t k; + for(k=0; k < (chunk.length / 2); k++) + { + ctx->hist.frequency[k] = read_u16(data + k*2); + } + + ctx->file.hist = 1; + ctx->stored.hist = 1; + } + else if(!memcmp(chunk.type, type_phys, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.phys) return SPNG_EDUP_PHYS; + + if(chunk.length != 9) return SPNG_ECHUNK_SIZE; + + ctx->phys.ppu_x = read_u32(data); + ctx->phys.ppu_y = read_u32(data + 4); + ctx->phys.unit_specifier = data[8]; + + if(check_phys(&ctx->phys)) return SPNG_EPHYS; + + ctx->file.phys = 1; + ctx->stored.phys = 1; + } + else if(!memcmp(chunk.type, type_time, 4)) + { + if(ctx->file.time) return SPNG_EDUP_TIME; + + if(chunk.length != 7) return SPNG_ECHUNK_SIZE; + + struct spng_time time; + + time.year = read_u16(data); + time.month = data[2]; + time.day = data[3]; + time.hour = data[4]; + time.minute = data[5]; + time.second = data[6]; + + if(check_time(&time)) return SPNG_ETIME; + + ctx->file.time = 1; + + if(!ctx->user.time) ctx->time = time; + + ctx->stored.time = 1; + } + else if(!memcmp(chunk.type, type_offs, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.offs) return SPNG_EDUP_OFFS; + + if(chunk.length != 9) return SPNG_ECHUNK_SIZE; + + ctx->offs.x = read_s32(data); + ctx->offs.y = read_s32(data + 4); + ctx->offs.unit_specifier = data[8]; + + if(check_offs(&ctx->offs)) return SPNG_EOFFS; + + ctx->file.offs = 1; + ctx->stored.offs = 1; + } + else /* Arbitrary-length chunk */ + { + + if(!memcmp(chunk.type, type_exif, 4)) + { + if(ctx->file.exif) return SPNG_EDUP_EXIF; + + ctx->file.exif = 1; + + if(ctx->user.exif) goto discard; + + if(increase_cache_usage(ctx, chunk.length, 1)) return SPNG_ECHUNK_LIMITS; + + struct spng_exif exif; + + exif.length = chunk.length; + + exif.data = spng__malloc(ctx, chunk.length); + if(exif.data == NULL) return SPNG_EMEM; + + ret = read_chunk_bytes2(ctx, exif.data, chunk.length); + if(ret) + { + spng__free(ctx, exif.data); + return ret; + } + + if(check_exif(&exif)) + { + spng__free(ctx, exif.data); + return SPNG_EEXIF; + } + + ctx->exif = exif; + + ctx->stored.exif = 1; + } + else if(!memcmp(chunk.type, type_iccp, 4)) + {/* TODO: add test file with color profile */ + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.iccp) return SPNG_EDUP_ICCP; + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + ctx->file.iccp = 1; + + uint32_t peek_bytes = 81 > chunk.length ? chunk.length : 81; + + ret = read_chunk_bytes(ctx, peek_bytes); + if(ret) return ret; + + unsigned char *keyword_nul = memchr(ctx->data, '\0', peek_bytes); + if(keyword_nul == NULL) return SPNG_EICCP_NAME; + + uint32_t keyword_len = keyword_nul - ctx->data; + + if(keyword_len > 79) return SPNG_EICCP_NAME; + + memcpy(ctx->iccp.profile_name, ctx->data, keyword_len); + + if(check_png_keyword(ctx->iccp.profile_name)) return SPNG_EICCP_NAME; + + if(chunk.length < (keyword_len + 2)) return SPNG_ECHUNK_SIZE; + + if(ctx->data[keyword_len + 1] != 0) return SPNG_EICCP_COMPRESSION_METHOD; + + ret = spng__inflate_stream(ctx, &ctx->iccp.profile, &ctx->iccp.profile_len, 0, ctx->data + keyword_len + 2, peek_bytes - (keyword_len + 2)); + + if(ret) return ret; + + ctx->stored.iccp = 1; + } + else if(!memcmp(chunk.type, type_text, 4) || + !memcmp(chunk.type, type_ztxt, 4) || + !memcmp(chunk.type, type_itxt, 4)) + { + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + ctx->file.text = 1; + + if(ctx->user.text) goto discard; + + if(increase_cache_usage(ctx, sizeof(struct spng_text2), 1)) return SPNG_ECHUNK_LIMITS; + + ctx->n_text++; + if(ctx->n_text < 1) return SPNG_EOVERFLOW; + if(sizeof(struct spng_text2) > SIZE_MAX / ctx->n_text) return SPNG_EOVERFLOW; + + void *buf = spng__realloc(ctx, ctx->text_list, ctx->n_text * sizeof(struct spng_text2)); + if(buf == NULL) return SPNG_EMEM; + ctx->text_list = buf; + + struct spng_text2 *text = &ctx->text_list[ctx->n_text - 1]; + memset(text, 0, sizeof(struct spng_text2)); + + ctx->undo = text_undo; + + uint32_t text_offset = 0, language_tag_offset = 0, translated_keyword_offset = 0; + uint32_t peek_bytes = 256; /* enough for 3 80-byte keywords and some text bytes */ + uint32_t keyword_len; + + if(peek_bytes > chunk.length) peek_bytes = chunk.length; + + ret = read_chunk_bytes(ctx, peek_bytes); + if(ret) return ret; + + data = ctx->data; + + const unsigned char *zlib_stream = NULL; + const unsigned char *peek_end = data + peek_bytes; + const unsigned char *keyword_nul = memchr(data, 0, chunk.length > 80 ? 80 : chunk.length); + + if(keyword_nul == NULL) return SPNG_ETEXT_KEYWORD; + + keyword_len = keyword_nul - data; + + if(!memcmp(chunk.type, type_text, 4)) + { + text->type = SPNG_TEXT; + + text->text_length = chunk.length - keyword_len - 1; + + text_offset = keyword_len; + + /* increment past nul if there is a text field */ + if(text->text_length) text_offset++; + } + else if(!memcmp(chunk.type, type_ztxt, 4)) + { + text->type = SPNG_ZTXT; + + if((peek_bytes - keyword_len) <= 2) return SPNG_EZTXT; + + if(keyword_nul[1]) return SPNG_EZTXT_COMPRESSION_METHOD; + + text->compression_flag = 1; + + text_offset = keyword_len + 2; + } + else if(!memcmp(chunk.type, type_itxt, 4)) + { + text->type = SPNG_ITXT; + + /* at least two 1-byte fields, two >=0 length strings, and one byte of (compressed) text */ + if((peek_bytes - keyword_len) < 5) return SPNG_EITXT; + + text->compression_flag = keyword_nul[1]; + + if(text->compression_flag > 1) return SPNG_EITXT_COMPRESSION_FLAG; + + if(keyword_nul[2]) return SPNG_EITXT_COMPRESSION_METHOD; + + language_tag_offset = keyword_len + 3; + + const unsigned char *term; + term = memchr(data + language_tag_offset, 0, peek_bytes - language_tag_offset); + if(term == NULL) return SPNG_EITXT_LANG_TAG; + + if((peek_end - term) < 2) return SPNG_EITXT; + + translated_keyword_offset = term - data + 1; + + zlib_stream = memchr(data + translated_keyword_offset, 0, peek_bytes - translated_keyword_offset); + if(zlib_stream == NULL) return SPNG_EITXT; + if(zlib_stream == peek_end) return SPNG_EITXT; + + text_offset = zlib_stream - data + 1; + text->text_length = chunk.length - text_offset; + } + else return SPNG_EINTERNAL; + + + if(text->compression_flag) + { + /* cache usage = peek_bytes + decompressed text size + nul */ + if(increase_cache_usage(ctx, peek_bytes, 0)) return SPNG_ECHUNK_LIMITS; + + text->keyword = spng__calloc(ctx, 1, peek_bytes); + if(text->keyword == NULL) return SPNG_EMEM; + + memcpy(text->keyword, data, peek_bytes); + + zlib_stream = ctx->data + text_offset; + + ret = spng__inflate_stream(ctx, &text->text, &text->text_length, 1, zlib_stream, peek_bytes - text_offset); + + if(ret) return ret; + + text->text[text->text_length - 1] = '\0'; + text->cache_usage = text->text_length + peek_bytes; + } + else + { + if(increase_cache_usage(ctx, chunk.length + 1, 0)) return SPNG_ECHUNK_LIMITS; + + text->keyword = spng__malloc(ctx, chunk.length + 1); + if(text->keyword == NULL) return SPNG_EMEM; + + memcpy(text->keyword, data, peek_bytes); + + if(chunk.length > peek_bytes) + { + ret = read_chunk_bytes2(ctx, text->keyword + peek_bytes, chunk.length - peek_bytes); + if(ret) return ret; + } + + text->text = text->keyword + text_offset; + + text->text_length = chunk.length - text_offset; + + text->text[text->text_length] = '\0'; + text->cache_usage = chunk.length + 1; + } + + if(check_png_keyword(text->keyword)) return SPNG_ETEXT_KEYWORD; + + text->text_length = strlen(text->text); + + if(text->type != SPNG_ITXT) + { + language_tag_offset = keyword_len; + translated_keyword_offset = keyword_len; + + if(ctx->strict && check_png_text(text->text, text->text_length)) + { + if(text->type == SPNG_ZTXT) return SPNG_EZTXT; + else return SPNG_ETEXT; + } + } + + text->language_tag = text->keyword + language_tag_offset; + text->translated_keyword = text->keyword + translated_keyword_offset; + + ctx->stored.text = 1; + } + else if(!memcmp(chunk.type, type_splt, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->user.splt) goto discard; /* XXX: could check profile names for uniqueness */ + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + ctx->file.splt = 1; + + /* chunk.length + sizeof(struct spng_splt) + splt->n_entries * sizeof(struct spng_splt_entry) */ + if(increase_cache_usage(ctx, chunk.length + sizeof(struct spng_splt), 1)) return SPNG_ECHUNK_LIMITS; + + ctx->n_splt++; + if(ctx->n_splt < 1) return SPNG_EOVERFLOW; + if(sizeof(struct spng_splt) > SIZE_MAX / ctx->n_splt) return SPNG_EOVERFLOW; + + void *buf = spng__realloc(ctx, ctx->splt_list, ctx->n_splt * sizeof(struct spng_splt)); + if(buf == NULL) return SPNG_EMEM; + ctx->splt_list = buf; + + struct spng_splt *splt = &ctx->splt_list[ctx->n_splt - 1]; + + memset(splt, 0, sizeof(struct spng_splt)); + + ctx->undo = splt_undo; + + void *t = spng__malloc(ctx, chunk.length); + if(t == NULL) return SPNG_EMEM; + + splt->entries = t; /* simplifies error handling */ + data = t; + + ret = read_chunk_bytes2(ctx, t, chunk.length); + if(ret) return ret; + + uint32_t keyword_len = chunk.length < 80 ? chunk.length : 80; + + const unsigned char *keyword_nul = memchr(data, 0, keyword_len); + if(keyword_nul == NULL) return SPNG_ESPLT_NAME; + + keyword_len = keyword_nul - data; + + memcpy(splt->name, data, keyword_len); + + if(check_png_keyword(splt->name)) return SPNG_ESPLT_NAME; + + uint32_t j; + for(j=0; j < (ctx->n_splt - 1); j++) + { + if(!strcmp(ctx->splt_list[j].name, splt->name)) return SPNG_ESPLT_DUP_NAME; + } + + if( (chunk.length - keyword_len) <= 2) return SPNG_ECHUNK_SIZE; + + splt->sample_depth = data[keyword_len + 1]; + + uint32_t entries_len = chunk.length - keyword_len - 2; + if(!entries_len) return SPNG_ECHUNK_SIZE; + + if(splt->sample_depth == 16) + { + if(entries_len % 10 != 0) return SPNG_ECHUNK_SIZE; + splt->n_entries = entries_len / 10; + } + else if(splt->sample_depth == 8) + { + if(entries_len % 6 != 0) return SPNG_ECHUNK_SIZE; + splt->n_entries = entries_len / 6; + } + else return SPNG_ESPLT_DEPTH; + + if(!splt->n_entries) return SPNG_ECHUNK_SIZE; + + size_t list_size = splt->n_entries; + + if(list_size > SIZE_MAX / sizeof(struct spng_splt_entry)) return SPNG_EOVERFLOW; + + list_size *= sizeof(struct spng_splt_entry); + + if(increase_cache_usage(ctx, list_size, 0)) return SPNG_ECHUNK_LIMITS; + + splt->entries = spng__malloc(ctx, list_size); + if(splt->entries == NULL) + { + spng__free(ctx, t); + return SPNG_EMEM; + } + + data = (unsigned char*)t + keyword_len + 2; + + uint32_t k; + if(splt->sample_depth == 16) + { + for(k=0; k < splt->n_entries; k++) + { + splt->entries[k].red = read_u16(data + k * 10); + splt->entries[k].green = read_u16(data + k * 10 + 2); + splt->entries[k].blue = read_u16(data + k * 10 + 4); + splt->entries[k].alpha = read_u16(data + k * 10 + 6); + splt->entries[k].frequency = read_u16(data + k * 10 + 8); + } + } + else if(splt->sample_depth == 8) + { + for(k=0; k < splt->n_entries; k++) + { + splt->entries[k].red = data[k * 6]; + splt->entries[k].green = data[k * 6 + 1]; + splt->entries[k].blue = data[k * 6 + 2]; + splt->entries[k].alpha = data[k * 6 + 3]; + splt->entries[k].frequency = read_u16(data + k * 6 + 4); + } + } + + spng__free(ctx, t); + decrease_cache_usage(ctx, chunk.length); + + ctx->stored.splt = 1; + } + else /* Unknown chunk */ + { + ctx->file.unknown = 1; + + if(!ctx->keep_unknown) goto discard; + if(ctx->user.unknown) goto discard; + + if(increase_cache_usage(ctx, chunk.length + sizeof(struct spng_unknown_chunk), 1)) return SPNG_ECHUNK_LIMITS; + + ctx->n_chunks++; + if(ctx->n_chunks < 1) return SPNG_EOVERFLOW; + if(sizeof(struct spng_unknown_chunk) > SIZE_MAX / ctx->n_chunks) return SPNG_EOVERFLOW; + + void *buf = spng__realloc(ctx, ctx->chunk_list, ctx->n_chunks * sizeof(struct spng_unknown_chunk)); + if(buf == NULL) return SPNG_EMEM; + ctx->chunk_list = buf; + + struct spng_unknown_chunk *chunkp = &ctx->chunk_list[ctx->n_chunks - 1]; + + memset(chunkp, 0, sizeof(struct spng_unknown_chunk)); + + ctx->undo = chunk_undo; + + memcpy(chunkp->type, chunk.type, 4); + + if(ctx->state < SPNG_STATE_FIRST_IDAT) + { + if(ctx->file.plte) chunkp->location = SPNG_AFTER_PLTE; + else chunkp->location = SPNG_AFTER_IHDR; + } + else if(ctx->state >= SPNG_STATE_AFTER_IDAT) chunkp->location = SPNG_AFTER_IDAT; + + if(chunk.length > 0) + { + void *t = spng__malloc(ctx, chunk.length); + if(t == NULL) return SPNG_EMEM; + + ret = read_chunk_bytes2(ctx, t, chunk.length); + if(ret) + { + spng__free(ctx, t); + return ret; + } + + chunkp->length = chunk.length; + chunkp->data = t; + } + + ctx->stored.unknown = 1; + } + +discard: + ret = discard_chunk_bytes(ctx, ctx->cur_chunk_bytes_left); + if(ret) return ret; + } + + } + + return ret; +} + +/* Read chunks before or after the IDAT chunks depending on state */ +static int read_chunks(spng_ctx *ctx, int only_ihdr) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!ctx->state) return SPNG_EBADSTATE; + if(ctx->data == NULL) + { + if(ctx->encode_only) return 0; + else return SPNG_EINTERNAL; + } + + int ret = 0; + + if(ctx->state == SPNG_STATE_INPUT) + { + ret = read_ihdr(ctx); + + if(ret) return decode_err(ctx, ret); + + ctx->state = SPNG_STATE_IHDR; + } + + if(only_ihdr) return 0; + + if(ctx->state == SPNG_STATE_EOI) + { + ctx->state = SPNG_STATE_AFTER_IDAT; + ctx->prev_was_idat = 1; + } + + while(ctx->state < SPNG_STATE_FIRST_IDAT || ctx->state == SPNG_STATE_AFTER_IDAT) + { + ret = read_non_idat_chunks(ctx); + + if(!ret) + { + if(ctx->state < SPNG_STATE_FIRST_IDAT) ctx->state = SPNG_STATE_FIRST_IDAT; + else if(ctx->state == SPNG_STATE_AFTER_IDAT) ctx->state = SPNG_STATE_IEND; + } + else + { + switch(ret) + { + case SPNG_ECHUNK_POS: + case SPNG_ECHUNK_SIZE: /* size != expected size, SPNG_ECHUNK_STDLEN = invalid size */ + case SPNG_EDUP_PLTE: + case SPNG_EDUP_CHRM: + case SPNG_EDUP_GAMA: + case SPNG_EDUP_ICCP: + case SPNG_EDUP_SBIT: + case SPNG_EDUP_SRGB: + case SPNG_EDUP_BKGD: + case SPNG_EDUP_HIST: + case SPNG_EDUP_TRNS: + case SPNG_EDUP_PHYS: + case SPNG_EDUP_TIME: + case SPNG_EDUP_OFFS: + case SPNG_EDUP_EXIF: + case SPNG_ECHRM: + case SPNG_ETRNS_COLOR_TYPE: + case SPNG_ETRNS_NO_PLTE: + case SPNG_EGAMA: + case SPNG_EICCP_NAME: + case SPNG_EICCP_COMPRESSION_METHOD: + case SPNG_ESBIT: + case SPNG_ESRGB: + case SPNG_ETEXT: + case SPNG_ETEXT_KEYWORD: + case SPNG_EZTXT: + case SPNG_EZTXT_COMPRESSION_METHOD: + case SPNG_EITXT: + case SPNG_EITXT_COMPRESSION_FLAG: + case SPNG_EITXT_COMPRESSION_METHOD: + case SPNG_EITXT_LANG_TAG: + case SPNG_EITXT_TRANSLATED_KEY: + case SPNG_EBKGD_NO_PLTE: + case SPNG_EBKGD_PLTE_IDX: + case SPNG_EHIST_NO_PLTE: + case SPNG_EPHYS: + case SPNG_ESPLT_NAME: + case SPNG_ESPLT_DUP_NAME: + case SPNG_ESPLT_DEPTH: + case SPNG_ETIME: + case SPNG_EOFFS: + case SPNG_EEXIF: + case SPNG_EZLIB: + { + if(!ctx->strict && !is_critical_chunk(&ctx->current_chunk)) + { + ret = discard_chunk_bytes(ctx, ctx->cur_chunk_bytes_left); + if(ret) return decode_err(ctx, ret); + + if(ctx->undo) ctx->undo(ctx); + + ctx->stored = ctx->prev_stored; + + ctx->discard = 0; + ctx->undo = NULL; + + continue; + } + else return decode_err(ctx, ret); + + break; + } + default: return decode_err(ctx, ret); + } + } + } + + return ret; +} + +static int read_scanline(spng_ctx *ctx) +{ + int ret, pass = ctx->row_info.pass; + struct spng_row_info *ri = &ctx->row_info; + const struct spng_subimage *sub = ctx->subimage; + size_t scanline_width = sub[pass].scanline_width; + uint32_t scanline_idx = ri->scanline_idx; + + uint8_t next_filter = 0; + + if(scanline_idx == (sub[pass].height - 1) && ri->pass == ctx->last_pass) + { + ret = read_scanline_bytes(ctx, ctx->scanline, scanline_width - 1); + } + else + { + ret = read_scanline_bytes(ctx, ctx->scanline, scanline_width); + if(ret) return ret; + + next_filter = ctx->scanline[scanline_width - 1]; + if(next_filter > 4) ret = SPNG_EFILTER; + } + + if(ret) return ret; + + if(!scanline_idx && ri->filter > 1) + { + /* prev_scanline is all zeros for the first scanline */ + memset(ctx->prev_scanline, 0, scanline_width); + } + + if(ctx->ihdr.bit_depth == 16 && ctx->fmt != SPNG_FMT_RAW) u16_row_to_host(ctx->scanline, scanline_width - 1); + + ret = defilter_scanline(ctx->prev_scanline, ctx->scanline, scanline_width, ctx->bytes_per_pixel, ri->filter); + if(ret) return ret; + + ri->filter = next_filter; + + return 0; +} + +static int update_row_info(spng_ctx *ctx) +{ + int interlacing = ctx->ihdr.interlace_method; + struct spng_row_info *ri = &ctx->row_info; + const struct spng_subimage *sub = ctx->subimage; + + if(ri->scanline_idx == (sub[ri->pass].height - 1)) /* Last scanline */ + { + if(ri->pass == ctx->last_pass) + { + ctx->state = SPNG_STATE_EOI; + + return SPNG_EOI; + } + + ri->scanline_idx = 0; + ri->pass++; + + /* Skip empty passes */ + while( (!sub[ri->pass].width || !sub[ri->pass].height) && (ri->pass < ctx->last_pass)) ri->pass++; + } + else + { + ri->row_num++; + ri->scanline_idx++; + } + + if(interlacing) ri->row_num = adam7_y_start[ri->pass] + ri->scanline_idx * adam7_y_delta[ri->pass]; + + return 0; +} + +int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len) +{ + if(ctx == NULL || out == NULL) return 1; + + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + + struct decode_flags f = ctx->decode_flags; + + struct spng_row_info *ri = &ctx->row_info; + const struct spng_subimage *sub = ctx->subimage; + + const struct spng_ihdr *ihdr = &ctx->ihdr; + const uint16_t *gamma_lut = ctx->gamma_lut; + unsigned char *trns_px = ctx->trns_px; + const struct spng_sbit *sb = &ctx->decode_sb; + const struct spng_plte_entry *plte = ctx->decode_plte.rgba; + struct spng__iter iter = spng__iter_init(ihdr->bit_depth, ctx->scanline); + + const unsigned char *scanline; + + const int pass = ri->pass; + const int fmt = ctx->fmt; + const size_t scanline_width = sub[pass].scanline_width; + const uint32_t width = sub[pass].width; + uint32_t k; + uint8_t r_8, g_8, b_8, a_8, gray_8; + uint16_t r_16, g_16, b_16, a_16, gray_16; + r_8=0; g_8=0; b_8=0; a_8=0; gray_8=0; + r_16=0; g_16=0; b_16=0; a_16=0; gray_16=0; + size_t pixel_size = 4; /* SPNG_FMT_RGBA8 */ + size_t pixel_offset = 0; + unsigned char *pixel; + unsigned processing_depth = ihdr->bit_depth; + + if(f.indexed) processing_depth = 8; + + if(fmt == SPNG_FMT_RGBA16) pixel_size = 8; + else if(fmt == SPNG_FMT_RGB8) pixel_size = 3; + + if(len < sub[pass].out_width) return SPNG_EBUFSIZ; + + int ret = read_scanline(ctx); + + if(ret) return decode_err(ctx, ret); + + scanline = ctx->scanline; + + for(k=0; k < width; k++) + { + pixel = (unsigned char*)out + pixel_offset; + pixel_offset += pixel_size; + + if(f.same_layout) + { + if(f.zerocopy) break; + + memcpy(out, scanline, scanline_width - 1); + break; + } + + if(f.unpack) + { + unpack_scanline(out, scanline, width, ihdr->bit_depth, fmt); + break; + } + + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR) + { + if(ihdr->bit_depth == 16) + { + memcpy(&r_16, scanline + (k * 6), 2); + memcpy(&g_16, scanline + (k * 6) + 2, 2); + memcpy(&b_16, scanline + (k * 6) + 4, 2); + + a_16 = 65535; + } + else /* == 8 */ + { + if(fmt == SPNG_FMT_RGBA8) + { + rgb8_row_to_rgba8(scanline, out, width); + break; + } + + r_8 = scanline[k * 3]; + g_8 = scanline[k * 3 + 1]; + b_8 = scanline[k * 3 + 2]; + + a_8 = 255; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED) + { + uint8_t entry = 0; + + if(ihdr->bit_depth == 8) + { + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + expand_row(out, scanline, &ctx->decode_plte, width, fmt); + break; + } + + entry = scanline[k]; + } + else /* < 8 */ + { + entry = get_sample(&iter); + } + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + pixel[0] = plte[entry].red; + pixel[1] = plte[entry].green; + pixel[2] = plte[entry].blue; + if(fmt == SPNG_FMT_RGBA8) pixel[3] = plte[entry].alpha; + + continue; + } + else /* RGBA16 */ + { + r_16 = plte[entry].red; + g_16 = plte[entry].green; + b_16 = plte[entry].blue; + a_16 = plte[entry].alpha; + + r_16 = (r_16 << 8) | r_16; + g_16 = (g_16 << 8) | g_16; + b_16 = (b_16 << 8) | b_16; + a_16 = (a_16 << 8) | a_16; + + memcpy(pixel, &r_16, 2); + memcpy(pixel + 2, &g_16, 2); + memcpy(pixel + 4, &b_16, 2); + memcpy(pixel + 6, &a_16, 2); + + continue; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) + { + if(ihdr->bit_depth == 16) + { + memcpy(&r_16, scanline + (k * 8), 2); + memcpy(&g_16, scanline + (k * 8) + 2, 2); + memcpy(&b_16, scanline + (k * 8) + 4, 2); + memcpy(&a_16, scanline + (k * 8) + 6, 2); + } + else /* == 8 */ + { + r_8 = scanline[k * 4]; + g_8 = scanline[k * 4 + 1]; + b_8 = scanline[k * 4 + 2]; + a_8 = scanline[k * 4 + 3]; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) + { + if(ihdr->bit_depth == 16) + { + memcpy(&gray_16, scanline + k * 2, 2); + + if(f.apply_trns && ctx->trns.gray == gray_16) a_16 = 0; + else a_16 = 65535; + + r_16 = gray_16; + g_16 = gray_16; + b_16 = gray_16; + } + else /* <= 8 */ + { + gray_8 = get_sample(&iter); + + if(f.apply_trns && ctx->trns.gray == gray_8) a_8 = 0; + else a_8 = 255; + + r_8 = gray_8; g_8 = gray_8; b_8 = gray_8; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) + { + if(ihdr->bit_depth == 16) + { + memcpy(&gray_16, scanline + (k * 4), 2); + memcpy(&a_16, scanline + (k * 4) + 2, 2); + + r_16 = gray_16; + g_16 = gray_16; + b_16 = gray_16; + } + else /* == 8 */ + { + gray_8 = scanline[k * 2]; + a_8 = scanline[k * 2 + 1]; + + r_8 = gray_8; + g_8 = gray_8; + b_8 = gray_8; + } + } + + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + if(ihdr->bit_depth == 16) + { + r_8 = r_16 >> 8; + g_8 = g_16 >> 8; + b_8 = b_16 >> 8; + a_8 = a_16 >> 8; + } + + pixel[0] = r_8; + pixel[1] = g_8; + pixel[2] = b_8; + + if(fmt == SPNG_FMT_RGBA8) pixel[3] = a_8; + } + else if(fmt == SPNG_FMT_RGBA16) + { + if(ihdr->bit_depth != 16) + { + r_16 = r_8; + g_16 = g_8; + b_16 = b_8; + a_16 = a_8; + } + + memcpy(pixel, &r_16, 2); + memcpy(pixel + 2, &g_16, 2); + memcpy(pixel + 4, &b_16, 2); + memcpy(pixel + 6, &a_16, 2); + } + }/* for(k=0; k < width; k++) */ + + if(f.apply_trns) trns_row(out, scanline, trns_px, ctx->bytes_per_pixel, &ctx->ihdr, width, fmt); + + if(f.do_scaling) scale_row(out, width, fmt, processing_depth, sb); + + if(f.apply_gamma) gamma_correct_row(out, width, fmt, gamma_lut); + + /* The previous scanline is always defiltered */ + void *t = ctx->prev_scanline; + ctx->prev_scanline = ctx->scanline; + ctx->scanline = t; + + ret = update_row_info(ctx); + + if(ret == SPNG_EOI) + { + if(ctx->cur_chunk_bytes_left) /* zlib stream ended before an IDAT chunk boundary */ + {/* Discard the rest of the chunk */ + int error = discard_chunk_bytes(ctx, ctx->cur_chunk_bytes_left); + if(error) return decode_err(ctx, error); + } + + ctx->last_idat = ctx->current_chunk; + } + + return ret; +} + +int spng_decode_row(spng_ctx *ctx, void *out, size_t len) +{ + if(ctx == NULL || out == NULL) return 1; + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + if(len < ctx->image_width) return SPNG_EBUFSIZ; + + const struct spng_ihdr *ihdr = &ctx->ihdr; + int ret, pass = ctx->row_info.pass; + unsigned char *outptr = out; + + if(!ihdr->interlace_method || pass == 6) return spng_decode_scanline(ctx, out, len); + + ret = spng_decode_scanline(ctx, ctx->row, ctx->image_width); + if(ret && ret != SPNG_EOI) return ret; + + uint32_t k; + unsigned pixel_size = 4; /* RGBA8 */ + if(ctx->fmt == SPNG_FMT_RGBA16) pixel_size = 8; + else if(ctx->fmt == SPNG_FMT_RGB8) pixel_size = 3; + else if(ctx->fmt == SPNG_FMT_G8) pixel_size = 1; + else if(ctx->fmt == SPNG_FMT_GA8) pixel_size = 2; + else if(ctx->fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW)) + { + if(ihdr->bit_depth < 8) + { + struct spng__iter iter = spng__iter_init(ihdr->bit_depth, ctx->row); + const uint8_t samples_per_byte = 8 / ihdr->bit_depth; + uint8_t sample; + + for(k=0; k < ctx->subimage[pass].width; k++) + { + sample = get_sample(&iter); + + size_t ioffset = adam7_x_start[pass] + k * adam7_x_delta[pass]; + + sample = sample << (iter.initial_shift - ioffset * ihdr->bit_depth % 8); + + ioffset /= samples_per_byte; + + outptr[ioffset] |= sample; + } + + return 0; + } + else pixel_size = ctx->bytes_per_pixel; + } + + for(k=0; k < ctx->subimage[pass].width; k++) + { + size_t ioffset = (adam7_x_start[pass] + (size_t) k * adam7_x_delta[pass]) * pixel_size; + + memcpy(outptr + ioffset, ctx->row + k * pixel_size, pixel_size); + } + + return 0; +} + +int spng_decode_chunks(spng_ctx *ctx) +{ + if(ctx == NULL) return 1; + if(ctx->encode_only) return SPNG_ECTXTYPE; + if(ctx->state < SPNG_STATE_INPUT) return SPNG_ENOSRC; + if(ctx->state == SPNG_STATE_IEND) return 0; + + return read_chunks(ctx, 0); +} + +int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags) +{ + if(ctx == NULL) return 1; + if(ctx->encode_only) return SPNG_ECTXTYPE; + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + + const struct spng_ihdr *ihdr = &ctx->ihdr; + + int ret = read_chunks(ctx, 0); + if(ret) return decode_err(ctx, ret); + + ret = check_decode_fmt(ihdr, fmt); + if(ret) return ret; + + ret = calculate_image_width(ihdr, fmt, &ctx->image_width); + if(ret) return decode_err(ctx, ret); + + if(ctx->image_width > SIZE_MAX / ihdr->height) ctx->image_size = 0; /* overflow */ + else ctx->image_size = ctx->image_width * ihdr->height; + + if( !(flags & SPNG_DECODE_PROGRESSIVE) ) + { + if(out == NULL) return 1; + if(!ctx->image_size) return SPNG_EOVERFLOW; + if(len < ctx->image_size) return SPNG_EBUFSIZ; + } + + uint32_t bytes_read = 0; + + ret = read_idat_bytes(ctx, &bytes_read); + if(ret) return decode_err(ctx, ret); + + if(bytes_read > 1) + { + int valid = read_u16(ctx->data) % 31 ? 0 : 1; + + unsigned flg = ctx->data[1]; + unsigned flevel = flg >> 6; + int compression_level = Z_DEFAULT_COMPRESSION; + + if(flevel == 0) compression_level = 0; /* fastest */ + else if(flevel == 1) compression_level = 1; /* fast */ + else if(flevel == 2) compression_level = 6; /* default */ + else if(flevel == 3) compression_level = 9; /* slowest, max compression */ + + if(valid) ctx->image_options.compression_level = compression_level; + } + + ret = spng__inflate_init(ctx, ctx->image_options.window_bits); + if(ret) return decode_err(ctx, ret); + + ctx->zstream.avail_in = bytes_read; + ctx->zstream.next_in = ctx->data; + + size_t scanline_buf_size = ctx->subimage[ctx->widest_pass].scanline_width; + + scanline_buf_size += 32; + + if(scanline_buf_size < 32) return SPNG_EOVERFLOW; + + ctx->scanline_buf = spng__malloc(ctx, scanline_buf_size); + ctx->prev_scanline_buf = spng__malloc(ctx, scanline_buf_size); + + ctx->scanline = ctx->scanline_buf; + ctx->prev_scanline = ctx->prev_scanline_buf; + + struct decode_flags f = {0}; + + ctx->fmt = fmt; + + if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED) f.indexed = 1; + + unsigned processing_depth = ihdr->bit_depth; + + if(f.indexed) processing_depth = 8; + + if(ihdr->interlace_method) + { + f.interlaced = 1; + ctx->row_buf = spng__malloc(ctx, ctx->image_width); + ctx->row = ctx->row_buf; + + if(ctx->row == NULL) return decode_err(ctx, SPNG_EMEM); + } + + if(ctx->scanline == NULL || ctx->prev_scanline == NULL) + { + return decode_err(ctx, SPNG_EMEM); + } + + f.do_scaling = 1; + if(f.indexed) f.do_scaling = 0; + + unsigned depth_target = 8; /* FMT_RGBA8, G8 */ + if(fmt == SPNG_FMT_RGBA16) depth_target = 16; + + if(flags & SPNG_DECODE_TRNS && ctx->stored.trns) f.apply_trns = 1; + else flags &= ~SPNG_DECODE_TRNS; + + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || + ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) flags &= ~SPNG_DECODE_TRNS; + + if(flags & SPNG_DECODE_GAMMA && ctx->stored.gama) f.apply_gamma = 1; + else flags &= ~SPNG_DECODE_GAMMA; + + if(flags & SPNG_DECODE_USE_SBIT && ctx->stored.sbit) f.use_sbit = 1; + else flags &= ~SPNG_DECODE_USE_SBIT; + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGBA16)) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA && + ihdr->bit_depth == depth_target) f.same_layout = 1; + } + else if(fmt == SPNG_FMT_RGB8) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR && + ihdr->bit_depth == depth_target) f.same_layout = 1; + + f.apply_trns = 0; /* not applicable */ + } + else if(fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW)) + { + f.same_layout = 1; + f.do_scaling = 0; + f.apply_gamma = 0; /* for now */ + f.apply_trns = 0; + } + else if(fmt == SPNG_FMT_G8 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth <= 8) + { + if(ihdr->bit_depth == depth_target) f.same_layout = 1; + else if(ihdr->bit_depth < 8) f.unpack = 1; + + f.apply_trns = 0; + } + else if(fmt == SPNG_FMT_GA8 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth <= 8) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA && + ihdr->bit_depth == depth_target) f.same_layout = 1; + else if(ihdr->bit_depth <= 8) f.unpack = 1; + } + else if(fmt == SPNG_FMT_GA16 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth == 16) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA && + ihdr->bit_depth == depth_target) f.same_layout = 1; + else if(ihdr->bit_depth == 16) f.unpack = 1; + } + + /*if(f.same_layout && !flags && !f.interlaced) f.zerocopy = 1;*/ + + uint16_t *gamma_lut = NULL; + + if(f.apply_gamma) + { + float file_gamma = (float)ctx->gama / 100000.0f; + float max; + + unsigned lut_entries; + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + lut_entries = 256; + max = 255.0f; + + gamma_lut = ctx->gamma_lut8; + ctx->gamma_lut = ctx->gamma_lut8; + } + else /* SPNG_FMT_RGBA16 */ + { + lut_entries = 65536; + max = 65535.0f; + + ctx->gamma_lut16 = spng__malloc(ctx, lut_entries * sizeof(uint16_t)); + if(ctx->gamma_lut16 == NULL) return decode_err(ctx, SPNG_EMEM); + + gamma_lut = ctx->gamma_lut16; + ctx->gamma_lut = ctx->gamma_lut16; + } + + float screen_gamma = 2.2f; + float exponent = file_gamma * screen_gamma; + + if(FP_ZERO == fpclassify(exponent)) return decode_err(ctx, SPNG_EGAMA); + + exponent = 1.0f / exponent; + + unsigned i; + for(i=0; i < lut_entries; i++) + { + float c = pow((float)i / max, exponent) * max; + if(c > max) c = max; + + gamma_lut[i] = (uint16_t)c; + } + } + + struct spng_sbit *sb = &ctx->decode_sb; + + sb->red_bits = processing_depth; + sb->green_bits = processing_depth; + sb->blue_bits = processing_depth; + sb->alpha_bits = processing_depth; + sb->grayscale_bits = processing_depth; + + if(f.use_sbit) + { + if(ihdr->color_type == 0) + { + sb->grayscale_bits = ctx->sbit.grayscale_bits; + sb->alpha_bits = ihdr->bit_depth; + } + else if(ihdr->color_type == 2 || ihdr->color_type == 3) + { + sb->red_bits = ctx->sbit.red_bits; + sb->green_bits = ctx->sbit.green_bits; + sb->blue_bits = ctx->sbit.blue_bits; + sb->alpha_bits = ihdr->bit_depth; + } + else if(ihdr->color_type == 4) + { + sb->grayscale_bits = ctx->sbit.grayscale_bits; + sb->alpha_bits = ctx->sbit.alpha_bits; + } + else /* == 6 */ + { + sb->red_bits = ctx->sbit.red_bits; + sb->green_bits = ctx->sbit.green_bits; + sb->blue_bits = ctx->sbit.blue_bits; + sb->alpha_bits = ctx->sbit.alpha_bits; + } + } + + if(ihdr->bit_depth == 16 && fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + {/* samples are scaled down by 8 bits in the decode loop */ + sb->red_bits -= 8; + sb->green_bits -= 8; + sb->blue_bits -= 8; + sb->alpha_bits -= 8; + sb->grayscale_bits -= 8; + + processing_depth = 8; + } + + /* Prevent infinite loops in sample_to_target() */ + if(!depth_target || depth_target > 16 || + !processing_depth || processing_depth > 16 || + !sb->grayscale_bits || sb->grayscale_bits > processing_depth || + !sb->alpha_bits || sb->alpha_bits > processing_depth || + !sb->red_bits || sb->red_bits > processing_depth || + !sb->green_bits || sb->green_bits > processing_depth || + !sb->blue_bits || sb->blue_bits > processing_depth) + { + return decode_err(ctx, SPNG_ESBIT); + } + + if(sb->red_bits == sb->green_bits && + sb->green_bits == sb->blue_bits && + sb->blue_bits == sb->alpha_bits && + sb->alpha_bits == processing_depth && + processing_depth == depth_target) f.do_scaling = 0; + + struct spng_plte_entry *plte = ctx->decode_plte.rgba; + + /* Pre-process palette entries */ + if(f.indexed) + { + uint8_t red, green, blue, alpha; + + uint32_t i; + for(i=0; i < 256; i++) + { + if(f.apply_trns && i < ctx->trns.n_type3_entries) + ctx->plte.entries[i].alpha = ctx->trns.type3_alpha[i]; + else + ctx->plte.entries[i].alpha = 255; + + red = sample_to_target(ctx->plte.entries[i].red, 8, sb->red_bits, 8); + green = sample_to_target(ctx->plte.entries[i].green, 8, sb->green_bits, 8); + blue = sample_to_target(ctx->plte.entries[i].blue, 8, sb->blue_bits, 8); + alpha = sample_to_target(ctx->plte.entries[i].alpha, 8, sb->alpha_bits, 8); + +#if defined(SPNG_ARM) + if(fmt == SPNG_FMT_RGB8 && ihdr->bit_depth == 8) + {/* Working with 3 bytes at a time is more of an ARM thing */ + ctx->decode_plte.rgb[i * 3 + 0] = red; + ctx->decode_plte.rgb[i * 3 + 1] = green; + ctx->decode_plte.rgb[i * 3 + 2] = blue; + continue; + } +#endif + plte[i].red = red; + plte[i].green = green; + plte[i].blue = blue; + plte[i].alpha = alpha; + } + + f.apply_trns = 0; + } + + unsigned char *trns_px = ctx->trns_px; + + if(f.apply_trns) + { + uint16_t mask = ~0; + if(ctx->ihdr.bit_depth < 16) mask = (1 << ctx->ihdr.bit_depth) - 1; + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGBA16)) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR) + { + if(ihdr->bit_depth == 16) + { + memcpy(trns_px, &ctx->trns.red, 2); + memcpy(trns_px + 2, &ctx->trns.green, 2); + memcpy(trns_px + 4, &ctx->trns.blue, 2); + } + else + { + trns_px[0] = ctx->trns.red & mask; + trns_px[1] = ctx->trns.green & mask; + trns_px[2] = ctx->trns.blue & mask; + } + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) // fmt == SPNG_FMT_GA8 && + { + if(ihdr->bit_depth == 16) + { + memcpy(trns_px, &ctx->trns.gray, 2); + } + else + { + trns_px[0] = ctx->trns.gray & mask; + } + } + } + + ctx->decode_flags = f; + + ctx->state = SPNG_STATE_DECODE_INIT; + + struct spng_row_info *ri = &ctx->row_info; + struct spng_subimage *sub = ctx->subimage; + + while(!sub[ri->pass].width || !sub[ri->pass].height) ri->pass++; + + if(f.interlaced) ri->row_num = adam7_y_start[ri->pass]; + + unsigned pixel_size = 4; /* SPNG_FMT_RGBA8 */ + + if(fmt == SPNG_FMT_RGBA16) pixel_size = 8; + else if(fmt == SPNG_FMT_RGB8) pixel_size = 3; + else if(fmt == SPNG_FMT_G8) pixel_size = 1; + else if(fmt == SPNG_FMT_GA8) pixel_size = 2; + + int i; + for(i=ri->pass; i <= ctx->last_pass; i++) + { + if(!sub[i].scanline_width) continue; + + if(fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW)) sub[i].out_width = sub[i].scanline_width - 1; + else sub[i].out_width = (size_t)sub[i].width * pixel_size; + + if(sub[i].out_width > UINT32_MAX) return decode_err(ctx, SPNG_EOVERFLOW); + } + + /* Read the first filter byte, offsetting all reads by 1 byte. + The scanlines will be aligned with the start of the array with + the next scanline's filter byte at the end, + the last scanline will end up being 1 byte "shorter". */ + ret = read_scanline_bytes(ctx, &ri->filter, 1); + if(ret) return decode_err(ctx, ret); + + if(ri->filter > 4) return decode_err(ctx, SPNG_EFILTER); + + if(flags & SPNG_DECODE_PROGRESSIVE) + { + return 0; + } + + do + { + size_t ioffset = ri->row_num * ctx->image_width; + + ret = spng_decode_row(ctx, (unsigned char*)out + ioffset, ctx->image_width); + }while(!ret); + + if(ret != SPNG_EOI) return decode_err(ctx, ret); + + return 0; +} + +int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info) +{ + if(ctx == NULL || row_info == NULL || ctx->state < SPNG_STATE_DECODE_INIT) return 1; + + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + + *row_info = ctx->row_info; + + return 0; +} + +static int write_chunks_before_idat(spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!ctx->encode_only) return SPNG_EINTERNAL; + if(!ctx->stored.ihdr) return SPNG_EINTERNAL; + + int ret; + uint32_t i; + size_t length; + const struct spng_ihdr *ihdr = &ctx->ihdr; + unsigned char *data = ctx->decode_plte.raw; + + ret = write_data(ctx, spng_signature, 8); + if(ret) return ret; + + write_u32(data, ihdr->width); + write_u32(data + 4, ihdr->height); + data[8] = ihdr->bit_depth; + data[9] = ihdr->color_type; + data[10] = ihdr->compression_method; + data[11] = ihdr->filter_method; + data[12] = ihdr->interlace_method; + + ret = write_chunk(ctx, type_ihdr, data, 13); + if(ret) return ret; + + if(ctx->stored.chrm) + { + write_u32(data, ctx->chrm_int.white_point_x); + write_u32(data + 4, ctx->chrm_int.white_point_y); + write_u32(data + 8, ctx->chrm_int.red_x); + write_u32(data + 12, ctx->chrm_int.red_y); + write_u32(data + 16, ctx->chrm_int.green_x); + write_u32(data + 20, ctx->chrm_int.green_y); + write_u32(data + 24, ctx->chrm_int.blue_x); + write_u32(data + 28, ctx->chrm_int.blue_y); + + ret = write_chunk(ctx, type_chrm, data, 32); + if(ret) return ret; + } + + if(ctx->stored.gama) + { + write_u32(data, ctx->gama); + + ret = write_chunk(ctx, type_gama, data, 4); + if(ret) return ret; + } + + if(ctx->stored.iccp) + { + uLongf dest_len = compressBound((uLong)ctx->iccp.profile_len); + + Bytef *buf = spng__malloc(ctx, dest_len); + if(buf == NULL) return SPNG_EMEM; + + ret = compress2(buf, &dest_len, (void*)ctx->iccp.profile, (uLong)ctx->iccp.profile_len, Z_DEFAULT_COMPRESSION); + + if(ret != Z_OK) + { + spng__free(ctx, buf); + return SPNG_EZLIB; + } + + size_t name_len = strlen(ctx->iccp.profile_name); + + length = name_len + 2; + length += dest_len; + + if(dest_len > length) return SPNG_EOVERFLOW; + + unsigned char *cdata = NULL; + + ret = write_header(ctx, type_iccp, length, &cdata); + + if(ret) + { + spng__free(ctx, buf); + return ret; + } + + memcpy(cdata, ctx->iccp.profile_name, name_len + 1); + cdata[name_len + 1] = 0; /* compression method */ + memcpy(cdata + name_len + 2, buf, dest_len); + + spng__free(ctx, buf); + + ret = finish_chunk(ctx); + if(ret) return ret; + } + + if(ctx->stored.sbit) + { + switch(ctx->ihdr.color_type) + { + case SPNG_COLOR_TYPE_GRAYSCALE: + { + length = 1; + + data[0] = ctx->sbit.grayscale_bits; + + break; + } + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_INDEXED: + { + length = 3; + + data[0] = ctx->sbit.red_bits; + data[1] = ctx->sbit.green_bits; + data[2] = ctx->sbit.blue_bits; + + break; + } + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + { + length = 2; + + data[0] = ctx->sbit.grayscale_bits; + data[1] = ctx->sbit.alpha_bits; + + break; + } + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + { + length = 4; + + data[0] = ctx->sbit.red_bits; + data[1] = ctx->sbit.green_bits; + data[2] = ctx->sbit.blue_bits; + data[3] = ctx->sbit.alpha_bits; + + break; + } + default: return SPNG_EINTERNAL; + } + + ret = write_chunk(ctx, type_sbit, data, length); + if(ret) return ret; + } + + if(ctx->stored.srgb) + { + ret = write_chunk(ctx, type_srgb, &ctx->srgb_rendering_intent, 1); + if(ret) return ret; + } + + ret = write_unknown_chunks(ctx, SPNG_AFTER_IHDR); + if(ret) return ret; + + if(ctx->stored.plte) + { + for(i=0; i < ctx->plte.n_entries; i++) + { + data[i * 3 + 0] = ctx->plte.entries[i].red; + data[i * 3 + 1] = ctx->plte.entries[i].green; + data[i * 3 + 2] = ctx->plte.entries[i].blue; + } + + ret = write_chunk(ctx, type_plte, data, ctx->plte.n_entries * 3); + if(ret) return ret; + } + + if(ctx->stored.bkgd) + { + switch(ctx->ihdr.color_type) + { + case SPNG_COLOR_TYPE_GRAYSCALE: + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + { + length = 2; + + write_u16(data, ctx->bkgd.gray); + + break; + } + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + { + length = 6; + + write_u16(data, ctx->bkgd.red); + write_u16(data + 2, ctx->bkgd.green); + write_u16(data + 4, ctx->bkgd.blue); + + break; + } + case SPNG_COLOR_TYPE_INDEXED: + { + length = 1; + + data[0] = ctx->bkgd.plte_index; + + break; + } + default: return SPNG_EINTERNAL; + } + + ret = write_chunk(ctx, type_bkgd, data, length); + if(ret) return ret; + } + + if(ctx->stored.hist) + { + length = ctx->plte.n_entries * 2; + + for(i=0; i < ctx->plte.n_entries; i++) + { + write_u16(data + i * 2, ctx->hist.frequency[i]); + } + + ret = write_chunk(ctx, type_hist, data, length); + if(ret) return ret; + } + + if(ctx->stored.trns) + { + if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE) + { + write_u16(data, ctx->trns.gray); + + ret = write_chunk(ctx, type_trns, data, 2); + } + else if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR) + { + write_u16(data, ctx->trns.red); + write_u16(data + 2, ctx->trns.green); + write_u16(data + 4, ctx->trns.blue); + + ret = write_chunk(ctx, type_trns, data, 6); + } + else if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED) + { + ret = write_chunk(ctx, type_trns, ctx->trns.type3_alpha, ctx->trns.n_type3_entries); + } + + if(ret) return ret; + } + + if(ctx->stored.phys) + { + write_u32(data, ctx->phys.ppu_x); + write_u32(data + 4, ctx->phys.ppu_y); + data[8] = ctx->phys.unit_specifier; + + ret = write_chunk(ctx, type_phys, data, 9); + if(ret) return ret; + } + + if(ctx->stored.splt) + { + const struct spng_splt *splt; + unsigned char *cdata = NULL; + + uint32_t k; + for(i=0; i < ctx->n_splt; i++) + { + splt = &ctx->splt_list[i]; + + size_t name_len = strlen(splt->name); + length = name_len + 1; + + if(splt->sample_depth == 8) length += splt->n_entries * 6 + 1; + else if(splt->sample_depth == 16) length += splt->n_entries * 10 + 1; + + ret = write_header(ctx, type_splt, length, &cdata); + if(ret) return ret; + + memcpy(cdata, splt->name, name_len + 1); + cdata += name_len + 2; + cdata[-1] = splt->sample_depth; + + if(splt->sample_depth == 8) + { + for(k=0; k < splt->n_entries; k++) + { + cdata[k * 6 + 0] = splt->entries[k].red; + cdata[k * 6 + 1] = splt->entries[k].green; + cdata[k * 6 + 2] = splt->entries[k].blue; + cdata[k * 6 + 3] = splt->entries[k].alpha; + write_u16(cdata + k * 6 + 4, splt->entries[k].frequency); + } + } + else if(splt->sample_depth == 16) + { + for(k=0; k < splt->n_entries; k++) + { + write_u16(cdata + k * 10 + 0, splt->entries[k].red); + write_u16(cdata + k * 10 + 2, splt->entries[k].green); + write_u16(cdata + k * 10 + 4, splt->entries[k].blue); + write_u16(cdata + k * 10 + 6, splt->entries[k].alpha); + write_u16(cdata + k * 10 + 8, splt->entries[k].frequency); + } + } + + ret = finish_chunk(ctx); + if(ret) return ret; + } + } + + if(ctx->stored.time) + { + write_u16(data, ctx->time.year); + data[2] = ctx->time.month; + data[3] = ctx->time.day; + data[4] = ctx->time.hour; + data[5] = ctx->time.minute; + data[6] = ctx->time.second; + + ret = write_chunk(ctx, type_time, data, 7); + if(ret) return ret; + } + + if(ctx->stored.text) + { + unsigned char *cdata = NULL; + const struct spng_text2 *text; + const uint8_t *text_type_array[4] = { 0, type_text, type_ztxt, type_itxt }; + + for(i=0; i < ctx->n_text; i++) + { + text = &ctx->text_list[i]; + + const uint8_t *text_chunk_type = text_type_array[text->type]; + Bytef *compressed_text = NULL; + size_t keyword_len = 0; + size_t language_tag_len = 0; + size_t translated_keyword_len = 0; + size_t compressed_length = 0; + size_t text_length = 0; + + keyword_len = strlen(text->keyword); + text_length = strlen(text->text); + + length = keyword_len + 1; + + if(text->type == SPNG_ZTXT) + { + length += 1; /* compression method */ + } + else if(text->type == SPNG_ITXT) + { + if(!text->language_tag || !text->translated_keyword) return SPNG_EINTERNAL; + + language_tag_len = strlen(text->language_tag); + translated_keyword_len = strlen(text->translated_keyword); + + length += language_tag_len; + if(length < language_tag_len) return SPNG_EOVERFLOW; + + length += translated_keyword_len; + if(length < translated_keyword_len) return SPNG_EOVERFLOW; + + length += 4; /* compression flag + method + nul for the two strings */ + if(length < 4) return SPNG_EOVERFLOW; + } + + if(text->compression_flag) + { + ret = spng__deflate_init(ctx, &ctx->text_options); + if(ret) return ret; + + z_stream *zstream = &ctx->zstream; + uLongf dest_len = deflateBound(zstream, (uLong)text_length); + + compressed_text = spng__malloc(ctx, dest_len); + + if(compressed_text == NULL) return SPNG_EMEM; + + zstream->next_in = (void*)text->text; + zstream->avail_in = (uInt)text_length; + + zstream->next_out = compressed_text; + zstream->avail_out = dest_len; + + ret = deflate(zstream, Z_FINISH); + + if(ret != Z_STREAM_END) + { + spng__free(ctx, compressed_text); + return SPNG_EZLIB; + } + + compressed_length = zstream->total_out; + + length += compressed_length; + if(length < compressed_length) return SPNG_EOVERFLOW; + } + else + { + text_length = strlen(text->text); + + length += text_length; + if(length < text_length) return SPNG_EOVERFLOW; + } + + ret = write_header(ctx, text_chunk_type, length, &cdata); + if(ret) + { + spng__free(ctx, compressed_text); + return ret; + } + + memcpy(cdata, text->keyword, keyword_len + 1); + cdata += keyword_len + 1; + + if(text->type == SPNG_ITXT) + { + cdata[0] = text->compression_flag; + cdata[1] = 0; /* compression method */ + cdata += 2; + + memcpy(cdata, text->language_tag, language_tag_len + 1); + cdata += language_tag_len + 1; + + memcpy(cdata, text->translated_keyword, translated_keyword_len + 1); + cdata += translated_keyword_len + 1; + } + else if(text->type == SPNG_ZTXT) + { + cdata[0] = 0; /* compression method */ + cdata++; + } + + if(text->compression_flag) memcpy(cdata, compressed_text, compressed_length); + else memcpy(cdata, text->text, text_length); + + spng__free(ctx, compressed_text); + + ret = finish_chunk(ctx); + if(ret) return ret; + } + } + + if(ctx->stored.offs) + { + write_s32(data, ctx->offs.x); + write_s32(data + 4, ctx->offs.y); + data[8] = ctx->offs.unit_specifier; + + ret = write_chunk(ctx, type_offs, data, 9); + if(ret) return ret; + } + + if(ctx->stored.exif) + { + ret = write_chunk(ctx, type_exif, ctx->exif.data, ctx->exif.length); + if(ret) return ret; + } + + ret = write_unknown_chunks(ctx, SPNG_AFTER_PLTE); + if(ret) return ret; + + return 0; +} + +static int write_chunks_after_idat(spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + int ret = write_unknown_chunks(ctx, SPNG_AFTER_IDAT); + if(ret) return ret; + + return write_iend(ctx); +} + +/* Compress and write scanline to IDAT stream */ +static int write_idat_bytes(spng_ctx *ctx, const void *scanline, size_t len, int flush) +{ + if(ctx == NULL || scanline == NULL) return SPNG_EINTERNAL; + if(len > UINT_MAX) return SPNG_EINTERNAL; + + int ret = 0; + unsigned char *data = NULL; + z_stream *zstream = &ctx->zstream; + uint32_t idat_length = SPNG_WRITE_SIZE; + + zstream->next_in = scanline; + zstream->avail_in = (uInt)len; + + do + { + ret = deflate(zstream, flush); + + if(zstream->avail_out == 0) + { + ret = finish_chunk(ctx); + if(ret) return encode_err(ctx, ret); + + ret = write_header(ctx, type_idat, idat_length, &data); + if(ret) return encode_err(ctx, ret); + + zstream->next_out = data; + zstream->avail_out = idat_length; + } + + }while(zstream->avail_in); + + if(ret != Z_OK) return SPNG_EZLIB; + + return 0; +} + +static int finish_idat(spng_ctx *ctx) +{ + int ret = 0; + unsigned char *data = NULL; + z_stream *zstream = &ctx->zstream; + uint32_t idat_length = SPNG_WRITE_SIZE; + + while(ret != Z_STREAM_END) + { + ret = deflate(zstream, Z_FINISH); + + if(ret) + { + if(ret == Z_STREAM_END) break; + + if(ret != Z_BUF_ERROR) return SPNG_EZLIB; + } + + if(zstream->avail_out == 0) + { + ret = finish_chunk(ctx); + if(ret) return encode_err(ctx, ret); + + ret = write_header(ctx, type_idat, idat_length, &data); + if(ret) return encode_err(ctx, ret); + + zstream->next_out = data; + zstream->avail_out = idat_length; + } + } + + uint32_t trimmed_length = idat_length - zstream->avail_out; + + ret = trim_chunk(ctx, trimmed_length); + if(ret) return ret; + + return finish_chunk(ctx); +} + +static int encode_scanline(spng_ctx *ctx, const void *scanline, size_t len) +{ + if(ctx == NULL || scanline == NULL) return SPNG_EINTERNAL; + + int ret, pass = ctx->row_info.pass; + uint8_t filter = 0; + struct spng_row_info *ri = &ctx->row_info; + const struct spng_subimage *sub = ctx->subimage; + struct encode_flags f = ctx->encode_flags; + unsigned char *filtered_scanline = ctx->filtered_scanline; + size_t scanline_width = sub[pass].scanline_width; + + if(len < scanline_width - 1) return SPNG_EINTERNAL; + + /* encode_row() interlaces directly to ctx->scanline */ + if(scanline != ctx->scanline) memcpy(ctx->scanline, scanline, scanline_width - 1); + + if(f.to_bigendian) u16_row_to_bigendian(ctx->scanline, scanline_width - 1); + const int requires_previous = f.filter_choice & (SPNG_FILTER_CHOICE_UP | SPNG_FILTER_CHOICE_AVG | SPNG_FILTER_CHOICE_PAETH); + + /* XXX: exclude 'requires_previous' filters by default for first scanline? */ + if(!ri->scanline_idx && requires_previous) + { + /* prev_scanline is all zeros for the first scanline */ + memset(ctx->prev_scanline, 0, scanline_width); + } + + filter = get_best_filter(ctx->prev_scanline, ctx->scanline, scanline_width, ctx->bytes_per_pixel, f.filter_choice); + + if(!filter) filtered_scanline = ctx->scanline; + + filtered_scanline[-1] = filter; + + if(filter) + { + ret = filter_scanline(filtered_scanline, ctx->prev_scanline, ctx->scanline, scanline_width, ctx->bytes_per_pixel, filter); + if(ret) return encode_err(ctx, ret); + } + + ret = write_idat_bytes(ctx, filtered_scanline - 1, scanline_width, Z_NO_FLUSH); + if(ret) return encode_err(ctx, ret); + + /* The previous scanline is always unfiltered */ + void *t = ctx->prev_scanline; + ctx->prev_scanline = ctx->scanline; + ctx->scanline = t; + + ret = update_row_info(ctx); + + if(ret == SPNG_EOI) + { + int error = finish_idat(ctx); + if(error) encode_err(ctx, error); + + if(f.finalize) + { + error = spng_encode_chunks(ctx); + if(error) return encode_err(ctx, error); + } + } + + return ret; +} + +static int encode_row(spng_ctx *ctx, const void *row, size_t len) +{ + if(ctx == NULL || row == NULL) return SPNG_EINTERNAL; + + const int pass = ctx->row_info.pass; + + if(!ctx->ihdr.interlace_method || pass == 6) return encode_scanline(ctx, row, len); + + uint32_t k; + const unsigned pixel_size = ctx->pixel_size; + const unsigned bit_depth = ctx->ihdr.bit_depth; + + if(bit_depth < 8) + { + const unsigned samples_per_byte = 8 / bit_depth; + const uint8_t mask = (1 << bit_depth) - 1; + const unsigned initial_shift = 8 - bit_depth; + unsigned shift_amount = initial_shift; + + unsigned char *scanline = ctx->scanline; + const unsigned char *row_uc = row; + uint8_t sample; + + memset(scanline, 0, ctx->subimage[pass].scanline_width); + + for(k=0; k < ctx->subimage[pass].width; k++) + { + size_t ioffset = adam7_x_start[pass] + k * adam7_x_delta[pass]; + + sample = row_uc[ioffset / samples_per_byte]; + + sample = sample >> (initial_shift - ioffset * bit_depth % 8); + sample = sample & mask; + sample = sample << shift_amount; + + scanline[0] |= sample; + + shift_amount -= bit_depth; + + if(shift_amount > 7) + { + shift_amount = initial_shift; + scanline++; + } + } + + return encode_scanline(ctx, ctx->scanline, len); + } + + for(k=0; k < ctx->subimage[pass].width; k++) + { + size_t ioffset = (adam7_x_start[pass] + (size_t) k * adam7_x_delta[pass]) * pixel_size; + + memcpy(ctx->scanline + k * pixel_size, (unsigned char*)row + ioffset, pixel_size); + } + + return encode_scanline(ctx, ctx->scanline, len); +} + +int spng_encode_scanline(spng_ctx *ctx, const void *scanline, size_t len) +{ + if(ctx == NULL || scanline == NULL) return SPNG_EINVAL; + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + if(len < (ctx->subimage[ctx->row_info.pass].scanline_width -1) ) return SPNG_EBUFSIZ; + + return encode_scanline(ctx, scanline, len); +} + +int spng_encode_row(spng_ctx *ctx, const void *row, size_t len) +{ + if(ctx == NULL || row == NULL) return SPNG_EINVAL; + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + if(len < ctx->image_width) return SPNG_EBUFSIZ; + + return encode_row(ctx, row, len); +} + +int spng_encode_chunks(spng_ctx *ctx) +{ + if(ctx == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + if(ctx->state < SPNG_STATE_OUTPUT) return SPNG_ENODST; + if(!ctx->encode_only) return SPNG_ECTXTYPE; + + int ret = 0; + + if(ctx->state < SPNG_STATE_FIRST_IDAT) + { + if(!ctx->stored.ihdr) return SPNG_ENOIHDR; + + ret = write_chunks_before_idat(ctx); + if(ret) return encode_err(ctx, ret); + + ctx->state = SPNG_STATE_FIRST_IDAT; + } + else if(ctx->state == SPNG_STATE_FIRST_IDAT) + { + return 0; + } + else if(ctx->state == SPNG_STATE_EOI) + { + ret = write_chunks_after_idat(ctx); + if(ret) return encode_err(ctx, ret); + + ctx->state = SPNG_STATE_IEND; + } + else return SPNG_EOPSTATE; + + return 0; +} + +int spng_encode_image(spng_ctx *ctx, const void *img, size_t len, int fmt, int flags) +{ + if(ctx == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + if(!ctx->encode_only) return SPNG_ECTXTYPE; + if(!ctx->stored.ihdr) return SPNG_ENOIHDR; + if( !(fmt == SPNG_FMT_PNG || fmt == SPNG_FMT_RAW) ) return SPNG_EFMT; + + int ret = 0; + const struct spng_ihdr *ihdr = &ctx->ihdr; + struct encode_flags *encode_flags = &ctx->encode_flags; + + if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED && !ctx->stored.plte) return SPNG_ENOPLTE; + + ret = calculate_image_width(ihdr, fmt, &ctx->image_width); + if(ret) return encode_err(ctx, ret); + + if(ctx->image_width > SIZE_MAX / ihdr->height) ctx->image_size = 0; /* overflow */ + else ctx->image_size = ctx->image_width * ihdr->height; + + if( !(flags & SPNG_ENCODE_PROGRESSIVE) ) + { + if(img == NULL) return 1; + if(!ctx->image_size) return SPNG_EOVERFLOW; + if(len != ctx->image_size) return SPNG_EBUFSIZ; + } + + ret = spng_encode_chunks(ctx); + if(ret) return encode_err(ctx, ret); + + ret = calculate_subimages(ctx); + if(ret) return encode_err(ctx, ret); + + if(ihdr->bit_depth < 8) ctx->bytes_per_pixel = 1; + else ctx->bytes_per_pixel = num_channels(ihdr) * (ihdr->bit_depth / 8); + + if(spng__optimize(SPNG_FILTER_CHOICE)) + { + /* Filtering would make no difference */ + if(!ctx->image_options.compression_level) + { + encode_flags->filter_choice = SPNG_DISABLE_FILTERING; + } + + /* Palette indices and low bit-depth images do not benefit from filtering */ + if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED || ihdr->bit_depth < 8) + { + encode_flags->filter_choice = SPNG_DISABLE_FILTERING; + } + } + + /* This is technically the same as disabling filtering */ + if(encode_flags->filter_choice == SPNG_FILTER_CHOICE_NONE) + { + encode_flags->filter_choice = SPNG_DISABLE_FILTERING; + } + + if(!encode_flags->filter_choice && spng__optimize(SPNG_IMG_COMPRESSION_STRATEGY)) + { + ctx->image_options.strategy = Z_DEFAULT_STRATEGY; + } + + ret = spng__deflate_init(ctx, &ctx->image_options); + if(ret) return encode_err(ctx, ret); + + size_t scanline_buf_size = ctx->subimage[ctx->widest_pass].scanline_width; + + scanline_buf_size += 32; + + if(scanline_buf_size < 32) return SPNG_EOVERFLOW; + + ctx->scanline_buf = spng__malloc(ctx, scanline_buf_size); + ctx->prev_scanline_buf = spng__malloc(ctx, scanline_buf_size); + + if(ctx->scanline_buf == NULL || ctx->prev_scanline_buf == NULL) return encode_err(ctx, SPNG_EMEM); + + /* Maintain alignment for pixels, filter at [-1] */ + ctx->scanline = ctx->scanline_buf + 16; + ctx->prev_scanline = ctx->prev_scanline_buf + 16; + + if(encode_flags->filter_choice) + { + ctx->filtered_scanline_buf = spng__malloc(ctx, scanline_buf_size); + if(ctx->filtered_scanline_buf == NULL) return encode_err(ctx, SPNG_EMEM); + + ctx->filtered_scanline = ctx->filtered_scanline_buf + 16; + } + + struct spng_subimage *sub = ctx->subimage; + struct spng_row_info *ri = &ctx->row_info; + + ctx->fmt = fmt; + + z_stream *zstream = &ctx->zstream; + zstream->avail_out = SPNG_WRITE_SIZE; + + ret = write_header(ctx, type_idat, zstream->avail_out, &zstream->next_out); + if(ret) return encode_err(ctx, ret); + + if(ihdr->interlace_method) encode_flags->interlace = 1; + + if(fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW) ) encode_flags->same_layout = 1; + + if(ihdr->bit_depth == 16 && fmt != SPNG_FMT_RAW) encode_flags->to_bigendian = 1; + + if(flags & SPNG_ENCODE_FINALIZE) encode_flags->finalize = 1; + + while(!sub[ri->pass].width || !sub[ri->pass].height) ri->pass++; + + if(encode_flags->interlace) ri->row_num = adam7_y_start[ri->pass]; + + ctx->pixel_size = 4; /* SPNG_FMT_RGBA8 */ + + if(fmt == SPNG_FMT_RGBA16) ctx->pixel_size = 8; + else if(fmt == SPNG_FMT_RGB8) ctx->pixel_size = 3; + else if(fmt == SPNG_FMT_G8) ctx->pixel_size = 1; + else if(fmt == SPNG_FMT_GA8) ctx->pixel_size = 2; + else if(fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW)) ctx->pixel_size = ctx->bytes_per_pixel; + + ctx->state = SPNG_STATE_ENCODE_INIT; + + if(flags & SPNG_ENCODE_PROGRESSIVE) + { + encode_flags->progressive = 1; + + return 0; + } + + do + { + size_t ioffset = ri->row_num * ctx->image_width; + + ret = encode_row(ctx, (unsigned char*)img + ioffset, ctx->image_width); + + }while(!ret); + + if(ret != SPNG_EOI) return encode_err(ctx, ret); + + return 0; +} + +spng_ctx *spng_ctx_new(int flags) +{ + struct spng_alloc alloc = + { + .malloc_fn = malloc, + .realloc_fn = realloc, + .calloc_fn = calloc, + .free_fn = free + }; + + return spng_ctx_new2(&alloc, flags); +} + +spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags) +{ + if(alloc == NULL) return NULL; + if(flags != (flags & SPNG__CTX_FLAGS_ALL)) return NULL; + + if(alloc->malloc_fn == NULL) return NULL; + if(alloc->realloc_fn == NULL) return NULL; + if(alloc->calloc_fn == NULL) return NULL; + if(alloc->free_fn == NULL) return NULL; + + spng_ctx *ctx = alloc->calloc_fn(1, sizeof(spng_ctx)); + if(ctx == NULL) return NULL; + + ctx->alloc = *alloc; + + ctx->max_width = spng_u32max; + ctx->max_height = spng_u32max; + + ctx->max_chunk_size = spng_u32max; + ctx->chunk_cache_limit = SIZE_MAX; + ctx->chunk_count_limit = SPNG_MAX_CHUNK_COUNT; + + ctx->state = SPNG_STATE_INIT; + + ctx->crc_action_critical = SPNG_CRC_ERROR; + ctx->crc_action_ancillary = SPNG_CRC_DISCARD; + + const struct spng__zlib_options image_defaults = + { + .compression_level = Z_DEFAULT_COMPRESSION, + .window_bits = 15, + .mem_level = 8, + .strategy = Z_FILTERED, + .data_type = 0 /* Z_BINARY */ + }; + + const struct spng__zlib_options text_defaults = + { + .compression_level = Z_DEFAULT_COMPRESSION, + .window_bits = 15, + .mem_level = 8, + .strategy = Z_DEFAULT_STRATEGY, + .data_type = 1 /* Z_TEXT */ + }; + + ctx->image_options = image_defaults; + ctx->text_options = text_defaults; + + ctx->optimize_option = ~0; + ctx->encode_flags.filter_choice = SPNG_FILTER_CHOICE_ALL; + + ctx->flags = flags; + + if(flags & SPNG_CTX_ENCODER) ctx->encode_only = 1; + + return ctx; +} + +void spng_ctx_free(spng_ctx *ctx) +{ + if(ctx == NULL) return; + + if(ctx->streaming && ctx->stream_buf != NULL) spng__free(ctx, ctx->stream_buf); + + if(!ctx->user.exif) spng__free(ctx, ctx->exif.data); + + if(!ctx->user.iccp) spng__free(ctx, ctx->iccp.profile); + + uint32_t i; + + if(ctx->splt_list != NULL && !ctx->user.splt) + { + for(i=0; i < ctx->n_splt; i++) + { + spng__free(ctx, ctx->splt_list[i].entries); + } + spng__free(ctx, ctx->splt_list); + } + + if(ctx->text_list != NULL) + { + for(i=0; i< ctx->n_text; i++) + { + if(ctx->user.text) break; + + spng__free(ctx, ctx->text_list[i].keyword); + if(ctx->text_list[i].compression_flag) spng__free(ctx, ctx->text_list[i].text); + } + spng__free(ctx, ctx->text_list); + } + + if(ctx->chunk_list != NULL && !ctx->user.unknown) + { + for(i=0; i< ctx->n_chunks; i++) + { + spng__free(ctx, ctx->chunk_list[i].data); + } + spng__free(ctx, ctx->chunk_list); + } + + if(ctx->deflate) deflateEnd(&ctx->zstream); + else inflateEnd(&ctx->zstream); + + if(!ctx->user_owns_out_png) spng__free(ctx, ctx->out_png); + + spng__free(ctx, ctx->gamma_lut16); + + spng__free(ctx, ctx->row_buf); + spng__free(ctx, ctx->scanline_buf); + spng__free(ctx, ctx->prev_scanline_buf); + spng__free(ctx, ctx->filtered_scanline_buf); + + spng_free_fn *free_func = ctx->alloc.free_fn; + + memset(ctx, 0, sizeof(spng_ctx)); + + free_func(ctx); +} + +static int buffer_read_fn(spng_ctx *ctx, void *user, void *data, size_t n) +{ + if(n > ctx->bytes_left) return SPNG_IO_EOF; + + (void)user; + (void)data; + ctx->data = ctx->data + ctx->last_read_size; + + ctx->last_read_size = n; + ctx->bytes_left -= n; + + return 0; +} + +static int file_read_fn(spng_ctx *ctx, void *user, void *data, size_t n) +{ + FILE *file = user; + (void)ctx; + + if(fread(data, n, 1, file) != 1) + { + if(feof(file)) return SPNG_IO_EOF; + else return SPNG_IO_ERROR; + } + + return 0; +} + +static int file_write_fn(spng_ctx *ctx, void *user, void *data, size_t n) +{ + FILE *file = user; + (void)ctx; + + if(fwrite(data, n, 1, file) != 1) return SPNG_IO_ERROR; + + return 0; +} + +int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size) +{ + if(ctx == NULL || buf == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + if(ctx->encode_only) return SPNG_ECTXTYPE; /* not supported */ + + if(ctx->data != NULL) return SPNG_EBUF_SET; + + ctx->data = buf; + ctx->png_base = buf; + ctx->data_size = size; + ctx->bytes_left = size; + + ctx->read_fn = buffer_read_fn; + + ctx->state = SPNG_STATE_INPUT; + + return 0; +} + +int spng_set_png_stream(spng_ctx *ctx, spng_rw_fn *rw_func, void *user) +{ + if(ctx == NULL || rw_func == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + + /* SPNG_STATE_OUTPUT shares the same value */ + if(ctx->state >= SPNG_STATE_INPUT) return SPNG_EBUF_SET; + + if(ctx->encode_only) + { + if(ctx->out_png != NULL) return SPNG_EBUF_SET; + + ctx->write_fn = rw_func; + ctx->write_ptr = ctx->stream_buf; + + ctx->state = SPNG_STATE_OUTPUT; + } + else + { + ctx->stream_buf = spng__malloc(ctx, SPNG_READ_SIZE); + if(ctx->stream_buf == NULL) return SPNG_EMEM; + + ctx->read_fn = rw_func; + ctx->data = ctx->stream_buf; + ctx->data_size = SPNG_READ_SIZE; + + ctx->state = SPNG_STATE_INPUT; + } + + ctx->stream_user_ptr = user; + + ctx->streaming = 1; + + return 0; +} + +int spng_set_png_file(spng_ctx *ctx, FILE *file) +{ + if(file == NULL) return 1; + + if(ctx->encode_only) return spng_set_png_stream(ctx, file_write_fn, file); + + return spng_set_png_stream(ctx, file_read_fn, file); +} + +void *spng_get_png_buffer(spng_ctx *ctx, size_t *len, int *error) +{ + int tmp = 0; + error = error ? error : &tmp; + *error = 0; + + if(ctx == NULL || !len) *error = SPNG_EINVAL; + + if(*error) return NULL; + + if(!ctx->encode_only) *error = SPNG_ECTXTYPE; + else if(!ctx->state) *error = SPNG_EBADSTATE; + else if(!ctx->internal_buffer) *error = SPNG_EOPSTATE; + else if(ctx->state < SPNG_STATE_EOI) *error = SPNG_EOPSTATE; + else if(ctx->state != SPNG_STATE_IEND) *error = SPNG_ENOTFINAL; + + if(*error) return NULL; + + ctx->user_owns_out_png = 1; + + *len = ctx->bytes_encoded; + + return ctx->out_png; +} + +int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height) +{ + if(ctx == NULL) return 1; + + if(width > spng_u32max || height > spng_u32max) return 1; + + ctx->max_width = width; + ctx->max_height = height; + + return 0; +} + +int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height) +{ + if(ctx == NULL || width == NULL || height == NULL) return 1; + + *width = ctx->max_width; + *height = ctx->max_height; + + return 0; +} + +int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_limit) +{ + if(ctx == NULL || chunk_size > spng_u32max || chunk_size > cache_limit) return 1; + + ctx->max_chunk_size = chunk_size; + + ctx->chunk_cache_limit = cache_limit; + + return 0; +} + +int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_limit) +{ + if(ctx == NULL || chunk_size == NULL || cache_limit == NULL) return 1; + + *chunk_size = ctx->max_chunk_size; + + *cache_limit = ctx->chunk_cache_limit; + + return 0; +} + +int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary) +{ + if(ctx == NULL) return 1; + if(ctx->encode_only) return SPNG_ECTXTYPE; + + if(critical > 2 || critical < 0) return 1; + if(ancillary > 2 || ancillary < 0) return 1; + + if(critical == SPNG_CRC_DISCARD) return 1; + + ctx->crc_action_critical = critical; + ctx->crc_action_ancillary = ancillary; + + return 0; +} + +int spng_set_option(spng_ctx *ctx, enum spng_option option, int value) +{ + if(ctx == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + + switch(option) + { + case SPNG_KEEP_UNKNOWN_CHUNKS: + { + ctx->keep_unknown = value ? 1 : 0; + break; + } + case SPNG_IMG_COMPRESSION_LEVEL: + { + ctx->image_options.compression_level = value; + break; + } + case SPNG_IMG_WINDOW_BITS: + { + ctx->image_options.window_bits = value; + break; + } + case SPNG_IMG_MEM_LEVEL: + { + ctx->image_options.mem_level = value; + break; + } + case SPNG_IMG_COMPRESSION_STRATEGY: + { + ctx->image_options.strategy = value; + break; + } + case SPNG_TEXT_COMPRESSION_LEVEL: + { + ctx->text_options.compression_level = value; + break; + } + case SPNG_TEXT_WINDOW_BITS: + { + ctx->text_options.window_bits = value; + break; + } + case SPNG_TEXT_MEM_LEVEL: + { + ctx->text_options.mem_level = value; + break; + } + case SPNG_TEXT_COMPRESSION_STRATEGY: + { + ctx->text_options.strategy = value; + break; + } + case SPNG_FILTER_CHOICE: + { + if(value & ~SPNG_FILTER_CHOICE_ALL) return 1; + ctx->encode_flags.filter_choice = value; + break; + } + case SPNG_CHUNK_COUNT_LIMIT: + { + if(value < 0) return 1; + if(value > (int)ctx->chunk_count_total) return 1; + ctx->chunk_count_limit = value; + break; + } + case SPNG_ENCODE_TO_BUFFER: + { + if(value < 0) return 1; + if(!ctx->encode_only) return SPNG_ECTXTYPE; + if(ctx->state >= SPNG_STATE_OUTPUT) return SPNG_EOPSTATE; + + if(!value) break; + + ctx->internal_buffer = 1; + ctx->state = SPNG_STATE_OUTPUT; + + break; + } + default: return 1; + } + + /* Option can no longer be overriden by the library */ + if(option < 32) ctx->optimize_option &= ~(1 << option); + + return 0; +} + +int spng_get_option(spng_ctx *ctx, enum spng_option option, int *value) +{ + if(ctx == NULL || value == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + + switch(option) + { + case SPNG_KEEP_UNKNOWN_CHUNKS: + { + *value = ctx->keep_unknown; + break; + } + case SPNG_IMG_COMPRESSION_LEVEL: + { + *value = ctx->image_options.compression_level; + break; + } + case SPNG_IMG_WINDOW_BITS: + { + *value = ctx->image_options.window_bits; + break; + } + case SPNG_IMG_MEM_LEVEL: + { + *value = ctx->image_options.mem_level; + break; + } + case SPNG_IMG_COMPRESSION_STRATEGY: + { + *value = ctx->image_options.strategy; + break; + } + case SPNG_TEXT_COMPRESSION_LEVEL: + { + *value = ctx->text_options.compression_level; + break; + } + case SPNG_TEXT_WINDOW_BITS: + { + *value = ctx->text_options.window_bits; + break; + } + case SPNG_TEXT_MEM_LEVEL: + { + *value = ctx->text_options.mem_level; + break; + } + case SPNG_TEXT_COMPRESSION_STRATEGY: + { + *value = ctx->text_options.strategy; + break; + } + case SPNG_FILTER_CHOICE: + { + *value = ctx->encode_flags.filter_choice; + break; + } + case SPNG_CHUNK_COUNT_LIMIT: + { + *value = ctx->chunk_count_limit; + break; + } + case SPNG_ENCODE_TO_BUFFER: + { + if(ctx->internal_buffer) *value = 1; + else *value = 0; + + break; + } + default: return 1; + } + + return 0; +} + +int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len) +{ + if(ctx == NULL || len == NULL) return 1; + + int ret = read_chunks(ctx, 1); + if(ret) return ret; + + ret = check_decode_fmt(&ctx->ihdr, fmt); + if(ret) return ret; + + return calculate_image_size(&ctx->ihdr, fmt, len); +} + +int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr) +{ + if(ctx == NULL) return 1; + int ret = read_chunks(ctx, 1); + if(ret) return ret; + if(ihdr == NULL) return 1; + + *ihdr = ctx->ihdr; + + return 0; +} + +int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte) +{ + SPNG_GET_CHUNK_BOILERPLATE(plte); + + *plte = ctx->plte; + + return 0; +} + +int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns) +{ + SPNG_GET_CHUNK_BOILERPLATE(trns); + + *trns = ctx->trns; + + return 0; +} + +int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm) +{ + SPNG_GET_CHUNK_BOILERPLATE(chrm); + + chrm->white_point_x = (double)ctx->chrm_int.white_point_x / 100000.0; + chrm->white_point_y = (double)ctx->chrm_int.white_point_y / 100000.0; + chrm->red_x = (double)ctx->chrm_int.red_x / 100000.0; + chrm->red_y = (double)ctx->chrm_int.red_y / 100000.0; + chrm->blue_y = (double)ctx->chrm_int.blue_y / 100000.0; + chrm->blue_x = (double)ctx->chrm_int.blue_x / 100000.0; + chrm->green_x = (double)ctx->chrm_int.green_x / 100000.0; + chrm->green_y = (double)ctx->chrm_int.green_y / 100000.0; + + return 0; +} + +int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm) +{ + SPNG_GET_CHUNK_BOILERPLATE(chrm); + + *chrm = ctx->chrm_int; + + return 0; +} + +int spng_get_gama(spng_ctx *ctx, double *gamma) +{ + double *gama = gamma; + SPNG_GET_CHUNK_BOILERPLATE(gama); + + *gama = (double)ctx->gama / 100000.0; + + return 0; +} + +int spng_get_gama_int(spng_ctx *ctx, uint32_t *gama_int) +{ + uint32_t *gama = gama_int; + SPNG_GET_CHUNK_BOILERPLATE(gama); + + *gama_int = ctx->gama; + + return 0; +} + +int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp) +{ + SPNG_GET_CHUNK_BOILERPLATE(iccp); + + *iccp = ctx->iccp; + + return 0; +} + +int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit) +{ + SPNG_GET_CHUNK_BOILERPLATE(sbit); + + *sbit = ctx->sbit; + + return 0; +} + +int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent) +{ + uint8_t *srgb = rendering_intent; + SPNG_GET_CHUNK_BOILERPLATE(srgb); + + *srgb = ctx->srgb_rendering_intent; + + return 0; +} + +int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text) +{ + if(ctx == NULL) return 1; + int ret = read_chunks(ctx, 0); + if(ret) return ret; + if(!ctx->stored.text) return SPNG_ECHUNKAVAIL; + if(n_text == NULL) return 1; + + if(text == NULL) + { + *n_text = ctx->n_text; + return 0; + } + + if(*n_text < ctx->n_text) return 1; + + uint32_t i; + for(i=0; i< ctx->n_text; i++) + { + text[i].type = ctx->text_list[i].type; + memcpy(&text[i].keyword, ctx->text_list[i].keyword, strlen(ctx->text_list[i].keyword) + 1); + text[i].compression_method = 0; + text[i].compression_flag = ctx->text_list[i].compression_flag; + text[i].language_tag = ctx->text_list[i].language_tag; + text[i].translated_keyword = ctx->text_list[i].translated_keyword; + text[i].length = ctx->text_list[i].text_length; + text[i].text = ctx->text_list[i].text; + } + + return ret; +} + +int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd) +{ + SPNG_GET_CHUNK_BOILERPLATE(bkgd); + + *bkgd = ctx->bkgd; + + return 0; +} + +int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist) +{ + SPNG_GET_CHUNK_BOILERPLATE(hist); + + *hist = ctx->hist; + + return 0; +} + +int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys) +{ + SPNG_GET_CHUNK_BOILERPLATE(phys); + + *phys = ctx->phys; + + return 0; +} + +int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt) +{ + if(ctx == NULL) return 1; + int ret = read_chunks(ctx, 0); + if(ret) return ret; + if(!ctx->stored.splt) return SPNG_ECHUNKAVAIL; + if(n_splt == NULL) return 1; + + if(splt == NULL) + { + *n_splt = ctx->n_splt; + return 0; + } + + if(*n_splt < ctx->n_splt) return 1; + + memcpy(splt, ctx->splt_list, ctx->n_splt * sizeof(struct spng_splt)); + + return 0; +} + +int spng_get_time(spng_ctx *ctx, struct spng_time *time) +{ + SPNG_GET_CHUNK_BOILERPLATE(time); + + *time = ctx->time; + + return 0; +} + +int spng_get_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t *n_chunks) +{ + if(ctx == NULL) return 1; + int ret = read_chunks(ctx, 0); + if(ret) return ret; + if(!ctx->stored.unknown) return SPNG_ECHUNKAVAIL; + if(n_chunks == NULL) return 1; + + if(chunks == NULL) + { + *n_chunks = ctx->n_chunks; + return 0; + } + + if(*n_chunks < ctx->n_chunks) return 1; + + memcpy(chunks, ctx->chunk_list, sizeof(struct spng_unknown_chunk)); + + return 0; +} + +int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs) +{ + SPNG_GET_CHUNK_BOILERPLATE(offs); + + *offs = ctx->offs; + + return 0; +} + +int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif) +{ + SPNG_GET_CHUNK_BOILERPLATE(exif); + + *exif = ctx->exif; + + return 0; +} + +int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr) +{ + SPNG_SET_CHUNK_BOILERPLATE(ihdr); + + if(ctx->stored.ihdr) return 1; + + ret = check_ihdr(ihdr, ctx->max_width, ctx->max_height); + if(ret) return ret; + + ctx->ihdr = *ihdr; + + ctx->stored.ihdr = 1; + ctx->user.ihdr = 1; + + return 0; +} + +int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte) +{ + SPNG_SET_CHUNK_BOILERPLATE(plte); + + if(!ctx->stored.ihdr) return 1; + + if(check_plte(plte, &ctx->ihdr)) return 1; + + ctx->plte.n_entries = plte->n_entries; + + memcpy(ctx->plte.entries, plte->entries, plte->n_entries * sizeof(struct spng_plte_entry)); + + ctx->stored.plte = 1; + ctx->user.plte = 1; + + return 0; +} + +int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns) +{ + SPNG_SET_CHUNK_BOILERPLATE(trns); + + if(!ctx->stored.ihdr) return SPNG_ENOIHDR; + + if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE) + { + ctx->trns.gray = trns->gray; + } + else if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR) + { + ctx->trns.red = trns->red; + ctx->trns.green = trns->green; + ctx->trns.blue = trns->blue; + } + else if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_INDEXED) + { + if(!ctx->stored.plte) return SPNG_ETRNS_NO_PLTE; + if(trns->n_type3_entries > ctx->plte.n_entries) return 1; + + ctx->trns.n_type3_entries = trns->n_type3_entries; + memcpy(ctx->trns.type3_alpha, trns->type3_alpha, trns->n_type3_entries); + } + else return SPNG_ETRNS_COLOR_TYPE; + + ctx->stored.trns = 1; + ctx->user.trns = 1; + + return 0; +} + +int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm) +{ + SPNG_SET_CHUNK_BOILERPLATE(chrm); + + struct spng_chrm_int chrm_int; + + chrm_int.white_point_x = (uint32_t)(chrm->white_point_x * 100000.0); + chrm_int.white_point_y = (uint32_t)(chrm->white_point_y * 100000.0); + chrm_int.red_x = (uint32_t)(chrm->red_x * 100000.0); + chrm_int.red_y = (uint32_t)(chrm->red_y * 100000.0); + chrm_int.green_x = (uint32_t)(chrm->green_x * 100000.0); + chrm_int.green_y = (uint32_t)(chrm->green_y * 100000.0); + chrm_int.blue_x = (uint32_t)(chrm->blue_x * 100000.0); + chrm_int.blue_y = (uint32_t)(chrm->blue_y * 100000.0); + + if(check_chrm_int(&chrm_int)) return SPNG_ECHRM; + + ctx->chrm_int = chrm_int; + + ctx->stored.chrm = 1; + ctx->user.chrm = 1; + + return 0; +} + +int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int) +{ + SPNG_SET_CHUNK_BOILERPLATE(chrm_int); + + if(check_chrm_int(chrm_int)) return SPNG_ECHRM; + + ctx->chrm_int = *chrm_int; + + ctx->stored.chrm = 1; + ctx->user.chrm = 1; + + return 0; +} + +int spng_set_gama(spng_ctx *ctx, double gamma) +{ + SPNG_SET_CHUNK_BOILERPLATE(ctx); + + uint32_t gama = gamma * 100000.0; + + if(!gama) return 1; + if(gama > spng_u32max) return 1; + + ctx->gama = gama; + + ctx->stored.gama = 1; + ctx->user.gama = 1; + + return 0; +} + +int spng_set_gama_int(spng_ctx *ctx, uint32_t gamma) +{ + SPNG_SET_CHUNK_BOILERPLATE(ctx); + + if(!gamma) return 1; + if(gamma > spng_u32max) return 1; + + ctx->gama = gamma; + + ctx->stored.gama = 1; + ctx->user.gama = 1; + + return 0; +} + +int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp) +{ + SPNG_SET_CHUNK_BOILERPLATE(iccp); + + if(check_png_keyword(iccp->profile_name)) return SPNG_EICCP_NAME; + if(!iccp->profile_len || iccp->profile_len > UINT_MAX) return 1; + + if(ctx->iccp.profile && !ctx->user.iccp) spng__free(ctx, ctx->iccp.profile); + + ctx->iccp = *iccp; + + ctx->stored.iccp = 1; + ctx->user.iccp = 1; + + return 0; +} + +int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit) +{ + SPNG_SET_CHUNK_BOILERPLATE(sbit); + + if(check_sbit(sbit, &ctx->ihdr)) return 1; + + if(!ctx->stored.ihdr) return 1; + + ctx->sbit = *sbit; + + ctx->stored.sbit = 1; + ctx->user.sbit = 1; + + return 0; +} + +int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent) +{ + SPNG_SET_CHUNK_BOILERPLATE(ctx); + + if(rendering_intent > 3) return 1; + + ctx->srgb_rendering_intent = rendering_intent; + + ctx->stored.srgb = 1; + ctx->user.srgb = 1; + + return 0; +} + +int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text) +{ + if(!n_text) return 1; + SPNG_SET_CHUNK_BOILERPLATE(text); + + uint32_t i; + for(i=0; i < n_text; i++) + { + if(check_png_keyword(text[i].keyword)) return SPNG_ETEXT_KEYWORD; + if(!text[i].length) return 1; + if(text[i].length > UINT_MAX) return 1; + if(text[i].text == NULL) return 1; + + if(text[i].type == SPNG_TEXT) + { + if(ctx->strict && check_png_text(text[i].text, text[i].length)) return 1; + } + else if(text[i].type == SPNG_ZTXT) + { + if(ctx->strict && check_png_text(text[i].text, text[i].length)) return 1; + + if(text[i].compression_method != 0) return SPNG_EZTXT_COMPRESSION_METHOD; + } + else if(text[i].type == SPNG_ITXT) + { + if(text[i].compression_flag > 1) return SPNG_EITXT_COMPRESSION_FLAG; + if(text[i].compression_method != 0) return SPNG_EITXT_COMPRESSION_METHOD; + if(text[i].language_tag == NULL) return SPNG_EITXT_LANG_TAG; + if(text[i].translated_keyword == NULL) return SPNG_EITXT_TRANSLATED_KEY; + } + else return 1; + + } + + struct spng_text2 *text_list = spng__calloc(ctx, sizeof(struct spng_text2), n_text); + + if(!text_list) return SPNG_EMEM; + + if(ctx->text_list != NULL) + { + for(i=0; i < ctx->n_text; i++) + { + if(ctx->user.text) break; + + spng__free(ctx, ctx->text_list[i].keyword); + if(ctx->text_list[i].compression_flag) spng__free(ctx, ctx->text_list[i].text); + } + spng__free(ctx, ctx->text_list); + } + + for(i=0; i < n_text; i++) + { + text_list[i].type = text[i].type; + /* Prevent issues with spng_text.keyword[80] going out of scope */ + text_list[i].keyword = text_list[i].user_keyword_storage; + memcpy(text_list[i].user_keyword_storage, text[i].keyword, strlen(text[i].keyword)); + text_list[i].text = text[i].text; + text_list[i].text_length = text[i].length; + + if(text[i].type == SPNG_ZTXT) + { + text_list[i].compression_flag = 1; + } + else if(text[i].type == SPNG_ITXT) + { + text_list[i].compression_flag = text[i].compression_flag; + text_list[i].language_tag = text[i].language_tag; + text_list[i].translated_keyword = text[i].translated_keyword; + } + } + + ctx->text_list = text_list; + ctx->n_text = n_text; + + ctx->stored.text = 1; + ctx->user.text = 1; + + return 0; +} + +int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd) +{ + SPNG_SET_CHUNK_BOILERPLATE(bkgd); + + if(!ctx->stored.ihdr) return 1; + + if(ctx->ihdr.color_type == 0 || ctx->ihdr.color_type == 4) + { + ctx->bkgd.gray = bkgd->gray; + } + else if(ctx->ihdr.color_type == 2 || ctx->ihdr.color_type == 6) + { + ctx->bkgd.red = bkgd->red; + ctx->bkgd.green = bkgd->green; + ctx->bkgd.blue = bkgd->blue; + } + else if(ctx->ihdr.color_type == 3) + { + if(!ctx->stored.plte) return SPNG_EBKGD_NO_PLTE; + if(bkgd->plte_index >= ctx->plte.n_entries) return SPNG_EBKGD_PLTE_IDX; + + ctx->bkgd.plte_index = bkgd->plte_index; + } + + ctx->stored.bkgd = 1; + ctx->user.bkgd = 1; + + return 0; +} + +int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist) +{ + SPNG_SET_CHUNK_BOILERPLATE(hist); + + if(!ctx->stored.plte) return SPNG_EHIST_NO_PLTE; + + ctx->hist = *hist; + + ctx->stored.hist = 1; + ctx->user.hist = 1; + + return 0; +} + +int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys) +{ + SPNG_SET_CHUNK_BOILERPLATE(phys); + + if(check_phys(phys)) return SPNG_EPHYS; + + ctx->phys = *phys; + + ctx->stored.phys = 1; + ctx->user.phys = 1; + + return 0; +} + +int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt) +{ + if(!n_splt) return 1; + SPNG_SET_CHUNK_BOILERPLATE(splt); + + uint32_t i; + for(i=0; i < n_splt; i++) + { + if(check_png_keyword(splt[i].name)) return SPNG_ESPLT_NAME; + if( !(splt[i].sample_depth == 8 || splt[i].sample_depth == 16) ) return SPNG_ESPLT_DEPTH; + } + + if(ctx->stored.splt && !ctx->user.splt) + { + for(i=0; i < ctx->n_splt; i++) + { + if(ctx->splt_list[i].entries != NULL) spng__free(ctx, ctx->splt_list[i].entries); + } + spng__free(ctx, ctx->splt_list); + } + + ctx->splt_list = splt; + ctx->n_splt = n_splt; + + ctx->stored.splt = 1; + ctx->user.splt = 1; + + return 0; +} + +int spng_set_time(spng_ctx *ctx, struct spng_time *time) +{ + SPNG_SET_CHUNK_BOILERPLATE(time); + + if(check_time(time)) return SPNG_ETIME; + + ctx->time = *time; + + ctx->stored.time = 1; + ctx->user.time = 1; + + return 0; +} + +int spng_set_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t n_chunks) +{ + if(!n_chunks) return 1; + SPNG_SET_CHUNK_BOILERPLATE(chunks); + + uint32_t i; + for(i=0; i < n_chunks; i++) + { + if(chunks[i].length > spng_u32max) return SPNG_ECHUNK_STDLEN; + if(chunks[i].length && chunks[i].data == NULL) return 1; + + switch(chunks[i].location) + { + case SPNG_AFTER_IHDR: + case SPNG_AFTER_PLTE: + case SPNG_AFTER_IDAT: + break; + default: return SPNG_ECHUNK_POS; + } + } + + if(ctx->stored.unknown && !ctx->user.unknown) + { + for(i=0; i < ctx->n_chunks; i++) + { + spng__free(ctx, ctx->chunk_list[i].data); + } + spng__free(ctx, ctx->chunk_list); + } + + ctx->chunk_list = chunks; + ctx->n_chunks = n_chunks; + + ctx->stored.unknown = 1; + ctx->user.unknown = 1; + + return 0; +} + +int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs) +{ + SPNG_SET_CHUNK_BOILERPLATE(offs); + + if(check_offs(offs)) return SPNG_EOFFS; + + ctx->offs = *offs; + + ctx->stored.offs = 1; + ctx->user.offs = 1; + + return 0; +} + +int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif) +{ + SPNG_SET_CHUNK_BOILERPLATE(exif); + + if(check_exif(exif)) return SPNG_EEXIF; + + if(ctx->exif.data != NULL && !ctx->user.exif) spng__free(ctx, ctx->exif.data); + + ctx->exif = *exif; + + ctx->stored.exif = 1; + ctx->user.exif = 1; + + return 0; +} + +const char *spng_strerror(int err) +{ + switch(err) + { + case SPNG_IO_EOF: return "end of stream"; + case SPNG_IO_ERROR: return "stream error"; + case SPNG_OK: return "success"; + case SPNG_EINVAL: return "invalid argument"; + case SPNG_EMEM: return "out of memory"; + case SPNG_EOVERFLOW: return "arithmetic overflow"; + case SPNG_ESIGNATURE: return "invalid signature"; + case SPNG_EWIDTH: return "invalid image width"; + case SPNG_EHEIGHT: return "invalid image height"; + case SPNG_EUSER_WIDTH: return "image width exceeds user limit"; + case SPNG_EUSER_HEIGHT: return "image height exceeds user limit"; + case SPNG_EBIT_DEPTH: return "invalid bit depth"; + case SPNG_ECOLOR_TYPE: return "invalid color type"; + case SPNG_ECOMPRESSION_METHOD: return "invalid compression method"; + case SPNG_EFILTER_METHOD: return "invalid filter method"; + case SPNG_EINTERLACE_METHOD: return "invalid interlace method"; + case SPNG_EIHDR_SIZE: return "invalid IHDR chunk size"; + case SPNG_ENOIHDR: return "missing IHDR chunk"; + case SPNG_ECHUNK_POS: return "invalid chunk position"; + case SPNG_ECHUNK_SIZE: return "invalid chunk length"; + case SPNG_ECHUNK_CRC: return "invalid chunk checksum"; + case SPNG_ECHUNK_TYPE: return "invalid chunk type"; + case SPNG_ECHUNK_UNKNOWN_CRITICAL: return "unknown critical chunk"; + case SPNG_EDUP_PLTE: return "duplicate PLTE chunk"; + case SPNG_EDUP_CHRM: return "duplicate cHRM chunk"; + case SPNG_EDUP_GAMA: return "duplicate gAMA chunk"; + case SPNG_EDUP_ICCP: return "duplicate iCCP chunk"; + case SPNG_EDUP_SBIT: return "duplicate sBIT chunk"; + case SPNG_EDUP_SRGB: return "duplicate sRGB chunk"; + case SPNG_EDUP_BKGD: return "duplicate bKGD chunk"; + case SPNG_EDUP_HIST: return "duplicate hIST chunk"; + case SPNG_EDUP_TRNS: return "duplicate tRNS chunk"; + case SPNG_EDUP_PHYS: return "duplicate pHYs chunk"; + case SPNG_EDUP_TIME: return "duplicate tIME chunk"; + case SPNG_EDUP_OFFS: return "duplicate oFFs chunk"; + case SPNG_EDUP_EXIF: return "duplicate eXIf chunk"; + case SPNG_ECHRM: return "invalid cHRM chunk"; + case SPNG_EPLTE_IDX: return "invalid palette (PLTE) index"; + case SPNG_ETRNS_COLOR_TYPE: return "tRNS chunk with incompatible color type"; + case SPNG_ETRNS_NO_PLTE: return "missing palette (PLTE) for tRNS chunk"; + case SPNG_EGAMA: return "invalid gAMA chunk"; + case SPNG_EICCP_NAME: return "invalid iCCP profile name"; + case SPNG_EICCP_COMPRESSION_METHOD: return "invalid iCCP compression method"; + case SPNG_ESBIT: return "invalid sBIT chunk"; + case SPNG_ESRGB: return "invalid sRGB chunk"; + case SPNG_ETEXT: return "invalid tEXt chunk"; + case SPNG_ETEXT_KEYWORD: return "invalid tEXt keyword"; + case SPNG_EZTXT: return "invalid zTXt chunk"; + case SPNG_EZTXT_COMPRESSION_METHOD: return "invalid zTXt compression method"; + case SPNG_EITXT: return "invalid iTXt chunk"; + case SPNG_EITXT_COMPRESSION_FLAG: return "invalid iTXt compression flag"; + case SPNG_EITXT_COMPRESSION_METHOD: return "invalid iTXt compression method"; + case SPNG_EITXT_LANG_TAG: return "invalid iTXt language tag"; + case SPNG_EITXT_TRANSLATED_KEY: return "invalid iTXt translated key"; + case SPNG_EBKGD_NO_PLTE: return "missing palette for bKGD chunk"; + case SPNG_EBKGD_PLTE_IDX: return "invalid palette index for bKGD chunk"; + case SPNG_EHIST_NO_PLTE: return "missing palette for hIST chunk"; + case SPNG_EPHYS: return "invalid pHYs chunk"; + case SPNG_ESPLT_NAME: return "invalid suggested palette name"; + case SPNG_ESPLT_DUP_NAME: return "duplicate suggested palette (sPLT) name"; + case SPNG_ESPLT_DEPTH: return "invalid suggested palette (sPLT) sample depth"; + case SPNG_ETIME: return "invalid tIME chunk"; + case SPNG_EOFFS: return "invalid oFFs chunk"; + case SPNG_EEXIF: return "invalid eXIf chunk"; + case SPNG_EIDAT_TOO_SHORT: return "IDAT stream too short"; + case SPNG_EIDAT_STREAM: return "IDAT stream error"; + case SPNG_EZLIB: return "zlib error"; + case SPNG_EFILTER: return "invalid scanline filter"; + case SPNG_EBUFSIZ: return "invalid buffer size"; + case SPNG_EIO: return "i/o error"; + case SPNG_EOF: return "end of file"; + case SPNG_EBUF_SET: return "buffer already set"; + case SPNG_EBADSTATE: return "non-recoverable state"; + case SPNG_EFMT: return "invalid format"; + case SPNG_EFLAGS: return "invalid flags"; + case SPNG_ECHUNKAVAIL: return "chunk not available"; + case SPNG_ENCODE_ONLY: return "encode only context"; + case SPNG_EOI: return "reached end-of-image state"; + case SPNG_ENOPLTE: return "missing PLTE for indexed image"; + case SPNG_ECHUNK_LIMITS: return "reached chunk/cache limits"; + case SPNG_EZLIB_INIT: return "zlib init error"; + case SPNG_ECHUNK_STDLEN: return "chunk exceeds maximum standard length"; + case SPNG_EINTERNAL: return "internal error"; + case SPNG_ECTXTYPE: return "invalid operation for context type"; + case SPNG_ENOSRC: return "source PNG not set"; + case SPNG_ENODST: return "PNG output not set"; + case SPNG_EOPSTATE: return "invalid operation for state"; + case SPNG_ENOTFINAL: return "PNG not finalized"; + default: return "unknown error"; + } +} + +const char *spng_version_string(void) +{ + return SPNG_VERSION_STRING; +} + +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + +/* The following SIMD optimizations are derived from libpng source code. */ + +/* +* PNG Reference Library License version 2 +* +* Copyright (c) 1995-2019 The PNG Reference Library Authors. +* Copyright (c) 2018-2019 Cosmin Truta. +* Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. +* Copyright (c) 1996-1997 Andreas Dilger. +* Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. +* +* The software is supplied "as is", without warranty of any kind, +* express or implied, including, without limitation, the warranties +* of merchantability, fitness for a particular purpose, title, and +* non-infringement. In no event shall the Copyright owners, or +* anyone distributing the software, be liable for any damages or +* other liability, whether in contract, tort or otherwise, arising +* from, out of, or in connection with the software, or the use or +* other dealings in the software, even if advised of the possibility +* of such damage. +* +* Permission is hereby granted to use, copy, modify, and distribute +* this software, or portions hereof, for any purpose, without fee, +* 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 Copyright notice may not be removed or altered from any +* source or altered source distribution. +*/ + +#if defined(SPNG_X86) + +#ifndef SPNG_SSE + #define SPNG_SSE 1 +#endif + +#if defined(__GNUC__) && !defined(__clang__) + #if SPNG_SSE == 3 + #pragma GCC target("ssse3") + #elif SPNG_SSE == 4 + #pragma GCC target("sse4.1") + #else + #pragma GCC target("sse2") + #endif +#endif + +/* SSE2 optimised filter functions + * Derived from filter_neon_intrinsics.c + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 2016-2017 Glenn Randers-Pehrson + * Written by Mike Klein and Matt Sarett + * Derived from arm/filter_neon_intrinsics.c + * + * This code is derived from libpng source code. + * For conditions of distribution and use, see the disclaimer + * and license above. + */ + +#include +#include +#include + +/* Functions in this file look at most 3 pixels (a,b,c) to predict the 4th (d). + * They're positioned like this: + * prev: c b + * row: a d + * The Sub filter predicts d=a, Avg d=(a+b)/2, and Paeth predicts d to be + * whichever of a, b, or c is closest to p=a+b-c. + */ + +static __m128i load4(const void* p) +{ + int tmp; + memcpy(&tmp, p, sizeof(tmp)); + return _mm_cvtsi32_si128(tmp); +} + +static void store4(void* p, __m128i v) +{ + int tmp = _mm_cvtsi128_si32(v); + memcpy(p, &tmp, sizeof(int)); +} + +static __m128i load3(const void* p) +{ + uint32_t tmp = 0; + memcpy(&tmp, p, 3); + return _mm_cvtsi32_si128(tmp); +} + +static void store3(void* p, __m128i v) +{ + int tmp = _mm_cvtsi128_si32(v); + memcpy(p, &tmp, 3); +} + +static void defilter_sub3(size_t rowbytes, unsigned char *row) +{ + /* The Sub filter predicts each pixel as the previous pixel, a. + * There is no pixel to the left of the first pixel. It's encoded directly. + * That works with our main loop if we just say that left pixel was zero. + */ + size_t rb = rowbytes; + + __m128i a, d = _mm_setzero_si128(); + + while(rb >= 4) + { + a = d; d = load4(row); + d = _mm_add_epi8(d, a); + store3(row, d); + + row += 3; + rb -= 3; + } + + if(rb > 0) + { + a = d; d = load3(row); + d = _mm_add_epi8(d, a); + store3(row, d); + } +} + +static void defilter_sub4(size_t rowbytes, unsigned char *row) +{ + /* The Sub filter predicts each pixel as the previous pixel, a. + * There is no pixel to the left of the first pixel. It's encoded directly. + * That works with our main loop if we just say that left pixel was zero. + */ + size_t rb = rowbytes+4; + + __m128i a, d = _mm_setzero_si128(); + + while(rb > 4) + { + a = d; d = load4(row); + d = _mm_add_epi8(d, a); + store4(row, d); + + row += 4; + rb -= 4; + } +} + +static void defilter_avg3(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* The Avg filter predicts each pixel as the (truncated) average of a and b. + * There's no pixel to the left of the first pixel. Luckily, it's + * predicted to be half of the pixel above it. So again, this works + * perfectly with our loop if we make sure a starts at zero. + */ + + size_t rb = rowbytes; + + const __m128i zero = _mm_setzero_si128(); + + __m128i b; + __m128i a, d = zero; + + while(rb >= 4) + { + __m128i avg; + b = load4(prev); + a = d; d = load4(row ); + + /* PNG requires a truncating average, so we can't just use _mm_avg_epu8 */ + avg = _mm_avg_epu8(a,b); + /* ...but we can fix it up by subtracting off 1 if it rounded up. */ + avg = _mm_sub_epi8(avg, _mm_and_si128(_mm_xor_si128(a, b), + _mm_set1_epi8(1))); + d = _mm_add_epi8(d, avg); + store3(row, d); + + prev += 3; + row += 3; + rb -= 3; + } + + if(rb > 0) + { + __m128i avg; + b = load3(prev); + a = d; d = load3(row ); + + /* PNG requires a truncating average, so we can't just use _mm_avg_epu8 */ + avg = _mm_avg_epu8(a, b); + /* ...but we can fix it up by subtracting off 1 if it rounded up. */ + avg = _mm_sub_epi8(avg, _mm_and_si128(_mm_xor_si128(a, b), + _mm_set1_epi8(1))); + + d = _mm_add_epi8(d, avg); + store3(row, d); + } +} + +static void defilter_avg4(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* The Avg filter predicts each pixel as the (truncated) average of a and b. + * There's no pixel to the left of the first pixel. Luckily, it's + * predicted to be half of the pixel above it. So again, this works + * perfectly with our loop if we make sure a starts at zero. + */ + size_t rb = rowbytes+4; + + const __m128i zero = _mm_setzero_si128(); + __m128i b; + __m128i a, d = zero; + + while(rb > 4) + { + __m128i avg; + b = load4(prev); + a = d; d = load4(row ); + + /* PNG requires a truncating average, so we can't just use _mm_avg_epu8 */ + avg = _mm_avg_epu8(a,b); + /* ...but we can fix it up by subtracting off 1 if it rounded up. */ + avg = _mm_sub_epi8(avg, _mm_and_si128(_mm_xor_si128(a, b), + _mm_set1_epi8(1))); + + d = _mm_add_epi8(d, avg); + store4(row, d); + + prev += 4; + row += 4; + rb -= 4; + } +} + +/* Returns |x| for 16-bit lanes. */ +#if (SPNG_SSE >= 3) && !defined(_MSC_VER) +__attribute__((target("ssse3"))) +#endif +static __m128i abs_i16(__m128i x) +{ +#if SPNG_SSE >= 3 + return _mm_abs_epi16(x); +#else + /* Read this all as, return x<0 ? -x : x. + * To negate two's complement, you flip all the bits then add 1. + */ + __m128i is_negative = _mm_cmplt_epi16(x, _mm_setzero_si128()); + + /* Flip negative lanes. */ + x = _mm_xor_si128(x, is_negative); + + /* +1 to negative lanes, else +0. */ + x = _mm_sub_epi16(x, is_negative); + return x; +#endif +} + +/* Bytewise c ? t : e. */ +static __m128i if_then_else(__m128i c, __m128i t, __m128i e) +{ +#if SPNG_SSE >= 4 + return _mm_blendv_epi8(e, t, c); +#else + return _mm_or_si128(_mm_and_si128(c, t), _mm_andnot_si128(c, e)); +#endif +} + +static void defilter_paeth3(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* Paeth tries to predict pixel d using the pixel to the left of it, a, + * and two pixels from the previous row, b and c: + * prev: c b + * row: a d + * The Paeth function predicts d to be whichever of a, b, or c is nearest to + * p=a+b-c. + * + * The first pixel has no left context, and so uses an Up filter, p = b. + * This works naturally with our main loop's p = a+b-c if we force a and c + * to zero. + * Here we zero b and d, which become c and a respectively at the start of + * the loop. + */ + size_t rb = rowbytes; + const __m128i zero = _mm_setzero_si128(); + __m128i c, b = zero, + a, d = zero; + + while(rb >= 4) + { + /* It's easiest to do this math (particularly, deal with pc) with 16-bit + * intermediates. + */ + __m128i pa,pb,pc,smallest,nearest; + c = b; b = _mm_unpacklo_epi8(load4(prev), zero); + a = d; d = _mm_unpacklo_epi8(load4(row ), zero); + + /* (p-a) == (a+b-c - a) == (b-c) */ + + pa = _mm_sub_epi16(b, c); + + /* (p-b) == (a+b-c - b) == (a-c) */ + pb = _mm_sub_epi16(a, c); + + /* (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) */ + pc = _mm_add_epi16(pa, pb); + + pa = abs_i16(pa); /* |p-a| */ + pb = abs_i16(pb); /* |p-b| */ + pc = abs_i16(pc); /* |p-c| */ + + smallest = _mm_min_epi16(pc, _mm_min_epi16(pa, pb)); + + /* Paeth breaks ties favoring a over b over c. */ + nearest = if_then_else(_mm_cmpeq_epi16(smallest, pa), a, + if_then_else(_mm_cmpeq_epi16(smallest, pb), b, c)); + + /* Note `_epi8`: we need addition to wrap modulo 255. */ + d = _mm_add_epi8(d, nearest); + store3(row, _mm_packus_epi16(d, d)); + + prev += 3; + row += 3; + rb -= 3; + } + + if(rb > 0) + { + /* It's easiest to do this math (particularly, deal with pc) with 16-bit + * intermediates. + */ + __m128i pa, pb, pc, smallest, nearest; + c = b; b = _mm_unpacklo_epi8(load3(prev), zero); + a = d; d = _mm_unpacklo_epi8(load3(row ), zero); + + /* (p-a) == (a+b-c - a) == (b-c) */ + pa = _mm_sub_epi16(b, c); + + /* (p-b) == (a+b-c - b) == (a-c) */ + pb = _mm_sub_epi16(a, c); + + /* (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) */ + pc = _mm_add_epi16(pa, pb); + + pa = abs_i16(pa); /* |p-a| */ + pb = abs_i16(pb); /* |p-b| */ + pc = abs_i16(pc); /* |p-c| */ + + smallest = _mm_min_epi16(pc, _mm_min_epi16(pa, pb)); + + /* Paeth breaks ties favoring a over b over c. */ + nearest = if_then_else(_mm_cmpeq_epi16(smallest, pa), a, + if_then_else(_mm_cmpeq_epi16(smallest, pb), b, c)); + + /* Note `_epi8`: we need addition to wrap modulo 255. */ + d = _mm_add_epi8(d, nearest); + store3(row, _mm_packus_epi16(d, d)); + } +} + +static void defilter_paeth4(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* Paeth tries to predict pixel d using the pixel to the left of it, a, + * and two pixels from the previous row, b and c: + * prev: c b + * row: a d + * The Paeth function predicts d to be whichever of a, b, or c is nearest to + * p=a+b-c. + * + * The first pixel has no left context, and so uses an Up filter, p = b. + * This works naturally with our main loop's p = a+b-c if we force a and c + * to zero. + * Here we zero b and d, which become c and a respectively at the start of + * the loop. + */ + size_t rb = rowbytes+4; + + const __m128i zero = _mm_setzero_si128(); + __m128i pa, pb, pc, smallest, nearest; + __m128i c, b = zero, + a, d = zero; + + while(rb > 4) + { + /* It's easiest to do this math (particularly, deal with pc) with 16-bit + * intermediates. + */ + c = b; b = _mm_unpacklo_epi8(load4(prev), zero); + a = d; d = _mm_unpacklo_epi8(load4(row ), zero); + + /* (p-a) == (a+b-c - a) == (b-c) */ + pa = _mm_sub_epi16(b, c); + + /* (p-b) == (a+b-c - b) == (a-c) */ + pb = _mm_sub_epi16(a, c); + + /* (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) */ + pc = _mm_add_epi16(pa, pb); + + pa = abs_i16(pa); /* |p-a| */ + pb = abs_i16(pb); /* |p-b| */ + pc = abs_i16(pc); /* |p-c| */ + + smallest = _mm_min_epi16(pc, _mm_min_epi16(pa, pb)); + + /* Paeth breaks ties favoring a over b over c. */ + nearest = if_then_else(_mm_cmpeq_epi16(smallest, pa), a, + if_then_else(_mm_cmpeq_epi16(smallest, pb), b, c)); + + /* Note `_epi8`: we need addition to wrap modulo 255. */ + d = _mm_add_epi8(d, nearest); + store4(row, _mm_packus_epi16(d, d)); + + prev += 4; + row += 4; + rb -= 4; + } +} + +#endif /* SPNG_X86 */ + + +#if defined(SPNG_ARM) + +/* NEON optimised filter functions + * Derived from filter_neon_intrinsics.c + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 2014,2016 Glenn Randers-Pehrson + * Written by James Yu , October 2013. + * Based on filter_neon.S, written by Mans Rullgard, 2011. + * + * This code is derived from libpng source code. + * For conditions of distribution and use, see the disclaimer + * and license in this file. + */ + +#define png_aligncast(type, value) ((void*)(value)) +#define png_aligncastconst(type, value) ((const void*)(value)) + +/* libpng row pointers are not necessarily aligned to any particular boundary, + * however this code will only work with appropriate alignment. mips/mips_init.c + * checks for this (and will not compile unless it is done). This code uses + * variants of png_aligncast to avoid compiler warnings. + */ +#define png_ptr(type,pointer) png_aligncast(type *,pointer) +#define png_ptrc(type,pointer) png_aligncastconst(const type *,pointer) + +/* The following relies on a variable 'temp_pointer' being declared with type + * 'type'. This is written this way just to hide the GCC strict aliasing + * warning; note that the code is safe because there never is an alias between + * the input and output pointers. + */ +#define png_ldr(type,pointer)\ + (temp_pointer = png_ptr(type,pointer), *temp_pointer) + + +#if defined(_MSC_VER) && !defined(__clang__) && defined(_M_ARM64) + #include +#else + #include +#endif + +static void defilter_sub3(size_t rowbytes, unsigned char *row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + + uint8x16_t vtmp = vld1q_u8(rp); + uint8x8x2_t *vrpt = png_ptr(uint8x8x2_t, &vtmp); + uint8x8x2_t vrp = *vrpt; + + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop;) + { + uint8x8_t vtmp1, vtmp2; + uint32x2_t *temp_pointer; + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 3); + vdest.val[0] = vadd_u8(vdest.val[3], vrp.val[0]); + vtmp2 = vext_u8(vrp.val[0], vrp.val[1], 6); + vdest.val[1] = vadd_u8(vdest.val[0], vtmp1); + + vtmp1 = vext_u8(vrp.val[1], vrp.val[1], 1); + vdest.val[2] = vadd_u8(vdest.val[1], vtmp2); + vdest.val[3] = vadd_u8(vdest.val[2], vtmp1); + + vtmp = vld1q_u8(rp + 12); + vrpt = png_ptr(uint8x8x2_t, &vtmp); + vrp = *vrpt; + + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[0]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[1]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[2]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[3]), 0); + rp += 3; + } +} + +static void defilter_sub4(size_t rowbytes, unsigned char *row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop; rp += 16) + { + uint32x2x4_t vtmp = vld4_u32(png_ptr(uint32_t,rp)); + uint8x8x4_t *vrpt = png_ptr(uint8x8x4_t,&vtmp); + uint8x8x4_t vrp = *vrpt; + uint32x2x4_t *temp_pointer; + uint32x2x4_t vdest_val; + + vdest.val[0] = vadd_u8(vdest.val[3], vrp.val[0]); + vdest.val[1] = vadd_u8(vdest.val[0], vrp.val[1]); + vdest.val[2] = vadd_u8(vdest.val[1], vrp.val[2]); + vdest.val[3] = vadd_u8(vdest.val[2], vrp.val[3]); + + vdest_val = png_ldr(uint32x2x4_t, &vdest); + vst4_lane_u32(png_ptr(uint32_t,rp), vdest_val, 0); + } +} + +static void defilter_avg3(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + const unsigned char *pp = prev_row; + unsigned char *rp_stop = row + rowbytes; + + uint8x16_t vtmp; + uint8x8x2_t *vrpt; + uint8x8x2_t vrp; + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + vtmp = vld1q_u8(rp); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + for (; rp < rp_stop; pp += 12) + { + uint8x8_t vtmp1, vtmp2, vtmp3; + + uint8x8x2_t *vppt; + uint8x8x2_t vpp; + + uint32x2_t *temp_pointer; + + vtmp = vld1q_u8(pp); + vppt = png_ptr(uint8x8x2_t,&vtmp); + vpp = *vppt; + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 3); + vdest.val[0] = vhadd_u8(vdest.val[3], vpp.val[0]); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + + vtmp2 = vext_u8(vpp.val[0], vpp.val[1], 3); + vtmp3 = vext_u8(vrp.val[0], vrp.val[1], 6); + vdest.val[1] = vhadd_u8(vdest.val[0], vtmp2); + vdest.val[1] = vadd_u8(vdest.val[1], vtmp1); + + vtmp2 = vext_u8(vpp.val[0], vpp.val[1], 6); + vtmp1 = vext_u8(vrp.val[1], vrp.val[1], 1); + + vtmp = vld1q_u8(rp + 12); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + vdest.val[2] = vhadd_u8(vdest.val[1], vtmp2); + vdest.val[2] = vadd_u8(vdest.val[2], vtmp3); + + vtmp2 = vext_u8(vpp.val[1], vpp.val[1], 1); + + vdest.val[3] = vhadd_u8(vdest.val[2], vtmp2); + vdest.val[3] = vadd_u8(vdest.val[3], vtmp1); + + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[0]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[1]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[2]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[3]), 0); + rp += 3; + } +} + +static void defilter_avg4(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + const unsigned char *pp = prev_row; + + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop; rp += 16, pp += 16) + { + uint32x2x4_t vtmp; + uint8x8x4_t *vrpt, *vppt; + uint8x8x4_t vrp, vpp; + uint32x2x4_t *temp_pointer; + uint32x2x4_t vdest_val; + + vtmp = vld4_u32(png_ptr(uint32_t,rp)); + vrpt = png_ptr(uint8x8x4_t,&vtmp); + vrp = *vrpt; + vtmp = vld4_u32(png_ptrc(uint32_t,pp)); + vppt = png_ptr(uint8x8x4_t,&vtmp); + vpp = *vppt; + + vdest.val[0] = vhadd_u8(vdest.val[3], vpp.val[0]); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + vdest.val[1] = vhadd_u8(vdest.val[0], vpp.val[1]); + vdest.val[1] = vadd_u8(vdest.val[1], vrp.val[1]); + vdest.val[2] = vhadd_u8(vdest.val[1], vpp.val[2]); + vdest.val[2] = vadd_u8(vdest.val[2], vrp.val[2]); + vdest.val[3] = vhadd_u8(vdest.val[2], vpp.val[3]); + vdest.val[3] = vadd_u8(vdest.val[3], vrp.val[3]); + + vdest_val = png_ldr(uint32x2x4_t, &vdest); + vst4_lane_u32(png_ptr(uint32_t,rp), vdest_val, 0); + } +} + +static uint8x8_t paeth_arm(uint8x8_t a, uint8x8_t b, uint8x8_t c) +{ + uint8x8_t d, e; + uint16x8_t p1, pa, pb, pc; + + p1 = vaddl_u8(a, b); /* a + b */ + pc = vaddl_u8(c, c); /* c * 2 */ + pa = vabdl_u8(b, c); /* pa */ + pb = vabdl_u8(a, c); /* pb */ + pc = vabdq_u16(p1, pc); /* pc */ + + p1 = vcleq_u16(pa, pb); /* pa <= pb */ + pa = vcleq_u16(pa, pc); /* pa <= pc */ + pb = vcleq_u16(pb, pc); /* pb <= pc */ + + p1 = vandq_u16(p1, pa); /* pa <= pb && pa <= pc */ + + d = vmovn_u16(pb); + e = vmovn_u16(p1); + + d = vbsl_u8(d, b, c); + e = vbsl_u8(e, a, d); + + return e; +} + +static void defilter_paeth3(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + const unsigned char *pp = prev_row; + unsigned char *rp_stop = row + rowbytes; + + uint8x16_t vtmp; + uint8x8x2_t *vrpt; + uint8x8x2_t vrp; + uint8x8_t vlast = vdup_n_u8(0); + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + vtmp = vld1q_u8(rp); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + for (; rp < rp_stop; pp += 12) + { + uint8x8x2_t *vppt; + uint8x8x2_t vpp; + uint8x8_t vtmp1, vtmp2, vtmp3; + uint32x2_t *temp_pointer; + + vtmp = vld1q_u8(pp); + vppt = png_ptr(uint8x8x2_t,&vtmp); + vpp = *vppt; + + vdest.val[0] = paeth_arm(vdest.val[3], vpp.val[0], vlast); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 3); + vtmp2 = vext_u8(vpp.val[0], vpp.val[1], 3); + vdest.val[1] = paeth_arm(vdest.val[0], vtmp2, vpp.val[0]); + vdest.val[1] = vadd_u8(vdest.val[1], vtmp1); + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 6); + vtmp3 = vext_u8(vpp.val[0], vpp.val[1], 6); + vdest.val[2] = paeth_arm(vdest.val[1], vtmp3, vtmp2); + vdest.val[2] = vadd_u8(vdest.val[2], vtmp1); + + vtmp1 = vext_u8(vrp.val[1], vrp.val[1], 1); + vtmp2 = vext_u8(vpp.val[1], vpp.val[1], 1); + + vtmp = vld1q_u8(rp + 12); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + vdest.val[3] = paeth_arm(vdest.val[2], vtmp2, vtmp3); + vdest.val[3] = vadd_u8(vdest.val[3], vtmp1); + + vlast = vtmp2; + + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[0]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[1]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[2]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[3]), 0); + rp += 3; + } +} + +static void defilter_paeth4(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + const unsigned char *pp = prev_row; + + uint8x8_t vlast = vdup_n_u8(0); + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop; rp += 16, pp += 16) + { + uint32x2x4_t vtmp; + uint8x8x4_t *vrpt, *vppt; + uint8x8x4_t vrp, vpp; + uint32x2x4_t *temp_pointer; + uint32x2x4_t vdest_val; + + vtmp = vld4_u32(png_ptr(uint32_t,rp)); + vrpt = png_ptr(uint8x8x4_t,&vtmp); + vrp = *vrpt; + vtmp = vld4_u32(png_ptrc(uint32_t,pp)); + vppt = png_ptr(uint8x8x4_t,&vtmp); + vpp = *vppt; + + vdest.val[0] = paeth_arm(vdest.val[3], vpp.val[0], vlast); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + vdest.val[1] = paeth_arm(vdest.val[0], vpp.val[1], vpp.val[0]); + vdest.val[1] = vadd_u8(vdest.val[1], vrp.val[1]); + vdest.val[2] = paeth_arm(vdest.val[1], vpp.val[2], vpp.val[1]); + vdest.val[2] = vadd_u8(vdest.val[2], vrp.val[2]); + vdest.val[3] = paeth_arm(vdest.val[2], vpp.val[3], vpp.val[2]); + vdest.val[3] = vadd_u8(vdest.val[3], vrp.val[3]); + + vlast = vpp.val[3]; + + vdest_val = png_ldr(uint32x2x4_t, &vdest); + vst4_lane_u32(png_ptr(uint32_t,rp), vdest_val, 0); + } +} + +/* NEON optimised palette expansion functions + * Derived from palette_neon_intrinsics.c + * + * Copyright (c) 2018-2019 Cosmin Truta + * Copyright (c) 2017-2018 Arm Holdings. All rights reserved. + * Written by Richard Townsend , February 2017. + * + * This code is derived from libpng source code. + * For conditions of distribution and use, see the disclaimer + * and license in this file. + * + * Related: https://developer.arm.com/documentation/101964/latest/Color-palette-expansion + * + * The functions were refactored to iterate forward. + * + */ + +/* Expands a palettized row into RGBA8. */ +static uint32_t expand_palette_rgba8_neon(unsigned char *row, const unsigned char *scanline, const unsigned char *plte, uint32_t width) +{ + const uint32_t scanline_stride = 4; + const uint32_t row_stride = scanline_stride * 4; + const uint32_t count = width / scanline_stride; + const uint32_t *palette = (const uint32_t*)plte; + + if(!count) return 0; + + uint32_t i; + uint32x4_t cur; + for(i=0; i < count; i++, scanline += scanline_stride) + { + cur = vld1q_dup_u32 (palette + scanline[0]); + cur = vld1q_lane_u32(palette + scanline[1], cur, 1); + cur = vld1q_lane_u32(palette + scanline[2], cur, 2); + cur = vld1q_lane_u32(palette + scanline[3], cur, 3); + vst1q_u32((uint32_t*)(row + i * row_stride), cur); + } + + return count * scanline_stride; +} + +/* Expands a palettized row into RGB8. */ +static uint32_t expand_palette_rgb8_neon(unsigned char *row, const unsigned char *scanline, const unsigned char *plte, uint32_t width) +{ + const uint32_t scanline_stride = 8; + const uint32_t row_stride = scanline_stride * 3; + const uint32_t count = width / scanline_stride; + + if(!count) return 0; + + uint32_t i; + uint8x8x3_t cur; + for(i=0; i < count; i++, scanline += scanline_stride) + { + cur = vld3_dup_u8 (plte + 3 * scanline[0]); + cur = vld3_lane_u8(plte + 3 * scanline[1], cur, 1); + cur = vld3_lane_u8(plte + 3 * scanline[2], cur, 2); + cur = vld3_lane_u8(plte + 3 * scanline[3], cur, 3); + cur = vld3_lane_u8(plte + 3 * scanline[4], cur, 4); + cur = vld3_lane_u8(plte + 3 * scanline[5], cur, 5); + cur = vld3_lane_u8(plte + 3 * scanline[6], cur, 6); + cur = vld3_lane_u8(plte + 3 * scanline[7], cur, 7); + vst3_u8(row + i * row_stride, cur); + } + + return count * scanline_stride; +} + +#endif /* SPNG_ARM */ diff --git a/3rdparty/libspng/spng.h b/3rdparty/libspng/spng.h new file mode 100644 index 0000000000..5937d6c15d --- /dev/null +++ b/3rdparty/libspng/spng.h @@ -0,0 +1,537 @@ +/* SPDX-License-Identifier: (BSD-2-Clause AND libpng-2.0) */ +#ifndef SPNG_H +#define SPNG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(SPNG_STATIC) + #if defined(SPNG__BUILD) + #define SPNG_API __declspec(dllexport) + #else + #define SPNG_API __declspec(dllimport) + #endif +#else + #define SPNG_API +#endif + +#if defined(_MSC_VER) + #define SPNG_CDECL __cdecl +#else + #define SPNG_CDECL +#endif + +#include +#include +#include + +#define SPNG_VERSION_MAJOR 0 +#define SPNG_VERSION_MINOR 7 +#define SPNG_VERSION_PATCH 3 + +enum spng_errno +{ + SPNG_IO_ERROR = -2, + SPNG_IO_EOF = -1, + SPNG_OK = 0, + SPNG_EINVAL, + SPNG_EMEM, + SPNG_EOVERFLOW, + SPNG_ESIGNATURE, + SPNG_EWIDTH, + SPNG_EHEIGHT, + SPNG_EUSER_WIDTH, + SPNG_EUSER_HEIGHT, + SPNG_EBIT_DEPTH, + SPNG_ECOLOR_TYPE, + SPNG_ECOMPRESSION_METHOD, + SPNG_EFILTER_METHOD, + SPNG_EINTERLACE_METHOD, + SPNG_EIHDR_SIZE, + SPNG_ENOIHDR, + SPNG_ECHUNK_POS, + SPNG_ECHUNK_SIZE, + SPNG_ECHUNK_CRC, + SPNG_ECHUNK_TYPE, + SPNG_ECHUNK_UNKNOWN_CRITICAL, + SPNG_EDUP_PLTE, + SPNG_EDUP_CHRM, + SPNG_EDUP_GAMA, + SPNG_EDUP_ICCP, + SPNG_EDUP_SBIT, + SPNG_EDUP_SRGB, + SPNG_EDUP_BKGD, + SPNG_EDUP_HIST, + SPNG_EDUP_TRNS, + SPNG_EDUP_PHYS, + SPNG_EDUP_TIME, + SPNG_EDUP_OFFS, + SPNG_EDUP_EXIF, + SPNG_ECHRM, + SPNG_EPLTE_IDX, + SPNG_ETRNS_COLOR_TYPE, + SPNG_ETRNS_NO_PLTE, + SPNG_EGAMA, + SPNG_EICCP_NAME, + SPNG_EICCP_COMPRESSION_METHOD, + SPNG_ESBIT, + SPNG_ESRGB, + SPNG_ETEXT, + SPNG_ETEXT_KEYWORD, + SPNG_EZTXT, + SPNG_EZTXT_COMPRESSION_METHOD, + SPNG_EITXT, + SPNG_EITXT_COMPRESSION_FLAG, + SPNG_EITXT_COMPRESSION_METHOD, + SPNG_EITXT_LANG_TAG, + SPNG_EITXT_TRANSLATED_KEY, + SPNG_EBKGD_NO_PLTE, + SPNG_EBKGD_PLTE_IDX, + SPNG_EHIST_NO_PLTE, + SPNG_EPHYS, + SPNG_ESPLT_NAME, + SPNG_ESPLT_DUP_NAME, + SPNG_ESPLT_DEPTH, + SPNG_ETIME, + SPNG_EOFFS, + SPNG_EEXIF, + SPNG_EIDAT_TOO_SHORT, + SPNG_EIDAT_STREAM, + SPNG_EZLIB, + SPNG_EFILTER, + SPNG_EBUFSIZ, + SPNG_EIO, + SPNG_EOF, + SPNG_EBUF_SET, + SPNG_EBADSTATE, + SPNG_EFMT, + SPNG_EFLAGS, + SPNG_ECHUNKAVAIL, + SPNG_ENCODE_ONLY, + SPNG_EOI, + SPNG_ENOPLTE, + SPNG_ECHUNK_LIMITS, + SPNG_EZLIB_INIT, + SPNG_ECHUNK_STDLEN, + SPNG_EINTERNAL, + SPNG_ECTXTYPE, + SPNG_ENOSRC, + SPNG_ENODST, + SPNG_EOPSTATE, + SPNG_ENOTFINAL, +}; + +enum spng_text_type +{ + SPNG_TEXT = 1, + SPNG_ZTXT = 2, + SPNG_ITXT = 3 +}; + +enum spng_color_type +{ + SPNG_COLOR_TYPE_GRAYSCALE = 0, + SPNG_COLOR_TYPE_TRUECOLOR = 2, + SPNG_COLOR_TYPE_INDEXED = 3, + SPNG_COLOR_TYPE_GRAYSCALE_ALPHA = 4, + SPNG_COLOR_TYPE_TRUECOLOR_ALPHA = 6 +}; + +enum spng_filter +{ + SPNG_FILTER_NONE = 0, + SPNG_FILTER_SUB = 1, + SPNG_FILTER_UP = 2, + SPNG_FILTER_AVERAGE = 3, + SPNG_FILTER_PAETH = 4 +}; + +enum spng_filter_choice +{ + SPNG_DISABLE_FILTERING = 0, + SPNG_FILTER_CHOICE_NONE = 8, + SPNG_FILTER_CHOICE_SUB = 16, + SPNG_FILTER_CHOICE_UP = 32, + SPNG_FILTER_CHOICE_AVG = 64, + SPNG_FILTER_CHOICE_PAETH = 128, + SPNG_FILTER_CHOICE_ALL = (8|16|32|64|128) +}; + +enum spng_interlace_method +{ + SPNG_INTERLACE_NONE = 0, + SPNG_INTERLACE_ADAM7 = 1 +}; + +/* Channels are always in byte-order */ +enum spng_format +{ + SPNG_FMT_RGBA8 = 1, + SPNG_FMT_RGBA16 = 2, + SPNG_FMT_RGB8 = 4, + + /* Partially implemented, see documentation */ + SPNG_FMT_GA8 = 16, + SPNG_FMT_GA16 = 32, + SPNG_FMT_G8 = 64, + + /* No conversion or scaling */ + SPNG_FMT_PNG = 256, + SPNG_FMT_RAW = 512 /* big-endian (everything else is host-endian) */ +}; + +enum spng_ctx_flags +{ + SPNG_CTX_IGNORE_ADLER32 = 1, /* Ignore checksum in DEFLATE streams */ + SPNG_CTX_ENCODER = 2 /* Create an encoder context */ +}; + +enum spng_decode_flags +{ + SPNG_DECODE_USE_TRNS = 1, /* Deprecated */ + SPNG_DECODE_USE_GAMA = 2, /* Deprecated */ + SPNG_DECODE_USE_SBIT = 8, /* Undocumented */ + + SPNG_DECODE_TRNS = 1, /* Apply transparency */ + SPNG_DECODE_GAMMA = 2, /* Apply gamma correction */ + SPNG_DECODE_PROGRESSIVE = 256 /* Initialize for progressive reads */ +}; + +enum spng_crc_action +{ + /* Default for critical chunks */ + SPNG_CRC_ERROR = 0, + + /* Discard chunk, invalid for critical chunks. + Since v0.6.2: default for ancillary chunks */ + SPNG_CRC_DISCARD = 1, + + /* Ignore and don't calculate checksum. + Since v0.6.2: also ignores checksums in DEFLATE streams */ + SPNG_CRC_USE = 2 +}; + +enum spng_encode_flags +{ + SPNG_ENCODE_PROGRESSIVE = 1, /* Initialize for progressive writes */ + SPNG_ENCODE_FINALIZE = 2, /* Finalize PNG after encoding image */ +}; + +struct spng_ihdr +{ + uint32_t width; + uint32_t height; + uint8_t bit_depth; + uint8_t color_type; + uint8_t compression_method; + uint8_t filter_method; + uint8_t interlace_method; +}; + +struct spng_plte_entry +{ + uint8_t red; + uint8_t green; + uint8_t blue; + + uint8_t alpha; /* Reserved for internal use */ +}; + +struct spng_plte +{ + uint32_t n_entries; + struct spng_plte_entry entries[256]; +}; + +struct spng_trns +{ + uint16_t gray; + + uint16_t red; + uint16_t green; + uint16_t blue; + + uint32_t n_type3_entries; + uint8_t type3_alpha[256]; +}; + +struct spng_chrm_int +{ + uint32_t white_point_x; + uint32_t white_point_y; + uint32_t red_x; + uint32_t red_y; + uint32_t green_x; + uint32_t green_y; + uint32_t blue_x; + uint32_t blue_y; +}; + +struct spng_chrm +{ + double white_point_x; + double white_point_y; + double red_x; + double red_y; + double green_x; + double green_y; + double blue_x; + double blue_y; +}; + +struct spng_iccp +{ + char profile_name[80]; + size_t profile_len; + char *profile; +}; + +struct spng_sbit +{ + uint8_t grayscale_bits; + uint8_t red_bits; + uint8_t green_bits; + uint8_t blue_bits; + uint8_t alpha_bits; +}; + +struct spng_text +{ + char keyword[80]; + int type; + + size_t length; + char *text; + + uint8_t compression_flag; /* iTXt only */ + uint8_t compression_method; /* iTXt, ztXt only */ + char *language_tag; /* iTXt only */ + char *translated_keyword; /* iTXt only */ +}; + +struct spng_bkgd +{ + uint16_t gray; /* Only for gray/gray alpha */ + uint16_t red; + uint16_t green; + uint16_t blue; + uint16_t plte_index; /* Only for indexed color */ +}; + +struct spng_hist +{ + uint16_t frequency[256]; +}; + +struct spng_phys +{ + uint32_t ppu_x, ppu_y; + uint8_t unit_specifier; +}; + +struct spng_splt_entry +{ + uint16_t red; + uint16_t green; + uint16_t blue; + uint16_t alpha; + uint16_t frequency; +}; + +struct spng_splt +{ + char name[80]; + uint8_t sample_depth; + uint32_t n_entries; + struct spng_splt_entry *entries; +}; + +struct spng_time +{ + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +struct spng_offs +{ + int32_t x, y; + uint8_t unit_specifier; +}; + +struct spng_exif +{ + size_t length; + char *data; +}; + +struct spng_chunk +{ + size_t offset; + uint32_t length; + uint8_t type[4]; + uint32_t crc; +}; + +enum spng_location +{ + SPNG_AFTER_IHDR = 1, + SPNG_AFTER_PLTE = 2, + SPNG_AFTER_IDAT = 8, +}; + +struct spng_unknown_chunk +{ + uint8_t type[4]; + size_t length; + void *data; + enum spng_location location; +}; + +enum spng_option +{ + SPNG_KEEP_UNKNOWN_CHUNKS = 1, + + SPNG_IMG_COMPRESSION_LEVEL, + SPNG_IMG_WINDOW_BITS, + SPNG_IMG_MEM_LEVEL, + SPNG_IMG_COMPRESSION_STRATEGY, + + SPNG_TEXT_COMPRESSION_LEVEL, + SPNG_TEXT_WINDOW_BITS, + SPNG_TEXT_MEM_LEVEL, + SPNG_TEXT_COMPRESSION_STRATEGY, + + SPNG_FILTER_CHOICE, + SPNG_CHUNK_COUNT_LIMIT, + SPNG_ENCODE_TO_BUFFER, +}; + +typedef void* SPNG_CDECL spng_malloc_fn(size_t size); +typedef void* SPNG_CDECL spng_realloc_fn(void* ptr, size_t size); +typedef void* SPNG_CDECL spng_calloc_fn(size_t count, size_t size); +typedef void SPNG_CDECL spng_free_fn(void* ptr); + +struct spng_alloc +{ + spng_malloc_fn *malloc_fn; + spng_realloc_fn *realloc_fn; + spng_calloc_fn *calloc_fn; + spng_free_fn *free_fn; +}; + +struct spng_row_info +{ + uint32_t scanline_idx; + uint32_t row_num; /* deinterlaced row index */ + int pass; + uint8_t filter; +}; + +typedef struct spng_ctx spng_ctx; + +typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length); +typedef int spng_write_fn(spng_ctx *ctx, void *user, void *src, size_t length); + +typedef int spng_rw_fn(spng_ctx *ctx, void *user, void *dst_src, size_t length); + +SPNG_API spng_ctx *spng_ctx_new(int flags); +SPNG_API spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags); +SPNG_API void spng_ctx_free(spng_ctx *ctx); + +SPNG_API int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size); +SPNG_API int spng_set_png_stream(spng_ctx *ctx, spng_rw_fn *rw_func, void *user); +SPNG_API int spng_set_png_file(spng_ctx *ctx, FILE *file); + +SPNG_API void *spng_get_png_buffer(spng_ctx *ctx, size_t *len, int *error); + +SPNG_API int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height); +SPNG_API int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height); + +SPNG_API int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_size); +SPNG_API int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_size); + +SPNG_API int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary); + +SPNG_API int spng_set_option(spng_ctx *ctx, enum spng_option option, int value); +SPNG_API int spng_get_option(spng_ctx *ctx, enum spng_option option, int *value); + +SPNG_API int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len); + +/* Decode */ +SPNG_API int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags); + +/* Progressive decode */ +SPNG_API int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len); +SPNG_API int spng_decode_row(spng_ctx *ctx, void *out, size_t len); +SPNG_API int spng_decode_chunks(spng_ctx *ctx); + +/* Encode/decode */ +SPNG_API int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info); + +/* Encode */ +SPNG_API int spng_encode_image(spng_ctx *ctx, const void *img, size_t len, int fmt, int flags); + +/* Progressive encode */ +SPNG_API int spng_encode_scanline(spng_ctx *ctx, const void *scanline, size_t len); +SPNG_API int spng_encode_row(spng_ctx *ctx, const void *row, size_t len); +SPNG_API int spng_encode_chunks(spng_ctx *ctx); + +SPNG_API int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr); +SPNG_API int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte); +SPNG_API int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns); +SPNG_API int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm); +SPNG_API int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int); +SPNG_API int spng_get_gama(spng_ctx *ctx, double *gamma); +SPNG_API int spng_get_gama_int(spng_ctx *ctx, uint32_t *gama_int); +SPNG_API int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp); +SPNG_API int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit); +SPNG_API int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent); +SPNG_API int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text); +SPNG_API int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd); +SPNG_API int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist); +SPNG_API int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys); +SPNG_API int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt); +SPNG_API int spng_get_time(spng_ctx *ctx, struct spng_time *time); +SPNG_API int spng_get_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t *n_chunks); + +/* Official extensions */ +SPNG_API int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs); +SPNG_API int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif); + + +SPNG_API int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr); +SPNG_API int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte); +SPNG_API int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns); +SPNG_API int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm); +SPNG_API int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int); +SPNG_API int spng_set_gama(spng_ctx *ctx, double gamma); +SPNG_API int spng_set_gama_int(spng_ctx *ctx, uint32_t gamma); +SPNG_API int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp); +SPNG_API int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit); +SPNG_API int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent); +SPNG_API int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text); +SPNG_API int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd); +SPNG_API int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist); +SPNG_API int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys); +SPNG_API int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt); +SPNG_API int spng_set_time(spng_ctx *ctx, struct spng_time *time); +SPNG_API int spng_set_unknown_chunks(spng_ctx *ctx, struct spng_unknown_chunk *chunks, uint32_t n_chunks); + +/* Official extensions */ +SPNG_API int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs); +SPNG_API int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif); + + +SPNG_API const char *spng_strerror(int err); +SPNG_API const char *spng_version_string(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SPNG_H */ diff --git a/3rdparty/readme.txt b/3rdparty/readme.txt index e67304c5ef..c3068521e3 100644 --- a/3rdparty/readme.txt +++ b/3rdparty/readme.txt @@ -20,6 +20,7 @@ libjpeg-turbo libjpeg-turbo is covered by three compatible BSD-style ope WITH_JPEG CMake option must be ON to add libjpeg or libjpeg-turbo support to imgcodecs. BUILD_JPEG=ON selects libjpeg-turbo by default (since OpenCV 3.4.2). Enable BUILD_JPEG_TURBO_DISABLE=ON to force using of libjpeg (this option is removed in OpenCV 4.0). + SIMD instructions are enabled by default. Use ENABLE_LIBJPEG_TURBO_SIMD to control SIMD instructions. ------------------------------------------------------------------------------------ libpng Portable Network Graphics library. The license and copyright notes can be found in libpng/LICENSE. @@ -27,6 +28,13 @@ libpng Portable Network Graphics library. for details and links to the source code WITH_PNG CMake option must be ON to add libpng support to imgcodecs. + +libspng Portable Network Graphics library. + The license and copyright notes can be found in libspng/LICENSE. + See libspng home page https://www.libspng.org + for details and links to the source code + + WITH_SPNG CMake option must be ON to add libspng support to imgcodecs ------------------------------------------------------------------------------------ libtiff Tag Image File Format (TIFF) Software Copyright (c) 1988-1997 Sam Leffler diff --git a/3rdparty/zlib/CMakeLists.txt b/3rdparty/zlib/CMakeLists.txt index 709e293c28..addd3e5a14 100644 --- a/3rdparty/zlib/CMakeLists.txt +++ b/3rdparty/zlib/CMakeLists.txt @@ -102,4 +102,4 @@ if(NOT BUILD_SHARED_LIBS) ocv_install_target(${ZLIB_LIBRARY} EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev) endif() -ocv_install_3rdparty_licenses(zlib README) +ocv_install_3rdparty_licenses(zlib LICENSE) diff --git a/3rdparty/zlib/ChangeLog b/3rdparty/zlib/ChangeLog index f0b0e61809..457526bc6a 100644 --- a/3rdparty/zlib/ChangeLog +++ b/3rdparty/zlib/ChangeLog @@ -1,6 +1,18 @@ ChangeLog file for zlib +Changes in 1.2.13 (13 Oct 2022) +- Fix configure issue that discarded provided CC definition +- Correct incorrect inputs provided to the CRC functions +- Repair prototypes and exporting of new CRC functions +- Fix inflateBack to detect invalid input with distances too far +- Have infback() deliver all of the available output up to any error +- Fix a bug when getting a gzip header extra field with inflate() +- Fix bug in block type selection when Z_FIXED used +- Tighten deflateBound bounds +- Remove deleted assembler code references +- Various portability and appearance improvements + Changes in 1.2.12 (27 Mar 2022) - Cygwin does not have _wopen(), so do not create gzopen_w() there - Permit a deflateParams() parameter change as soon as possible @@ -159,7 +171,7 @@ Changes in 1.2.7.1 (24 Mar 2013) - Fix types in contrib/minizip to match result of get_crc_table() - Simplify contrib/vstudio/vc10 with 'd' suffix - Add TOP support to win32/Makefile.msc -- Suport i686 and amd64 assembler builds in CMakeLists.txt +- Support i686 and amd64 assembler builds in CMakeLists.txt - Fix typos in the use of _LARGEFILE64_SOURCE in zconf.h - Add vc11 and vc12 build files to contrib/vstudio - Add gzvprintf() as an undocumented function in zlib @@ -359,14 +371,14 @@ Changes in 1.2.5.1 (10 Sep 2011) - Use u4 type for crc_table to avoid conversion warnings - Apply casts in zlib.h to avoid conversion warnings - Add OF to prototypes for adler32_combine_ and crc32_combine_ [Miller] -- Improve inflateSync() documentation to note indeterminancy +- Improve inflateSync() documentation to note indeterminacy - Add deflatePending() function to return the amount of pending output - Correct the spelling of "specification" in FAQ [Randers-Pehrson] - Add a check in configure for stdarg.h, use for gzprintf() - Check that pointers fit in ints when gzprint() compiled old style - Add dummy name before $(SHAREDLIBV) in Makefile [Bar-Lev, Bowler] - Delete line in configure that adds -L. libz.a to LDFLAGS [Weigelt] -- Add debug records in assmebler code [Londer] +- Add debug records in assembler code [Londer] - Update RFC references to use http://tools.ietf.org/html/... [Li] - Add --archs option, use of libtool to configure for Mac OS X [Borstel] @@ -1033,7 +1045,7 @@ Changes in 1.2.0.1 (17 March 2003) - Include additional header file on VMS for off_t typedef - Try to use _vsnprintf where it supplants vsprintf [Vollant] - Add some casts in inffast.c -- Enchance comments in zlib.h on what happens if gzprintf() tries to +- Enhance comments in zlib.h on what happens if gzprintf() tries to write more than 4095 bytes before compression - Remove unused state from inflateBackEnd() - Remove exit(0) from minigzip.c, example.c @@ -1211,7 +1223,7 @@ Changes in 1.0.9 (17 Feb 1998) - Avoid gcc 2.8.0 comparison bug a little differently than zlib 1.0.8 - in inftrees.c, avoid cc -O bug on HP (Farshid Elahi) - in zconf.h move the ZLIB_DLL stuff earlier to avoid problems with - the declaration of FAR (Gilles VOllant) + the declaration of FAR (Gilles Vollant) - install libz.so* with mode 755 (executable) instead of 644 (Marc Lehmann) - read_buf buf parameter of type Bytef* instead of charf* - zmemcpy parameters are of type Bytef*, not charf* (Joseph Strout) @@ -1567,7 +1579,7 @@ Changes in 0.4: - renamed deflateOptions as deflateInit2, call one or the other but not both - added the method parameter for deflateInit2 - added inflateInit2 -- simplied considerably deflateInit and inflateInit by not supporting +- simplified considerably deflateInit and inflateInit by not supporting user-provided history buffer. This is supported only in deflateInit2 and inflateInit2 diff --git a/3rdparty/zlib/LICENSE b/3rdparty/zlib/LICENSE new file mode 100644 index 0000000000..ab8ee6f714 --- /dev/null +++ b/3rdparty/zlib/LICENSE @@ -0,0 +1,22 @@ +Copyright notice: + + (C) 1995-2022 Jean-loup Gailly and Mark Adler + + 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. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu diff --git a/3rdparty/zlib/README b/3rdparty/zlib/README index 024b79d3d8..ba34d1894a 100644 --- a/3rdparty/zlib/README +++ b/3rdparty/zlib/README @@ -1,6 +1,6 @@ ZLIB DATA COMPRESSION LIBRARY -zlib 1.2.12 is a general purpose data compression library. All the code is +zlib 1.2.13 is a general purpose data compression library. All the code is thread safe. The data format used by the zlib library is described by RFCs (Request for Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and @@ -31,7 +31,7 @@ Mark Nelson wrote an article about zlib for the Jan. 1997 issue of Dr. Dobb's Journal; a copy of the article is available at http://marknelson.us/1997/01/01/zlib-engine/ . -The changes made in version 1.2.12 are documented in the file ChangeLog. +The changes made in version 1.2.13 are documented in the file ChangeLog. Unsupported third party contributions are provided in directory contrib/ . diff --git a/3rdparty/zlib/compress.c b/3rdparty/zlib/compress.c index e2db404abf..2ad5326c14 100644 --- a/3rdparty/zlib/compress.c +++ b/3rdparty/zlib/compress.c @@ -19,7 +19,7 @@ memory, Z_BUF_ERROR if there was not enough room in the output buffer, Z_STREAM_ERROR if the level parameter is invalid. */ -int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) +int ZEXPORT compress2(dest, destLen, source, sourceLen, level) Bytef *dest; uLongf *destLen; const Bytef *source; @@ -65,7 +65,7 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) /* =========================================================================== */ -int ZEXPORT compress (dest, destLen, source, sourceLen) +int ZEXPORT compress(dest, destLen, source, sourceLen) Bytef *dest; uLongf *destLen; const Bytef *source; @@ -78,7 +78,7 @@ int ZEXPORT compress (dest, destLen, source, sourceLen) If the default memLevel or windowBits for deflateInit() is changed, then this function needs to be updated. */ -uLong ZEXPORT compressBound (sourceLen) +uLong ZEXPORT compressBound(sourceLen) uLong sourceLen; { return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + diff --git a/3rdparty/zlib/crc32.c b/3rdparty/zlib/crc32.c index a1bdce5c23..f8357b083f 100644 --- a/3rdparty/zlib/crc32.c +++ b/3rdparty/zlib/crc32.c @@ -98,13 +98,22 @@ # endif #endif +/* If available, use the ARM processor CRC32 instruction. */ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && W == 8 +# define ARMCRC32 +#endif + /* Local functions. */ local z_crc_t multmodp OF((z_crc_t a, z_crc_t b)); local z_crc_t x2nmodp OF((z_off64_t n, unsigned k)); -/* If available, use the ARM processor CRC32 instruction. */ -#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && W == 8 -# define ARMCRC32 +#if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE)) + local z_word_t byte_swap OF((z_word_t word)); +#endif + +#if defined(W) && !defined(ARMCRC32) + local z_crc_t crc_word OF((z_word_t data)); + local z_word_t crc_word_big OF((z_word_t data)); #endif #if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE)) @@ -630,7 +639,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) #endif /* DYNAMIC_CRC_TABLE */ /* Pre-condition the CRC */ - crc ^= 0xffffffff; + crc = (~crc) & 0xffffffff; /* Compute the CRC up to a word boundary. */ while (len && ((z_size_t)buf & 7) != 0) { @@ -645,8 +654,8 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) len &= 7; /* Do three interleaved CRCs to realize the throughput of one crc32x - instruction per cycle. Each CRC is calcuated on Z_BATCH words. The three - CRCs are combined into a single CRC after each set of batches. */ + instruction per cycle. Each CRC is calculated on Z_BATCH words. The + three CRCs are combined into a single CRC after each set of batches. */ while (num >= 3 * Z_BATCH) { crc1 = 0; crc2 = 0; @@ -749,7 +758,7 @@ unsigned long ZEXPORT crc32_z(crc, buf, len) #endif /* DYNAMIC_CRC_TABLE */ /* Pre-condition the CRC */ - crc ^= 0xffffffff; + crc = (~crc) & 0xffffffff; #ifdef W @@ -1077,7 +1086,7 @@ uLong ZEXPORT crc32_combine64(crc1, crc2, len2) #ifdef DYNAMIC_CRC_TABLE once(&made, make_crc_table); #endif /* DYNAMIC_CRC_TABLE */ - return multmodp(x2nmodp(len2, 3), crc1) ^ crc2; + return multmodp(x2nmodp(len2, 3), crc1) ^ (crc2 & 0xffffffff); } /* ========================================================================= */ @@ -1086,7 +1095,7 @@ uLong ZEXPORT crc32_combine(crc1, crc2, len2) uLong crc2; z_off_t len2; { - return crc32_combine64(crc1, crc2, len2); + return crc32_combine64(crc1, crc2, (z_off64_t)len2); } /* ========================================================================= */ @@ -1103,14 +1112,14 @@ uLong ZEXPORT crc32_combine_gen64(len2) uLong ZEXPORT crc32_combine_gen(len2) z_off_t len2; { - return crc32_combine_gen64(len2); + return crc32_combine_gen64((z_off64_t)len2); } /* ========================================================================= */ -uLong crc32_combine_op(crc1, crc2, op) +uLong ZEXPORT crc32_combine_op(crc1, crc2, op) uLong crc1; uLong crc2; uLong op; { - return multmodp(op, crc1) ^ crc2; + return multmodp(op, crc1) ^ (crc2 & 0xffffffff); } diff --git a/3rdparty/zlib/deflate.c b/3rdparty/zlib/deflate.c index 799fb93cc0..4a689db359 100644 --- a/3rdparty/zlib/deflate.c +++ b/3rdparty/zlib/deflate.c @@ -52,7 +52,7 @@ #include "deflate.h" const char deflate_copyright[] = - " deflate 1.2.12 Copyright 1995-2022 Jean-loup Gailly and Mark Adler "; + " deflate 1.2.13 Copyright 1995-2022 Jean-loup Gailly and Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -87,13 +87,7 @@ local void lm_init OF((deflate_state *s)); local void putShortMSB OF((deflate_state *s, uInt b)); local void flush_pending OF((z_streamp strm)); local unsigned read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); -#ifdef ASMV -# pragma message("Assembler code may have bugs -- use at your own risk") - void match_init OF((void)); /* asm code initialization */ - uInt longest_match OF((deflate_state *s, IPos cur_match)); -#else local uInt longest_match OF((deflate_state *s, IPos cur_match)); -#endif #ifdef ZLIB_DEBUG local void check_match OF((deflate_state *s, IPos start, IPos match, @@ -160,7 +154,7 @@ local const config configuration_table[10] = { * characters, so that a running hash key can be computed from the previous * key instead of complete recalculation each time. */ -#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) +#define UPDATE_HASH(s,h,c) (h = (((h) << s->hash_shift) ^ (c)) & s->hash_mask) /* =========================================================================== @@ -191,9 +185,9 @@ local const config configuration_table[10] = { */ #define CLEAR_HASH(s) \ do { \ - s->head[s->hash_size-1] = NIL; \ + s->head[s->hash_size - 1] = NIL; \ zmemzero((Bytef *)s->head, \ - (unsigned)(s->hash_size-1)*sizeof(*s->head)); \ + (unsigned)(s->hash_size - 1)*sizeof(*s->head)); \ } while (0) /* =========================================================================== @@ -285,6 +279,8 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (windowBits < 0) { /* suppress zlib wrapper */ wrap = 0; + if (windowBits < -15) + return Z_STREAM_ERROR; windowBits = -windowBits; } #ifdef GZIP @@ -314,7 +310,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, s->hash_bits = (uInt)memLevel + 7; s->hash_size = 1 << s->hash_bits; s->hash_mask = s->hash_size - 1; - s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + s->hash_shift = ((s->hash_bits + MIN_MATCH-1) / MIN_MATCH); s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); @@ -340,11 +336,11 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, * sym_buf value to read moves forward three bytes. From that symbol, up to * 31 bits are written to pending_buf. The closest the written pending_buf * bits gets to the next sym_buf symbol to read is just before the last - * code is written. At that time, 31*(n-2) bits have been written, just - * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at - * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1 + * code is written. At that time, 31*(n - 2) bits have been written, just + * after 24*(n - 2) bits have been consumed from sym_buf. sym_buf starts at + * 8*n bits into pending_buf. (Note that the symbol buffer fills when n - 1 * symbols are written.) The closest the writing gets to what is unread is - * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and + * then n + 14 bits. Here n is lit_bufsize, which is 16384 by default, and * can range from 128 to 32768. * * Therefore, at a minimum, there are 142 bits of space between what is @@ -390,7 +386,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, /* ========================================================================= * Check for a valid deflate stream state. Return 0 if ok, 1 if not. */ -local int deflateStateCheck (strm) +local int deflateStateCheck(strm) z_streamp strm; { deflate_state *s; @@ -413,7 +409,7 @@ local int deflateStateCheck (strm) } /* ========================================================================= */ -int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) +int ZEXPORT deflateSetDictionary(strm, dictionary, dictLength) z_streamp strm; const Bytef *dictionary; uInt dictLength; @@ -482,7 +478,7 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) } /* ========================================================================= */ -int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength) +int ZEXPORT deflateGetDictionary(strm, dictionary, dictLength) z_streamp strm; Bytef *dictionary; uInt *dictLength; @@ -504,7 +500,7 @@ int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength) } /* ========================================================================= */ -int ZEXPORT deflateResetKeep (strm) +int ZEXPORT deflateResetKeep(strm) z_streamp strm; { deflate_state *s; @@ -542,7 +538,7 @@ int ZEXPORT deflateResetKeep (strm) } /* ========================================================================= */ -int ZEXPORT deflateReset (strm) +int ZEXPORT deflateReset(strm) z_streamp strm; { int ret; @@ -554,7 +550,7 @@ int ZEXPORT deflateReset (strm) } /* ========================================================================= */ -int ZEXPORT deflateSetHeader (strm, head) +int ZEXPORT deflateSetHeader(strm, head) z_streamp strm; gz_headerp head; { @@ -565,7 +561,7 @@ int ZEXPORT deflateSetHeader (strm, head) } /* ========================================================================= */ -int ZEXPORT deflatePending (strm, pending, bits) +int ZEXPORT deflatePending(strm, pending, bits) unsigned *pending; int *bits; z_streamp strm; @@ -579,7 +575,7 @@ int ZEXPORT deflatePending (strm, pending, bits) } /* ========================================================================= */ -int ZEXPORT deflatePrime (strm, bits, value) +int ZEXPORT deflatePrime(strm, bits, value) z_streamp strm; int bits; int value; @@ -674,36 +670,50 @@ int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) } /* ========================================================================= - * For the default windowBits of 15 and memLevel of 8, this function returns - * a close to exact, as well as small, upper bound on the compressed size. - * They are coded as constants here for a reason--if the #define's are - * changed, then this function needs to be changed as well. The return - * value for 15 and 8 only works for those exact settings. + * For the default windowBits of 15 and memLevel of 8, this function returns a + * close to exact, as well as small, upper bound on the compressed size. This + * is an expansion of ~0.03%, plus a small constant. * - * For any setting other than those defaults for windowBits and memLevel, - * the value returned is a conservative worst case for the maximum expansion - * resulting from using fixed blocks instead of stored blocks, which deflate - * can emit on compressed data for some combinations of the parameters. + * For any setting other than those defaults for windowBits and memLevel, one + * of two worst case bounds is returned. This is at most an expansion of ~4% or + * ~13%, plus a small constant. * - * This function could be more sophisticated to provide closer upper bounds for - * every combination of windowBits and memLevel. But even the conservative - * upper bound of about 14% expansion does not seem onerous for output buffer - * allocation. + * Both the 0.03% and 4% derive from the overhead of stored blocks. The first + * one is for stored blocks of 16383 bytes (memLevel == 8), whereas the second + * is for stored blocks of 127 bytes (the worst case memLevel == 1). The + * expansion results from five bytes of header for each stored block. + * + * The larger expansion of 13% results from a window size less than or equal to + * the symbols buffer size (windowBits <= memLevel + 7). In that case some of + * the data being compressed may have slid out of the sliding window, impeding + * a stored block from being emitted. Then the only choice is a fixed or + * dynamic block, where a fixed block limits the maximum expansion to 9 bits + * per 8-bit byte, plus 10 bits for every block. The smallest block size for + * which this can occur is 255 (memLevel == 2). + * + * Shifts are used to approximate divisions, for speed. */ uLong ZEXPORT deflateBound(strm, sourceLen) z_streamp strm; uLong sourceLen; { deflate_state *s; - uLong complen, wraplen; + uLong fixedlen, storelen, wraplen; - /* conservative upper bound for compressed data */ - complen = sourceLen + - ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5; + /* upper bound for fixed blocks with 9-bit literals and length 255 + (memLevel == 2, which is the lowest that may not use stored blocks) -- + ~13% overhead plus a small constant */ + fixedlen = sourceLen + (sourceLen >> 3) + (sourceLen >> 8) + + (sourceLen >> 9) + 4; - /* if can't get parameters, return conservative bound plus zlib wrapper */ + /* upper bound for stored blocks with length 127 (memLevel == 1) -- + ~4% overhead plus a small constant */ + storelen = sourceLen + (sourceLen >> 5) + (sourceLen >> 7) + + (sourceLen >> 11) + 7; + + /* if can't get parameters, return larger bound plus a zlib wrapper */ if (deflateStateCheck(strm)) - return complen + 6; + return (fixedlen > storelen ? fixedlen : storelen) + 6; /* compute wrapper length */ s = strm->state; @@ -740,11 +750,12 @@ uLong ZEXPORT deflateBound(strm, sourceLen) wraplen = 6; } - /* if not default parameters, return conservative bound */ + /* if not default parameters, return one of the conservative bounds */ if (s->w_bits != 15 || s->hash_bits != 8 + 7) - return complen + wraplen; + return (s->w_bits <= s->hash_bits ? fixedlen : storelen) + wraplen; - /* default settings: return tight bound for that case */ + /* default settings: return tight bound for that case -- ~0.03% overhead + plus a small constant */ return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + (sourceLen >> 25) + 13 - 6 + wraplen; } @@ -754,7 +765,7 @@ uLong ZEXPORT deflateBound(strm, sourceLen) * IN assertion: the stream state is correct and there is enough room in * pending_buf. */ -local void putShortMSB (s, b) +local void putShortMSB(s, b) deflate_state *s; uInt b; { @@ -801,7 +812,7 @@ local void flush_pending(strm) } while (0) /* ========================================================================= */ -int ZEXPORT deflate (strm, flush) +int ZEXPORT deflate(strm, flush) z_streamp strm; int flush; { @@ -856,7 +867,7 @@ int ZEXPORT deflate (strm, flush) s->status = BUSY_STATE; if (s->status == INIT_STATE) { /* zlib header */ - uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt header = (Z_DEFLATED + ((s->w_bits - 8) << 4)) << 8; uInt level_flags; if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) @@ -1116,7 +1127,7 @@ int ZEXPORT deflate (strm, flush) } /* ========================================================================= */ -int ZEXPORT deflateEnd (strm) +int ZEXPORT deflateEnd(strm) z_streamp strm; { int status; @@ -1142,7 +1153,7 @@ int ZEXPORT deflateEnd (strm) * To simplify the source, this is not supported for 16-bit MSDOS (which * doesn't have enough memory anyway to duplicate compression states). */ -int ZEXPORT deflateCopy (dest, source) +int ZEXPORT deflateCopy(dest, source) z_streamp dest; z_streamp source; { @@ -1231,7 +1242,7 @@ local unsigned read_buf(strm, buf, size) /* =========================================================================== * Initialize the "longest match" routines for a new zlib stream */ -local void lm_init (s) +local void lm_init(s) deflate_state *s; { s->window_size = (ulg)2L*s->w_size; @@ -1252,11 +1263,6 @@ local void lm_init (s) s->match_length = s->prev_length = MIN_MATCH-1; s->match_available = 0; s->ins_h = 0; -#ifndef FASTEST -#ifdef ASMV - match_init(); /* initialize the asm code */ -#endif -#endif } #ifndef FASTEST @@ -1269,10 +1275,6 @@ local void lm_init (s) * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 * OUT assertion: the match length is not greater than s->lookahead. */ -#ifndef ASMV -/* For 80x86 and 680x0, an optimized version will be provided in match.asm or - * match.S. The code will be functionally equivalent. - */ local uInt longest_match(s, cur_match) deflate_state *s; IPos cur_match; /* current match */ @@ -1297,10 +1299,10 @@ local uInt longest_match(s, cur_match) */ register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; register ush scan_start = *(ushf*)scan; - register ush scan_end = *(ushf*)(scan+best_len-1); + register ush scan_end = *(ushf*)(scan + best_len - 1); #else register Bytef *strend = s->window + s->strstart + MAX_MATCH; - register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end1 = scan[best_len - 1]; register Byte scan_end = scan[best_len]; #endif @@ -1318,7 +1320,8 @@ local uInt longest_match(s, cur_match) */ if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead; - Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "need lookahead"); do { Assert(cur_match < s->strstart, "no future"); @@ -1336,43 +1339,44 @@ local uInt longest_match(s, cur_match) /* This code assumes sizeof(unsigned short) == 2. Do not use * UNALIGNED_OK if your compiler uses a different size. */ - if (*(ushf*)(match+best_len-1) != scan_end || + if (*(ushf*)(match + best_len - 1) != scan_end || *(ushf*)match != scan_start) continue; /* It is not necessary to compare scan[2] and match[2] since they are * always equal when the other bytes match, given that the hash keys * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at - * strstart+3, +5, ... up to strstart+257. We check for insufficient + * strstart + 3, + 5, up to strstart + 257. We check for insufficient * lookahead only every 4th comparison; the 128th check will be made - * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * at strstart + 257. If MAX_MATCH-2 is not a multiple of 8, it is * necessary to put more guard bytes at the end of the window, or * to check more often for insufficient lookahead. */ Assert(scan[2] == match[2], "scan[2]?"); scan++, match++; do { - } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + } while (*(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && scan < strend); /* The funny "do {}" generates better code on most compilers */ - /* Here, scan <= window+strstart+257 */ - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + /* Here, scan <= window + strstart + 257 */ + Assert(scan <= s->window + (unsigned)(s->window_size - 1), + "wild scan"); if (*scan == *match) scan++; - len = (MAX_MATCH - 1) - (int)(strend-scan); + len = (MAX_MATCH - 1) - (int)(strend - scan); scan = strend - (MAX_MATCH-1); #else /* UNALIGNED_OK */ - if (match[best_len] != scan_end || - match[best_len-1] != scan_end1 || - *match != *scan || - *++match != scan[1]) continue; + if (match[best_len] != scan_end || + match[best_len - 1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; - /* The check at best_len-1 can be removed because it will be made + /* The check at best_len - 1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that @@ -1382,7 +1386,7 @@ local uInt longest_match(s, cur_match) Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. + * the 256th check will be made at strstart + 258. */ do { } while (*++scan == *++match && *++scan == *++match && @@ -1391,7 +1395,8 @@ local uInt longest_match(s, cur_match) *++scan == *++match && *++scan == *++match && scan < strend); - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (unsigned)(s->window_size - 1), + "wild scan"); len = MAX_MATCH - (int)(strend - scan); scan = strend - MAX_MATCH; @@ -1403,9 +1408,9 @@ local uInt longest_match(s, cur_match) best_len = len; if (len >= nice_match) break; #ifdef UNALIGNED_OK - scan_end = *(ushf*)(scan+best_len-1); + scan_end = *(ushf*)(scan + best_len - 1); #else - scan_end1 = scan[best_len-1]; + scan_end1 = scan[best_len - 1]; scan_end = scan[best_len]; #endif } @@ -1415,7 +1420,6 @@ local uInt longest_match(s, cur_match) if ((uInt)best_len <= s->lookahead) return (uInt)best_len; return s->lookahead; } -#endif /* ASMV */ #else /* FASTEST */ @@ -1436,7 +1440,8 @@ local uInt longest_match(s, cur_match) */ Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "need lookahead"); Assert(cur_match < s->strstart, "no future"); @@ -1446,7 +1451,7 @@ local uInt longest_match(s, cur_match) */ if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; - /* The check at best_len-1 can be removed because it will be made + /* The check at best_len - 1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that @@ -1456,7 +1461,7 @@ local uInt longest_match(s, cur_match) Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. + * the 256th check will be made at strstart + 258. */ do { } while (*++scan == *++match && *++scan == *++match && @@ -1465,7 +1470,7 @@ local uInt longest_match(s, cur_match) *++scan == *++match && *++scan == *++match && scan < strend); - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (unsigned)(s->window_size - 1), "wild scan"); len = MAX_MATCH - (int)(strend - scan); @@ -1501,7 +1506,7 @@ local void check_match(s, start, match, length) z_error("invalid match"); } if (z_verbose > 1) { - fprintf(stderr,"\\[%d,%d]", start-match, length); + fprintf(stderr,"\\[%d,%d]", start - match, length); do { putc(s->window[start++], stderr); } while (--length != 0); } } @@ -1547,9 +1552,9 @@ local void fill_window(s) /* If the window is almost full and there is insufficient lookahead, * move the upper half to the lower one to make room in the upper half. */ - if (s->strstart >= wsize+MAX_DIST(s)) { + if (s->strstart >= wsize + MAX_DIST(s)) { - zmemcpy(s->window, s->window+wsize, (unsigned)wsize - more); + zmemcpy(s->window, s->window + wsize, (unsigned)wsize - more); s->match_start -= wsize; s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ s->block_start -= (long) wsize; @@ -1680,7 +1685,7 @@ local void fill_window(s) * * deflate_stored() is written to minimize the number of times an input byte is * copied. It is most efficient with large input and output buffers, which - * maximizes the opportunites to have a single copy from next_in to next_out. + * maximizes the opportunities to have a single copy from next_in to next_out. */ local block_state deflate_stored(s, flush) deflate_state *s; @@ -1890,7 +1895,7 @@ local block_state deflate_fast(s, flush) if (s->lookahead == 0) break; /* flush the current block */ } - /* Insert the string window[strstart .. strstart+2] in the + /* Insert the string window[strstart .. strstart + 2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = NIL; @@ -1938,7 +1943,7 @@ local block_state deflate_fast(s, flush) s->strstart += s->match_length; s->match_length = 0; s->ins_h = s->window[s->strstart]; - UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); + UPDATE_HASH(s, s->ins_h, s->window[s->strstart + 1]); #if MIN_MATCH != 3 Call UPDATE_HASH() MIN_MATCH-3 more times #endif @@ -1949,7 +1954,7 @@ local block_state deflate_fast(s, flush) } else { /* No match, output a literal byte */ Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; } @@ -1993,7 +1998,7 @@ local block_state deflate_slow(s, flush) if (s->lookahead == 0) break; /* flush the current block */ } - /* Insert the string window[strstart .. strstart+2] in the + /* Insert the string window[strstart .. strstart + 2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = NIL; @@ -2035,17 +2040,17 @@ local block_state deflate_slow(s, flush) uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; /* Do not insert strings in hash table beyond this. */ - check_match(s, s->strstart-1, s->prev_match, s->prev_length); + check_match(s, s->strstart - 1, s->prev_match, s->prev_length); - _tr_tally_dist(s, s->strstart -1 - s->prev_match, + _tr_tally_dist(s, s->strstart - 1 - s->prev_match, s->prev_length - MIN_MATCH, bflush); /* Insert in hash table all strings up to the end of the match. - * strstart-1 and strstart are already inserted. If there is not + * strstart - 1 and strstart are already inserted. If there is not * enough lookahead, the last two strings are not inserted in * the hash table. */ - s->lookahead -= s->prev_length-1; + s->lookahead -= s->prev_length - 1; s->prev_length -= 2; do { if (++s->strstart <= max_insert) { @@ -2063,8 +2068,8 @@ local block_state deflate_slow(s, flush) * single literal. If there was a match but the current match * is longer, truncate the previous match to a single literal. */ - Tracevv((stderr,"%c", s->window[s->strstart-1])); - _tr_tally_lit(s, s->window[s->strstart-1], bflush); + Tracevv((stderr,"%c", s->window[s->strstart - 1])); + _tr_tally_lit(s, s->window[s->strstart - 1], bflush); if (bflush) { FLUSH_BLOCK_ONLY(s, 0); } @@ -2082,8 +2087,8 @@ local block_state deflate_slow(s, flush) } Assert (flush != Z_NO_FLUSH, "no flush?"); if (s->match_available) { - Tracevv((stderr,"%c", s->window[s->strstart-1])); - _tr_tally_lit(s, s->window[s->strstart-1], bflush); + Tracevv((stderr,"%c", s->window[s->strstart - 1])); + _tr_tally_lit(s, s->window[s->strstart - 1], bflush); s->match_available = 0; } s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; @@ -2140,7 +2145,8 @@ local block_state deflate_rle(s, flush) if (s->match_length > s->lookahead) s->match_length = s->lookahead; } - Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (uInt)(s->window_size - 1), + "wild scan"); } /* Emit match if have run of MIN_MATCH or longer, else emit literal */ @@ -2155,7 +2161,7 @@ local block_state deflate_rle(s, flush) } else { /* No match, output a literal byte */ Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; } @@ -2195,7 +2201,7 @@ local block_state deflate_huff(s, flush) /* Output a literal byte */ s->match_length = 0; Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; if (bflush) FLUSH_BLOCK(s, 0); diff --git a/3rdparty/zlib/deflate.h b/3rdparty/zlib/deflate.h index 17c226113b..1a06cd5f25 100644 --- a/3rdparty/zlib/deflate.h +++ b/3rdparty/zlib/deflate.h @@ -329,8 +329,8 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, # define _tr_tally_dist(s, distance, length, flush) \ { uch len = (uch)(length); \ ush dist = (ush)(distance); \ - s->sym_buf[s->sym_next++] = dist; \ - s->sym_buf[s->sym_next++] = dist >> 8; \ + s->sym_buf[s->sym_next++] = (uch)dist; \ + s->sym_buf[s->sym_next++] = (uch)(dist >> 8); \ s->sym_buf[s->sym_next++] = len; \ dist--; \ s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ diff --git a/3rdparty/zlib/gzlib.c b/3rdparty/zlib/gzlib.c index dddaf26873..55da46a453 100644 --- a/3rdparty/zlib/gzlib.c +++ b/3rdparty/zlib/gzlib.c @@ -30,7 +30,7 @@ local gzFile gz_open OF((const void *, int, const char *)); The gz_strwinerror function does not change the current setting of GetLastError. */ -char ZLIB_INTERNAL *gz_strwinerror (error) +char ZLIB_INTERNAL *gz_strwinerror(error) DWORD error; { static char buf[1024]; diff --git a/3rdparty/zlib/gzread.c b/3rdparty/zlib/gzread.c index 884c9bfe4c..dd77381596 100644 --- a/3rdparty/zlib/gzread.c +++ b/3rdparty/zlib/gzread.c @@ -157,11 +157,9 @@ local int gz_look(state) the output buffer is larger than the input buffer, which also assures space for gzungetc() */ state->x.next = state->out; - if (strm->avail_in) { - memcpy(state->x.next, strm->next_in, strm->avail_in); - state->x.have = strm->avail_in; - strm->avail_in = 0; - } + memcpy(state->x.next, strm->next_in, strm->avail_in); + state->x.have = strm->avail_in; + strm->avail_in = 0; state->how = COPY; state->direct = 1; return 0; diff --git a/3rdparty/zlib/gzwrite.c b/3rdparty/zlib/gzwrite.c index a8ffc8f53d..eb8a0e5893 100644 --- a/3rdparty/zlib/gzwrite.c +++ b/3rdparty/zlib/gzwrite.c @@ -474,7 +474,7 @@ int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) #else /* !STDC && !Z_HAVE_STDARG_H */ /* -- see zlib.h -- */ -int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, +int ZEXPORTVA gzprintf(file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) gzFile file; const char *format; diff --git a/3rdparty/zlib/infback.c b/3rdparty/zlib/infback.c index a390c58e81..babeaf1806 100644 --- a/3rdparty/zlib/infback.c +++ b/3rdparty/zlib/infback.c @@ -66,6 +66,7 @@ int stream_size; state->window = window; state->wnext = 0; state->whave = 0; + state->sane = 1; return Z_OK; } @@ -605,25 +606,27 @@ void FAR *out_desc; break; case DONE: - /* inflate stream terminated properly -- write leftover output */ + /* inflate stream terminated properly */ ret = Z_STREAM_END; - if (left < state->wsize) { - if (out(out_desc, state->window, state->wsize - left)) - ret = Z_BUF_ERROR; - } goto inf_leave; case BAD: ret = Z_DATA_ERROR; goto inf_leave; - default: /* can't happen, but makes compilers happy */ + default: + /* can't happen, but makes compilers happy */ ret = Z_STREAM_ERROR; goto inf_leave; } - /* Return unused input */ + /* Write leftover output and return unused input */ inf_leave: + if (left < state->wsize) { + if (out(out_desc, state->window, state->wsize - left) && + ret == Z_STREAM_END) + ret = Z_BUF_ERROR; + } strm->next_in = next; strm->avail_in = have; return ret; diff --git a/3rdparty/zlib/inflate.c b/3rdparty/zlib/inflate.c index 0e7c4f26b1..c84f52507c 100644 --- a/3rdparty/zlib/inflate.c +++ b/3rdparty/zlib/inflate.c @@ -168,6 +168,8 @@ int windowBits; /* extract wrap request from windowBits parameter */ if (windowBits < 0) { + if (windowBits < -15) + return Z_STREAM_ERROR; wrap = 0; windowBits = -windowBits; } @@ -765,8 +767,9 @@ int flush; if (copy > have) copy = have; if (copy) { if (state->head != Z_NULL && - state->head->extra != Z_NULL) { - len = state->head->extra_len - state->length; + state->head->extra != Z_NULL && + (len = state->head->extra_len - state->length) < + state->head->extra_max) { zmemcpy(state->head->extra + len, next, len + copy > state->head->extra_max ? state->head->extra_max - len : copy); diff --git a/3rdparty/zlib/inftrees.c b/3rdparty/zlib/inftrees.c index 09462a740b..57d2793bec 100644 --- a/3rdparty/zlib/inftrees.c +++ b/3rdparty/zlib/inftrees.c @@ -9,7 +9,7 @@ #define MAXBITS 15 const char inflate_copyright[] = - " inflate 1.2.12 Copyright 1995-2022 Mark Adler "; + " inflate 1.2.13 Copyright 1995-2022 Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -62,7 +62,7 @@ unsigned short FAR *work; 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; static const unsigned short lext[31] = { /* Length codes 257..285 extra */ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 199, 202}; + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 194, 65}; static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, diff --git a/3rdparty/zlib/inftrees.h b/3rdparty/zlib/inftrees.h index baa53a0b1a..f53665311c 100644 --- a/3rdparty/zlib/inftrees.h +++ b/3rdparty/zlib/inftrees.h @@ -38,7 +38,7 @@ typedef struct { /* Maximum size of the dynamic table. The maximum number of code structures is 1444, which is the sum of 852 for literal/length codes and 592 for distance codes. These values were found by exhaustive searches using the program - examples/enough.c found in the zlib distribtution. The arguments to that + examples/enough.c found in the zlib distribution. The arguments to that program are the number of symbols, the initial root table size, and the maximum bit length of a code. "enough 286 9 15" for literal/length codes returns returns 852, and "enough 30 6 15" for distance codes returns 592. diff --git a/3rdparty/zlib/trees.c b/3rdparty/zlib/trees.c index f73fd99c37..5f305c4722 100644 --- a/3rdparty/zlib/trees.c +++ b/3rdparty/zlib/trees.c @@ -193,7 +193,7 @@ local void send_bits(s, value, length) s->bits_sent += (ulg)length; /* If not enough room in bi_buf, use (valid) bits from bi_buf and - * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * (16 - bi_valid) bits from value, leaving (width - (16 - bi_valid)) * unused bits in value. */ if (s->bi_valid > (int)Buf_size - length) { @@ -256,7 +256,7 @@ local void tr_static_init() length = 0; for (code = 0; code < LENGTH_CODES-1; code++) { base_length[code] = length; - for (n = 0; n < (1< dist code (0..29) */ dist = 0; for (code = 0 ; code < 16; code++) { base_dist[code] = dist; - for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ for ( ; code < D_CODES; code++) { base_dist[code] = dist << 7; - for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { _dist_code[256 + dist++] = (uch)code; } } - Assert (dist == 256, "tr_static_init: 256+dist != 512"); + Assert (dist == 256, "tr_static_init: 256 + dist != 512"); /* Construct the codes of the static literal tree */ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; @@ -312,7 +312,7 @@ local void tr_static_init() } /* =========================================================================== - * Genererate the file trees.h describing the static trees. + * Generate the file trees.h describing the static trees. */ #ifdef GEN_TREES_H # ifndef ZLIB_DEBUG @@ -321,7 +321,7 @@ local void tr_static_init() # define SEPARATOR(i, last, width) \ ((i) == (last)? "\n};\n\n" : \ - ((i) % (width) == (width)-1 ? ",\n" : ", ")) + ((i) % (width) == (width) - 1 ? ",\n" : ", ")) void gen_trees_header() { @@ -458,7 +458,7 @@ local void pqdownheap(s, tree, k) while (j <= s->heap_len) { /* Set j to the smallest of the two sons: */ if (j < s->heap_len && - smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + smaller(tree, s->heap[j + 1], s->heap[j], s->depth)) { j++; } /* Exit if v is smaller than both sons */ @@ -507,7 +507,7 @@ local void gen_bitlen(s, desc) */ tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ - for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + for (h = s->heap_max + 1; h < HEAP_SIZE; h++) { n = s->heap[h]; bits = tree[tree[n].Dad].Len + 1; if (bits > max_length) bits = max_length, overflow++; @@ -518,7 +518,7 @@ local void gen_bitlen(s, desc) s->bl_count[bits]++; xbits = 0; - if (n >= base) xbits = extra[n-base]; + if (n >= base) xbits = extra[n - base]; f = tree[n].Freq; s->opt_len += (ulg)f * (unsigned)(bits + xbits); if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits); @@ -530,10 +530,10 @@ local void gen_bitlen(s, desc) /* Find the first bit length which could increase: */ do { - bits = max_length-1; + bits = max_length - 1; while (s->bl_count[bits] == 0) bits--; - s->bl_count[bits]--; /* move one leaf down the tree */ - s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits + 1] += 2; /* move one overflow item as its brother */ s->bl_count[max_length]--; /* The brother of the overflow item also moves one step up, * but this does not affect bl_count[max_length] @@ -569,7 +569,7 @@ local void gen_bitlen(s, desc) * OUT assertion: the field code is set for all tree elements of non * zero code length. */ -local void gen_codes (tree, max_code, bl_count) +local void gen_codes(tree, max_code, bl_count) ct_data *tree; /* the tree to decorate */ int max_code; /* largest code with non zero frequency */ ushf *bl_count; /* number of codes at each bit length */ @@ -583,13 +583,13 @@ local void gen_codes (tree, max_code, bl_count) * without bit reversal. */ for (bits = 1; bits <= MAX_BITS; bits++) { - code = (code + bl_count[bits-1]) << 1; + code = (code + bl_count[bits - 1]) << 1; next_code[bits] = (ush)code; } /* Check that the bit counts in bl_count are consistent. The last code * must be all ones. */ - Assert (code + bl_count[MAX_BITS]-1 == (1<heap_len = 0, s->heap_max = HEAP_SIZE; @@ -652,7 +652,7 @@ local void build_tree(s, desc) } desc->max_code = max_code; - /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + /* The elements heap[heap_len/2 + 1 .. heap_len] are leaves of the tree, * establish sub-heaps of increasing lengths: */ for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); @@ -700,7 +700,7 @@ local void build_tree(s, desc) * Scan a literal or distance tree to determine the frequencies of the codes * in the bit length tree. */ -local void scan_tree (s, tree, max_code) +local void scan_tree(s, tree, max_code) deflate_state *s; ct_data *tree; /* the tree to be scanned */ int max_code; /* and its largest code of non zero frequency */ @@ -714,10 +714,10 @@ local void scan_tree (s, tree, max_code) int min_count = 4; /* min repeat count */ if (nextlen == 0) max_count = 138, min_count = 3; - tree[max_code+1].Len = (ush)0xffff; /* guard */ + tree[max_code + 1].Len = (ush)0xffff; /* guard */ for (n = 0; n <= max_code; n++) { - curlen = nextlen; nextlen = tree[n+1].Len; + curlen = nextlen; nextlen = tree[n + 1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { @@ -745,7 +745,7 @@ local void scan_tree (s, tree, max_code) * Send a literal or distance tree in compressed form, using the codes in * bl_tree. */ -local void send_tree (s, tree, max_code) +local void send_tree(s, tree, max_code) deflate_state *s; ct_data *tree; /* the tree to be scanned */ int max_code; /* and its largest code of non zero frequency */ @@ -758,11 +758,11 @@ local void send_tree (s, tree, max_code) int max_count = 7; /* max repeat count */ int min_count = 4; /* min repeat count */ - /* tree[max_code+1].Len = -1; */ /* guard already set */ + /* tree[max_code + 1].Len = -1; */ /* guard already set */ if (nextlen == 0) max_count = 138, min_count = 3; for (n = 0; n <= max_code; n++) { - curlen = nextlen; nextlen = tree[n+1].Len; + curlen = nextlen; nextlen = tree[n + 1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { @@ -773,13 +773,13 @@ local void send_tree (s, tree, max_code) send_code(s, curlen, s->bl_tree); count--; } Assert(count >= 3 && count <= 6, " 3_6?"); - send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count - 3, 2); } else if (count <= 10) { - send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count - 3, 3); } else { - send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count - 11, 7); } count = 0; prevlen = curlen; if (nextlen == 0) { @@ -807,8 +807,8 @@ local int build_bl_tree(s) /* Build the bit length tree: */ build_tree(s, (tree_desc *)(&(s->bl_desc))); - /* opt_len now includes the length of the tree representations, except - * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + /* opt_len now includes the length of the tree representations, except the + * lengths of the bit lengths codes and the 5 + 5 + 4 bits for the counts. */ /* Determine the number of bit length codes to send. The pkzip format @@ -819,7 +819,7 @@ local int build_bl_tree(s) if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; } /* Update opt_len to include the bit length tree and counts */ - s->opt_len += 3*((ulg)max_blindex+1) + 5+5+4; + s->opt_len += 3*((ulg)max_blindex + 1) + 5 + 5 + 4; Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", s->opt_len, s->static_len)); @@ -841,19 +841,19 @@ local void send_all_trees(s, lcodes, dcodes, blcodes) Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, "too many codes"); Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes-1, 5); - send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ for (rank = 0; rank < blcodes; rank++) { Tracev((stderr, "\nbl code %2d ", bl_order[rank])); send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); } Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); - send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + send_tree(s, (ct_data *)s->dyn_ltree, lcodes - 1); /* literal tree */ Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); - send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + send_tree(s, (ct_data *)s->dyn_dtree, dcodes - 1); /* distance tree */ Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); } @@ -866,7 +866,7 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) ulg stored_len; /* length of input block */ int last; /* one if this is the last block for a file */ { - send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */ + send_bits(s, (STORED_BLOCK<<1) + last, 3); /* send block type */ bi_windup(s); /* align on byte boundary */ put_short(s, (ush)stored_len); put_short(s, (ush)~stored_len); @@ -877,7 +877,7 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; s->compressed_len += (stored_len + 4) << 3; s->bits_sent += 2*16; - s->bits_sent += stored_len<<3; + s->bits_sent += stored_len << 3; #endif } @@ -943,14 +943,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) max_blindex = build_bl_tree(s); /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s->opt_len+3+7)>>3; - static_lenb = (s->static_len+3+7)>>3; + opt_lenb = (s->opt_len + 3 + 7) >> 3; + static_lenb = (s->static_len + 3 + 7) >> 3; Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, s->sym_next / 3)); - if (static_lenb <= opt_lenb) opt_lenb = static_lenb; +#ifndef FORCE_STATIC + if (static_lenb <= opt_lenb || s->strategy == Z_FIXED) +#endif + opt_lenb = static_lenb; } else { Assert(buf != (char*)0, "lost buf"); @@ -960,7 +963,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) #ifdef FORCE_STORED if (buf != (char*)0) { /* force stored block */ #else - if (stored_len+4 <= opt_lenb && buf != (char*)0) { + if (stored_len + 4 <= opt_lenb && buf != (char*)0) { /* 4: two words for the lengths */ #endif /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. @@ -971,21 +974,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) */ _tr_stored_block(s, buf, stored_len, last); -#ifdef FORCE_STATIC - } else if (static_lenb >= 0) { /* force static trees */ -#else - } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { -#endif - send_bits(s, (STATIC_TREES<<1)+last, 3); + } else if (static_lenb == opt_lenb) { + send_bits(s, (STATIC_TREES<<1) + last, 3); compress_block(s, (const ct_data *)static_ltree, (const ct_data *)static_dtree); #ifdef ZLIB_DEBUG s->compressed_len += 3 + s->static_len; #endif } else { - send_bits(s, (DYN_TREES<<1)+last, 3); - send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, - max_blindex+1); + send_bits(s, (DYN_TREES<<1) + last, 3); + send_all_trees(s, s->l_desc.max_code + 1, s->d_desc.max_code + 1, + max_blindex + 1); compress_block(s, (const ct_data *)s->dyn_ltree, (const ct_data *)s->dyn_dtree); #ifdef ZLIB_DEBUG @@ -1004,22 +1003,22 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) s->compressed_len += 7; /* align on byte boundary */ #endif } - Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, - s->compressed_len-7*last)); + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len >> 3, + s->compressed_len - 7*last)); } /* =========================================================================== * Save the match info and tally the frequency counts. Return true if * the current block must be flushed. */ -int ZLIB_INTERNAL _tr_tally (s, dist, lc) +int ZLIB_INTERNAL _tr_tally(s, dist, lc) deflate_state *s; unsigned dist; /* distance of matched string */ - unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ + unsigned lc; /* match length - MIN_MATCH or unmatched char (dist==0) */ { - s->sym_buf[s->sym_next++] = dist; - s->sym_buf[s->sym_next++] = dist >> 8; - s->sym_buf[s->sym_next++] = lc; + s->sym_buf[s->sym_next++] = (uch)dist; + s->sym_buf[s->sym_next++] = (uch)(dist >> 8); + s->sym_buf[s->sym_next++] = (uch)lc; if (dist == 0) { /* lc is the unmatched char */ s->dyn_ltree[lc].Freq++; @@ -1031,7 +1030,7 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc) (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_ltree[_length_code[lc] + LITERALS + 1].Freq++; s->dyn_dtree[d_code(dist)].Freq++; } return (s->sym_next == s->sym_end); @@ -1061,7 +1060,7 @@ local void compress_block(s, ltree, dtree) } else { /* Here, lc is the match length - MIN_MATCH */ code = _length_code[lc]; - send_code(s, code+LITERALS+1, ltree); /* send the length code */ + send_code(s, code + LITERALS + 1, ltree); /* send length code */ extra = extra_lbits[code]; if (extra != 0) { lc -= base_length[code]; @@ -1177,6 +1176,6 @@ local void bi_windup(s) s->bi_buf = 0; s->bi_valid = 0; #ifdef ZLIB_DEBUG - s->bits_sent = (s->bits_sent+7) & ~7; + s->bits_sent = (s->bits_sent + 7) & ~7; #endif } diff --git a/3rdparty/zlib/uncompr.c b/3rdparty/zlib/uncompr.c index f03a1a865e..f9532f46c1 100644 --- a/3rdparty/zlib/uncompr.c +++ b/3rdparty/zlib/uncompr.c @@ -24,7 +24,7 @@ Z_DATA_ERROR if the input data was corrupted, including if the input data is an incomplete zlib stream. */ -int ZEXPORT uncompress2 (dest, destLen, source, sourceLen) +int ZEXPORT uncompress2(dest, destLen, source, sourceLen) Bytef *dest; uLongf *destLen; const Bytef *source; @@ -83,7 +83,7 @@ int ZEXPORT uncompress2 (dest, destLen, source, sourceLen) err; } -int ZEXPORT uncompress (dest, destLen, source, sourceLen) +int ZEXPORT uncompress(dest, destLen, source, sourceLen) Bytef *dest; uLongf *destLen; const Bytef *source; diff --git a/3rdparty/zlib/zconf.h b/3rdparty/zlib/zconf.h index 5e1d68a004..bf977d3e70 100644 --- a/3rdparty/zlib/zconf.h +++ b/3rdparty/zlib/zconf.h @@ -38,6 +38,9 @@ # define crc32 z_crc32 # define crc32_combine z_crc32_combine # define crc32_combine64 z_crc32_combine64 +# define crc32_combine_gen z_crc32_combine_gen +# define crc32_combine_gen64 z_crc32_combine_gen64 +# define crc32_combine_op z_crc32_combine_op # define crc32_z z_crc32_z # define deflate z_deflate # define deflateBound z_deflateBound @@ -349,6 +352,9 @@ # ifdef FAR # undef FAR # endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif # include /* No need for _export, use ZLIB.DEF instead. */ /* For complete Windows compatibility, use WINAPI, not __stdcall. */ @@ -467,11 +473,18 @@ typedef uLong FAR uLongf; # undef _LARGEFILE64_SOURCE #endif -#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) -# define Z_HAVE_UNISTD_H +#ifndef Z_HAVE_UNISTD_H +# ifdef __WATCOMC__ +# define Z_HAVE_UNISTD_H +# endif +#endif +#ifndef Z_HAVE_UNISTD_H +# if defined(_LARGEFILE64_SOURCE) && !defined(_WIN32) +# define Z_HAVE_UNISTD_H +# endif #endif #ifndef Z_SOLO -# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# if defined(Z_HAVE_UNISTD_H) # include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ # ifdef VMS # include /* for off_t */ diff --git a/3rdparty/zlib/zlib.h b/3rdparty/zlib/zlib.h index 4a98e38bf3..953cb5012d 100644 --- a/3rdparty/zlib/zlib.h +++ b/3rdparty/zlib/zlib.h @@ -1,5 +1,5 @@ /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.12, March 11th, 2022 + version 1.2.13, October 13th, 2022 Copyright (C) 1995-2022 Jean-loup Gailly and Mark Adler @@ -37,11 +37,11 @@ extern "C" { #endif -#define ZLIB_VERSION "1.2.12" -#define ZLIB_VERNUM 0x12c0 +#define ZLIB_VERSION "1.2.13" +#define ZLIB_VERNUM 0x12d0 #define ZLIB_VER_MAJOR 1 #define ZLIB_VER_MINOR 2 -#define ZLIB_VER_REVISION 12 +#define ZLIB_VER_REVISION 13 #define ZLIB_VER_SUBREVISION 0 /* @@ -276,7 +276,7 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); == 0), or after each call of deflate(). If deflate returns Z_OK and with zero avail_out, it must be called again after making room in the output buffer because there might be more output pending. See deflatePending(), - which can be used if desired to determine whether or not there is more ouput + which can be used if desired to determine whether or not there is more output in that case. Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to @@ -660,7 +660,7 @@ ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm, to dictionary. dictionary must have enough space, where 32768 bytes is always enough. If deflateGetDictionary() is called with dictionary equal to Z_NULL, then only the dictionary length is returned, and nothing is copied. - Similary, if dictLength is Z_NULL, then it is not set. + Similarly, if dictLength is Z_NULL, then it is not set. deflateGetDictionary() may return a length less than the window size, even when more than the window size in input has been provided. It may return up @@ -915,7 +915,7 @@ ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, to dictionary. dictionary must have enough space, where 32768 bytes is always enough. If inflateGetDictionary() is called with dictionary equal to Z_NULL, then only the dictionary length is returned, and nothing is copied. - Similary, if dictLength is Z_NULL, then it is not set. + Similarly, if dictLength is Z_NULL, then it is not set. inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the stream state is inconsistent. @@ -1437,12 +1437,12 @@ ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems, In the event that the end of file is reached and only a partial item is available at the end, i.e. the remaining uncompressed data length is not a - multiple of size, then the final partial item is nevetheless read into buf + multiple of size, then the final partial item is nevertheless read into buf and the end-of-file flag is set. The length of the partial item read is not provided, but could be inferred from the result of gztell(). This behavior is the same as the behavior of fread() implementations in common libraries, but it prevents the direct use of gzfread() to read a concurrently written - file, reseting and retrying on end-of-file, when size is not 1. + file, resetting and retrying on end-of-file, when size is not 1. */ ZEXTERN int ZEXPORT gzwrite OF((gzFile file, voidpc buf, unsigned len)); @@ -1913,7 +1913,7 @@ ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int)); -ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp)); +ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF((z_streamp)); ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); #if defined(_WIN32) && !defined(Z_SOLO) diff --git a/3rdparty/zlib/zutil.c b/3rdparty/zlib/zutil.c index dcab28a0d5..9543ae825e 100644 --- a/3rdparty/zlib/zutil.c +++ b/3rdparty/zlib/zutil.c @@ -61,9 +61,11 @@ uLong ZEXPORT zlibCompileFlags() #ifdef ZLIB_DEBUG flags += 1 << 8; #endif + /* #if defined(ASMV) || defined(ASMINF) flags += 1 << 9; #endif + */ #ifdef ZLIB_WINAPI flags += 1 << 10; #endif @@ -119,7 +121,7 @@ uLong ZEXPORT zlibCompileFlags() # endif int ZLIB_INTERNAL z_verbose = verbose; -void ZLIB_INTERNAL z_error (m) +void ZLIB_INTERNAL z_error(m) char *m; { fprintf(stderr, "%s\n", m); @@ -214,7 +216,7 @@ local ptr_table table[MAX_PTR]; * a protected system like OS/2. Use Microsoft C instead. */ -voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size) { voidpf buf; ulg bsize = (ulg)items*size; @@ -240,7 +242,7 @@ voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) return buf; } -void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { int n; @@ -277,13 +279,13 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) # define _hfree hfree #endif -voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size) +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, uInt items, uInt size) { (void)opaque; return _halloc((long)items, size); } -void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { (void)opaque; _hfree(ptr); @@ -302,7 +304,7 @@ extern voidp calloc OF((uInt items, uInt size)); extern void free OF((voidpf ptr)); #endif -voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) +voidpf ZLIB_INTERNAL zcalloc(opaque, items, size) voidpf opaque; unsigned items; unsigned size; @@ -312,7 +314,7 @@ voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) (voidpf)calloc(items, size); } -void ZLIB_INTERNAL zcfree (opaque, ptr) +void ZLIB_INTERNAL zcfree(opaque, ptr) voidpf opaque; voidpf ptr; { diff --git a/3rdparty/zlib/zutil.h b/3rdparty/zlib/zutil.h index d9a20ae1bf..0bc7f4ecd1 100644 --- a/3rdparty/zlib/zutil.h +++ b/3rdparty/zlib/zutil.h @@ -193,6 +193,7 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine_gen64 OF((z_off_t)); #endif /* common defaults */ diff --git a/CMakeLists.txt b/CMakeLists.txt index 809004a2b6..25e808bec3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -264,9 +264,12 @@ OCV_OPTION(WITH_CUBLAS "Include NVidia Cuda Basic Linear Algebra Subprograms (BL OCV_OPTION(WITH_CUDNN "Include NVIDIA CUDA Deep Neural Network (cuDNN) library support" WITH_CUDA VISIBLE_IF WITH_CUDA VERIFY HAVE_CUDNN) -OCV_OPTION(WITH_NVCUVID "Include NVidia Video Decoding library support" OFF # disabled, details: https://github.com/opencv/opencv/issues/14850 +OCV_OPTION(WITH_NVCUVID "Include NVidia Video Decoding library support" ON VISIBLE_IF WITH_CUDA VERIFY HAVE_NVCUVID) +OCV_OPTION(WITH_NVCUVENC "Include NVidia Video Encoding library support" ON + VISIBLE_IF WITH_CUDA + VERIFY HAVE_NVCUVENC) OCV_OPTION(WITH_EIGEN "Include Eigen2/Eigen3 support" (NOT CV_DISABLE_OPTIMIZATION AND NOT CMAKE_CROSSCOMPILING) VISIBLE_IF NOT WINRT VERIFY HAVE_EIGEN) @@ -331,6 +334,9 @@ OCV_OPTION(WITH_OPENNI2 "Include OpenNI2 support" OFF OCV_OPTION(WITH_PNG "Include PNG support" ON VISIBLE_IF TRUE VERIFY HAVE_PNG) +OCV_OPTION(WITH_SPNG "Include SPNG support" OFF + VISIBLE_IF TRUE + VERIFY HAVE_SPNG) OCV_OPTION(WITH_GDCM "Include DICOM support" OFF VISIBLE_IF TRUE VERIFY HAVE_GDCM) @@ -463,6 +469,9 @@ OCV_OPTION(WITH_TIMVX "Include Tim-VX support" OFF OCV_OPTION(WITH_OBSENSOR "Include obsensor support (Orbbec RGB-D modules: Astra+/Femto)" ON VISIBLE_IF (WIN32 AND NOT ARM AND NOT WINRT) OR ( UNIX AND NOT APPLE AND NOT ANDROID) VERIFY HAVE_OBSENSOR) +OCV_OPTION(WITH_CANN "Include CANN support" OFF + VISIBLE_IF TRUE + VERIFY HAVE_CANN) # OpenCV build components # =================================================== @@ -645,7 +654,7 @@ ocv_cmake_hook(POST_COMPILER_OPTIONS) # ---------------------------------------------------------------------------- # CHECK FOR SYSTEM LIBRARIES, OPTIONS, ETC.. # ---------------------------------------------------------------------------- -if(UNIX) +if(UNIX OR MINGW) if(NOT APPLE_FRAMEWORK OR OPENCV_ENABLE_PKG_CONFIG) if(CMAKE_CROSSCOMPILING AND NOT DEFINED ENV{PKG_CONFIG_LIBDIR} AND NOT DEFINED ENV{PKG_CONFIG_SYSROOT_DIR} AND NOT OPENCV_ENABLE_PKG_CONFIG @@ -677,6 +686,8 @@ if(UNIX) # no need to link to system libs with emscripten elseif(QNXNTO) set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} m) + elseif(MINGW) + set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} pthread) else() set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} dl m pthread rt) endif() @@ -752,6 +763,9 @@ endif() if(WITH_TIMVX) include(cmake/OpenCVFindTIMVX.cmake) endif() +if(WITH_CANN) + include(cmake/OpenCVFindCANN.cmake) +endif() # ---------------------------------------------------------------------------- # Detect other 3rd-party libraries/tools @@ -1352,6 +1366,16 @@ if(WITH_JPEG OR HAVE_JPEG) status(" JPEG:" NO) elseif(BUILD_JPEG) status(" JPEG:" "build-${JPEG_LIBRARY} (ver ${JPEG_LIB_VERSION})") + if(ENABLE_LIBJPEG_TURBO_SIMD) + status(" SIMD Support Request:" "YES") + if(HAVE_LIBJPEG_TURBO_SIMD) + status(" SIMD Support:" "YES") + else() + status(" SIMD Support:" "NO") + endif() + else() + status(" SIMD Support Request:" "NO") + endif() else() status(" JPEG:" "${JPEG_LIBRARY} (ver ${JPEG_LIB_VERSION})") endif() @@ -1361,8 +1385,12 @@ if(WITH_WEBP OR HAVE_WEBP) status(" WEBP:" WEBP_FOUND THEN "${WEBP_LIBRARY} (ver ${WEBP_VERSION})" ELSE "build (ver ${WEBP_VERSION})") endif() -if(WITH_PNG OR HAVE_PNG) - status(" PNG:" PNG_FOUND THEN "${PNG_LIBRARY} (ver ${PNG_VERSION})" ELSE "build (ver ${PNG_VERSION})") +if(WITH_PNG OR HAVE_PNG OR WITH_SPNG) + if(WITH_SPNG) + status(" PNG:" "build-${SPNG_LIBRARY} (ver ${SPNG_VERSION})") + else() + status(" PNG:" PNG_FOUND THEN "${PNG_LIBRARY} (ver ${PNG_VERSION})" ELSE "build (ver ${PNG_VERSION})") + endif() endif() if(WITH_TIFF OR HAVE_TIFF) @@ -1433,6 +1461,9 @@ if(WITH_FFMPEG OR HAVE_FFMPEG) status(" avutil:" FFMPEG_libavutil_VERSION THEN "YES (${FFMPEG_libavutil_VERSION})" ELSE NO) status(" swscale:" FFMPEG_libswscale_VERSION THEN "YES (${FFMPEG_libswscale_VERSION})" ELSE NO) status(" avresample:" FFMPEG_libavresample_VERSION THEN "YES (${FFMPEG_libavresample_VERSION})" ELSE NO) + if(OPENCV_FFMPEG_ENABLE_LIBAVDEVICE) + status(" avdevice:" FFMPEG_libavdevice_VERSION THEN "YES (${FFMPEG_libavdevice_VERSION})" ELSE NO) + endif() endif() if(WITH_GSTREAMER OR HAVE_GSTREAMER) @@ -1653,6 +1684,7 @@ if(WITH_CUDA OR HAVE_CUDA) IF HAVE_CUFFT THEN "CUFFT" IF HAVE_CUBLAS THEN "CUBLAS" IF HAVE_NVCUVID THEN "NVCUVID" + IF HAVE_NVCUVENC THEN "NVCUVENC" IF CUDA_FAST_MATH THEN "FAST_MATH" ELSE "no extra features") status("") @@ -1721,6 +1753,15 @@ if(WITH_ONNX OR HAVE_ONNX) endif() endif() +if(WITH_CANN) + status("") + status(" CANN:" HAVE_CANN THEN "YES" ELSE "NO") + if(HAVE_CANN) + status(" Include path" CANN_INCLUDE_DIRS THEN "${CANN_INCLUDE_DIRS}" ELSE "NO") + status(" Link libraries:" CANN_LIBRARIES THEN "${CANN_LIBRARIES}" ELSE "NO") + endif() +endif() + # ========================== python ========================== if(BUILD_opencv_python2) status("") diff --git a/COPYRIGHT b/COPYRIGHT index d5875e9864..6b0b6882f6 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -2,10 +2,10 @@ Copyright (C) 2000-2022, Intel Corporation, all rights reserved. Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved. Copyright (C) 2009-2016, NVIDIA Corporation, all rights reserved. Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved. -Copyright (C) 2015-2022, OpenCV Foundation, all rights reserved. +Copyright (C) 2015-2023, OpenCV Foundation, all rights reserved. Copyright (C) 2008-2016, Itseez Inc., all rights reserved. -Copyright (C) 2019-2022, Xperience AI, all rights reserved. -Copyright (C) 2019-2022, Shenzhen Institute of Artificial Intelligence and - Robotics for Society, all rights reserved. +Copyright (C) 2019-2023, Xperience AI, all rights reserved. +Copyright (C) 2019-2022, Shenzhen Institute of Artificial Intelligence and Robotics for Society, all rights reserved. +Copyright (C) 2022-2023, Southern University of Science And Technology, all rights reserved. Third party copyrights are property of their respective owners. diff --git a/apps/interactive-calibration/CMakeLists.txt b/apps/interactive-calibration/CMakeLists.txt index 85d121a20b..9db4527d1d 100644 --- a/apps/interactive-calibration/CMakeLists.txt +++ b/apps/interactive-calibration/CMakeLists.txt @@ -1,6 +1,3 @@ -set(DEPS opencv_core opencv_imgproc opencv_features2d opencv_highgui opencv_3d opencv_calib opencv_videoio) -if(${BUILD_opencv_aruco}) - list(APPEND DEPS opencv_aruco) -endif() +set(DEPS opencv_core opencv_imgproc opencv_features2d opencv_highgui opencv_3d opencv_calib opencv_videoio opencv_objdetect) file(GLOB SRCS *.cpp) ocv_add_application(opencv_interactive-calibration MODULES ${DEPS} SRCS ${SRCS}) diff --git a/apps/interactive-calibration/calibController.cpp b/apps/interactive-calibration/calibController.cpp index 421c778ad2..432cc29a40 100644 --- a/apps/interactive-calibration/calibController.cpp +++ b/apps/interactive-calibration/calibController.cpp @@ -222,10 +222,10 @@ void calib::calibDataController::filterFrames() if(mCalibData->imagePoints.size()) { mCalibData->imagePoints.erase(mCalibData->imagePoints.begin() + worstElemIndex); mCalibData->objectPoints.erase(mCalibData->objectPoints.begin() + worstElemIndex); - } - else { - mCalibData->allCharucoCorners.erase(mCalibData->allCharucoCorners.begin() + worstElemIndex); - mCalibData->allCharucoIds.erase(mCalibData->allCharucoIds.begin() + worstElemIndex); + if (mCalibData->allCharucoCorners.size()) { + mCalibData->allCharucoCorners.erase(mCalibData->allCharucoCorners.begin() + worstElemIndex); + mCalibData->allCharucoIds.erase(mCalibData->allCharucoIds.begin() + worstElemIndex); + } } cv::Mat newErrorsVec = cv::Mat((int)numberOfFrames - 1, 1, CV_64F); diff --git a/apps/interactive-calibration/frameProcessor.cpp b/apps/interactive-calibration/frameProcessor.cpp index 9cf2e283b0..a8cf8ae074 100644 --- a/apps/interactive-calibration/frameProcessor.cpp +++ b/apps/interactive-calibration/frameProcessor.cpp @@ -12,7 +12,6 @@ #include #include -#include #include using namespace calib; @@ -75,17 +74,12 @@ bool CalibProcessor::detectAndParseChessboard(const cv::Mat &frame) bool CalibProcessor::detectAndParseChAruco(const cv::Mat &frame) { -#ifdef HAVE_OPENCV_ARUCO cv::Ptr board = mCharucoBoard.staticCast(); std::vector > corners, rejected; std::vector ids; - cv::aruco::detectMarkers(frame, mArucoDictionary, corners, ids, cv::aruco::DetectorParameters::create(), rejected); - cv::aruco::refineDetectedMarkers(frame, board, corners, ids, rejected); cv::Mat currentCharucoCorners, currentCharucoIds; - if(ids.size() > 0) - cv::aruco::interpolateCornersCharuco(corners, ids, frame, mCharucoBoard, currentCharucoCorners, - currentCharucoIds); + detector->detectBoard(frame, currentCharucoCorners, currentCharucoIds, corners, ids); if(ids.size() > 0) cv::aruco::drawDetectedMarkers(frame, corners); if(currentCharucoCorners.total() > 3) { @@ -103,9 +97,6 @@ bool CalibProcessor::detectAndParseChAruco(const cv::Mat &frame) mCurrentCharucoIds = currentCharucoIds; return true; } -#else - CV_UNUSED(frame); -#endif return false; } @@ -156,6 +147,7 @@ bool CalibProcessor::detectAndParseDualACircles(const cv::Mat &frame) void CalibProcessor::saveFrameData() { std::vector objectPoints; + std::vector imagePoints; switch(mBoardType) { @@ -170,6 +162,11 @@ void CalibProcessor::saveFrameData() case chAruco: mCalibData->allCharucoCorners.push_back(mCurrentCharucoCorners); mCalibData->allCharucoIds.push_back(mCurrentCharucoIds); + + mCharucoBoard->matchImagePoints(mCurrentCharucoCorners, mCurrentCharucoIds, objectPoints, imagePoints); + CV_Assert(mCurrentCharucoIds.total() == imagePoints.size()); + mCalibData->imagePoints.push_back(imagePoints); + mCalibData->objectPoints.push_back(objectPoints); break; case CirclesGrid: objectPoints.reserve(mBoardSize.height*mBoardSize.width); @@ -249,37 +246,17 @@ bool CalibProcessor::checkLastFrame() else mCalibData->cameraMatrix.copyTo(tmpCamMatrix); - if(mBoardType != chAruco) { - cv::Mat r, t, angles; - cv::solvePnP(mCalibData->objectPoints.back(), mCurrentImagePoints, tmpCamMatrix, mCalibData->distCoeffs, r, t); - RodriguesToEuler(r, angles, CALIB_DEGREES); - - if(fabs(angles.at(0)) > badAngleThresh || fabs(angles.at(1)) > badAngleThresh) { - mCalibData->objectPoints.pop_back(); - mCalibData->imagePoints.pop_back(); - isFrameBad = true; - } - } - else { -#ifdef HAVE_OPENCV_ARUCO - cv::Mat r, t, angles; - std::vector allObjPoints; - allObjPoints.reserve(mCurrentCharucoIds.total()); - for(size_t i = 0; i < mCurrentCharucoIds.total(); i++) { - int pointID = mCurrentCharucoIds.at((int)i); - CV_Assert(pointID >= 0 && pointID < (int)mCharucoBoard->chessboardCorners.size()); - allObjPoints.push_back(mCharucoBoard->chessboardCorners[pointID]); - } - - cv::solvePnP(allObjPoints, mCurrentCharucoCorners, tmpCamMatrix, mCalibData->distCoeffs, r, t); - RodriguesToEuler(r, angles, CALIB_DEGREES); - - if(180.0 - fabs(angles.at(0)) > badAngleThresh || fabs(angles.at(1)) > badAngleThresh) { - isFrameBad = true; + cv::Mat r, t, angles; + cv::solvePnP(mCalibData->objectPoints.back(), mCalibData->imagePoints.back(), tmpCamMatrix, mCalibData->distCoeffs, r, t); + RodriguesToEuler(r, angles, CALIB_DEGREES); + if(fabs(angles.at(0)) > badAngleThresh || fabs(angles.at(1)) > badAngleThresh) { + mCalibData->objectPoints.pop_back(); + mCalibData->imagePoints.pop_back(); + if (mCalibData->allCharucoCorners.size()) { mCalibData->allCharucoCorners.pop_back(); mCalibData->allCharucoIds.pop_back(); } -#endif + isFrameBad = true; } return isFrameBad; } @@ -296,16 +273,16 @@ CalibProcessor::CalibProcessor(cv::Ptr data, captureParameters mTemplDist = capParams.templDst; mSaveFrames = capParams.saveFrames; mZoom = capParams.zoom; + cv::aruco::CharucoParameters charucoParameters; + charucoParameters.tryRefineMarkers = true; switch(mBoardType) { case chAruco: -#ifdef HAVE_OPENCV_ARUCO - mArucoDictionary = cv::aruco::getPredefinedDictionary( - cv::aruco::PREDEFINED_DICTIONARY_NAME(capParams.charucoDictName)); - mCharucoBoard = cv::aruco::CharucoBoard::create(mBoardSize.width, mBoardSize.height, capParams.charucoSquareLength, - capParams.charucoMarkerSize, mArucoDictionary); -#endif + mArucoDictionary = cv::aruco::getPredefinedDictionary(cv::aruco::PredefinedDictionaryType(capParams.charucoDictName)); + mCharucoBoard = cv::makePtr(cv::Size(mBoardSize.width + 1, mBoardSize.height + 1), capParams.charucoSquareLength, + capParams.charucoMarkerSize, mArucoDictionary); + detector = cv::makePtr(cv::aruco::CharucoDetector(*mCharucoBoard, charucoParameters)); break; case CirclesGrid: case AcirclesGrid: diff --git a/apps/interactive-calibration/frameProcessor.hpp b/apps/interactive-calibration/frameProcessor.hpp index 2a1b7aab95..923020311c 100644 --- a/apps/interactive-calibration/frameProcessor.hpp +++ b/apps/interactive-calibration/frameProcessor.hpp @@ -7,9 +7,7 @@ #include #include -#ifdef HAVE_OPENCV_ARUCO -#include -#endif +#include #include "calibCommon.hpp" #include "calibController.hpp" @@ -39,10 +37,9 @@ protected: cv::Mat mCurrentCharucoIds; cv::Ptr mBlobDetectorPtr; -#ifdef HAVE_OPENCV_ARUCO - cv::Ptr mArucoDictionary; + cv::aruco::Dictionary mArucoDictionary; cv::Ptr mCharucoBoard; -#endif + cv::Ptr detector; int mNeededFramesNum; unsigned mDelayBetweenCaptures; diff --git a/apps/interactive-calibration/main.cpp b/apps/interactive-calibration/main.cpp index 2a1750fcb0..4fe43f6070 100644 --- a/apps/interactive-calibration/main.cpp +++ b/apps/interactive-calibration/main.cpp @@ -8,9 +8,6 @@ #include #include -#ifdef HAVE_OPENCV_ARUCO -#include -#endif #include #include @@ -106,11 +103,6 @@ int main(int argc, char** argv) captureParameters capParams = paramsController.getCaptureParameters(); internalParameters intParams = paramsController.getInternalParameters(); -#ifndef HAVE_OPENCV_ARUCO - if(capParams.board == chAruco) - CV_Error(cv::Error::StsNotImplemented, "Aruco module is disabled in current build configuration." - " Consider usage of another calibration pattern\n"); -#endif cv::TermCriteria solverTermCrit = cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS, intParams.solverMaxIters, intParams.solverEps); @@ -171,29 +163,12 @@ int main(int argc, char** argv) globalData->imageSize = pipeline->getImageSize(); calibrationFlags = controller->getNewFlags(); - if(capParams.board != chAruco) { - globalData->totalAvgErr = - cv::calibrateCamera(globalData->objectPoints, globalData->imagePoints, - globalData->imageSize, globalData->cameraMatrix, - globalData->distCoeffs, cv::noArray(), cv::noArray(), - globalData->stdDeviations, cv::noArray(), globalData->perViewErrors, - calibrationFlags, solverTermCrit); - } - else { -#ifdef HAVE_OPENCV_ARUCO - cv::Ptr dictionary = - cv::aruco::getPredefinedDictionary(cv::aruco::PREDEFINED_DICTIONARY_NAME(capParams.charucoDictName)); - cv::Ptr charucoboard = - cv::aruco::CharucoBoard::create(capParams.boardSize.width, capParams.boardSize.height, - capParams.charucoSquareLength, capParams.charucoMarkerSize, dictionary); - globalData->totalAvgErr = - cv::aruco::calibrateCameraCharuco(globalData->allCharucoCorners, globalData->allCharucoIds, - charucoboard, globalData->imageSize, - globalData->cameraMatrix, globalData->distCoeffs, - cv::noArray(), cv::noArray(), globalData->stdDeviations, cv::noArray(), - globalData->perViewErrors, calibrationFlags, solverTermCrit); -#endif - } + globalData->totalAvgErr = + cv::calibrateCamera(globalData->objectPoints, globalData->imagePoints, + globalData->imageSize, globalData->cameraMatrix, + globalData->distCoeffs, cv::noArray(), cv::noArray(), + globalData->stdDeviations, cv::noArray(), globalData->perViewErrors, + calibrationFlags, solverTermCrit); dataController->updateUndistortMap(); dataController->printParametersToConsole(std::cout); controller->updateState(); diff --git a/apps/interactive-calibration/parametersController.cpp b/apps/interactive-calibration/parametersController.cpp index ebec850323..eab183ab69 100644 --- a/apps/interactive-calibration/parametersController.cpp +++ b/apps/interactive-calibration/parametersController.cpp @@ -109,6 +109,7 @@ bool calib::parametersController::loadFromParser(cv::CommandLineParser &parser) std::string templateType = parser.get("t"); + if(templateType.find("symcircles", 0) == 0) { mCapParams.board = CirclesGrid; mCapParams.boardSize = cv::Size(4, 11); @@ -127,7 +128,7 @@ bool calib::parametersController::loadFromParser(cv::CommandLineParser &parser) } else if(templateType.find("charuco", 0) == 0) { mCapParams.board = chAruco; - mCapParams.boardSize = cv::Size(6, 8); + mCapParams.boardSize = cv::Size(5, 7); mCapParams.charucoDictName = 0; mCapParams.charucoSquareLength = 200; mCapParams.charucoMarkerSize = 100; diff --git a/cmake/FindCUDA.cmake b/cmake/FindCUDA.cmake index 37d557a792..ca04bb4a4e 100644 --- a/cmake/FindCUDA.cmake +++ b/cmake/FindCUDA.cmake @@ -287,7 +287,7 @@ # Only available for CUDA version 5.5+. # CUDA_npps_LIBRARY -- NVIDIA Performance Primitives lib (signal processing). # Only available for CUDA version 5.5+. -# CUDA_nvcuvenc_LIBRARY -- CUDA Video Encoder library. +# CUDA_nvencodeapi_LIBRARY -- CUDA Video Encoder library. # Only available for CUDA version 3.2+. # Windows only. # CUDA_nvcuvid_LIBRARY -- CUDA Video Decoder library. @@ -530,7 +530,7 @@ macro(cuda_unset_include_and_libraries) unset(CUDA_nppc_LIBRARY CACHE) unset(CUDA_nppi_LIBRARY CACHE) unset(CUDA_npps_LIBRARY CACHE) - unset(CUDA_nvcuvenc_LIBRARY CACHE) + unset(CUDA_nvencodeapi_LIBRARY CACHE) unset(CUDA_nvcuvid_LIBRARY CACHE) endmacro() @@ -790,7 +790,7 @@ if(NOT CUDA_VERSION VERSION_LESS "3.2") find_cuda_helper_libs(cusparse) find_cuda_helper_libs(curand) if (WIN32) - find_cuda_helper_libs(nvcuvenc) + find_cuda_helper_libs(nvencodeapi) find_cuda_helper_libs(nvcuvid) endif() endif() diff --git a/cmake/FindCUDNN.cmake b/cmake/FindCUDNN.cmake index 195781b957..df64db39e6 100644 --- a/cmake/FindCUDNN.cmake +++ b/cmake/FindCUDNN.cmake @@ -78,12 +78,7 @@ if(CUDNN_INCLUDE_DIR) string(REGEX MATCH "define CUDNN_PATCHLEVEL ([0-9]+)" _ "${CUDNN_H_CONTENTS}") set(CUDNN_VERSION_PATCH ${CMAKE_MATCH_1} CACHE INTERNAL "") - set(CUDNN_VERSION - "${CUDNN_VERSION_MAJOR}.${CUDNN_VERSION_MINOR}.${CUDNN_VERSION_PATCH}" - CACHE - STRING - "cuDNN version" - ) + set(CUDNN_VERSION "${CUDNN_VERSION_MAJOR}.${CUDNN_VERSION_MINOR}.${CUDNN_VERSION_PATCH}") unset(CUDNN_H_CONTENTS) endif() diff --git a/cmake/OpenCVCompilerOptimizations.cmake b/cmake/OpenCVCompilerOptimizations.cmake index cc0b5f8216..28929c3890 100644 --- a/cmake/OpenCVCompilerOptimizations.cmake +++ b/cmake/OpenCVCompilerOptimizations.cmake @@ -20,6 +20,9 @@ # VSX (always available on Power8) # VSX3 (always available on Power9) +# RISC-V arch: +# RVV + # CPU_{opt}_SUPPORTED=ON/OFF - compiler support (possibly with additional flag) # CPU_{opt}_IMPLIES= # CPU_{opt}_FORCE= - subset of "implies" list @@ -50,6 +53,7 @@ list(APPEND CPU_ALL_OPTIMIZATIONS NEON VFPV3 FP16 NEON_DOTPROD) list(APPEND CPU_ALL_OPTIMIZATIONS MSA) list(APPEND CPU_ALL_OPTIMIZATIONS VSX VSX3) list(APPEND CPU_ALL_OPTIMIZATIONS RVV) +list(APPEND CPU_ALL_OPTIMIZATIONS LASX) list(REMOVE_DUPLICATES CPU_ALL_OPTIMIZATIONS) ocv_update(CPU_VFPV3_FEATURE_ALIAS "") @@ -103,8 +107,6 @@ ocv_optimization_process_obsolete_option(ENABLE_NEON NEON OFF) ocv_optimization_process_obsolete_option(ENABLE_VSX VSX ON) -ocv_optimization_process_obsolete_option(ENABLE_RVV RVV OFF) - macro(ocv_is_optimization_in_list resultvar check_opt) set(__checked "") set(__queue ${ARGN}) @@ -374,11 +376,24 @@ elseif(PPC64LE) set(CPU_BASELINE "VSX" CACHE STRING "${HELP_CPU_BASELINE}") elseif(RISCV) + option(RISCV_RVV_SCALABLE "Use scalable RVV API on RISC-V" ON) + ocv_update(CPU_RVV_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_rvv.cpp") ocv_update(CPU_KNOWN_OPTIMIZATIONS "RVV") - ocv_update(CPU_RVV_FLAGS_ON "") - set(CPU_DISPATCH "RVV" CACHE STRING "${HELP_CPU_DISPATCH}") - set(CPU_BASELINE "RVV" CACHE STRING "${HELP_CPU_BASELINE}") + ocv_update(CPU_RVV_FLAGS_ON "-march=rv64gcv") + if(RISCV_RVV_SCALABLE) + set(CPU_RVV_FLAGS_ON "${CPU_RVV_FLAGS_ON} -DCV_RVV_SCALABLE") + endif() + ocv_update(CPU_RVV_FLAGS_CONFLICT "-march=[^ ]*") + + set(CPU_DISPATCH "" CACHE STRING "${HELP_CPU_DISPATCH}") + set(CPU_BASELINE "DETECT" CACHE STRING "${HELP_CPU_BASELINE}") + +elseif(LOONGARCH64) + ocv_update(CPU_LASX_TEST_FILE "${OpenCV_SOURCE_DIR}/cmake/checks/cpu_lasx.cpp") + ocv_update(CPU_KNOWN_OPTIMIZATIONS "LASX") + ocv_update(CPU_LASX_FLAGS_ON "-mlasx") + set(CPU_BASELINE "LASX" CACHE STRING "${HELP_CPU_BASELINE}") endif() @@ -691,7 +706,7 @@ macro(ocv_compiler_optimization_process_sources SOURCES_VAR_NAME LIBS_VAR_NAME T if(fname_LOWER MATCHES "\\.${OPT_LOWER}\\.cpp$") #message("${fname} BASELINE-${OPT}") set(__opt_found 1) - list(APPEND __result "${fname}") + list(APPEND __result_${OPT} "${fname}") break() endif() endforeach() @@ -725,7 +740,7 @@ macro(ocv_compiler_optimization_process_sources SOURCES_VAR_NAME LIBS_VAR_NAME T endif() endforeach() - foreach(OPT ${CPU_DISPATCH_FINAL}) + foreach(OPT ${CPU_BASELINE_FINAL} ${CPU_DISPATCH_FINAL}) if(__result_${OPT}) #message("${OPT}: ${__result_${OPT}}") if(CMAKE_GENERATOR MATCHES "^Visual" diff --git a/cmake/OpenCVDetectCUDA.cmake b/cmake/OpenCVDetectCUDA.cmake index acc101396c..a3d987a2b8 100644 --- a/cmake/OpenCVDetectCUDA.cmake +++ b/cmake/OpenCVDetectCUDA.cmake @@ -10,6 +10,10 @@ endif() #set(OPENCV_CMAKE_CUDA_DEBUG 1) +if(CUDA_TOOLKIT_ROOT_DIR) + set(CUDA_TOOLKIT_TARGET_DIR ${CUDA_TOOLKIT_ROOT_DIR}) +endif() + if(((NOT CMAKE_VERSION VERSION_LESS "3.9.0") # requires https://gitlab.kitware.com/cmake/cmake/merge_requests/663 OR OPENCV_CUDA_FORCE_EXTERNAL_CMAKE_MODULE) AND NOT OPENCV_CUDA_FORCE_BUILTIN_CMAKE_MODULE) @@ -28,6 +32,7 @@ else() endif() if(CUDA_FOUND) + unset(CUDA_nvcuvenc_LIBRARY CACHE) set(HAVE_CUDA 1) if(NOT CUDA_VERSION VERSION_LESS 11.0) # CUDA 11.0 removes nppicom @@ -53,7 +58,7 @@ if(CUDA_FOUND) endif() endif() - if(WITH_NVCUVID) + if(WITH_NVCUVID OR WITH_NVCUVENC) macro(ocv_cuda_SEARCH_NVCUVID_HEADER _filename _result) # place header file under CUDA_TOOLKIT_TARGET_DIR or CUDA_TOOLKIT_ROOT_DIR find_path(_header_result @@ -71,18 +76,25 @@ if(CUDA_FOUND) endif() unset(_header_result CACHE) endmacro() - ocv_cuda_SEARCH_NVCUVID_HEADER("nvcuvid.h" HAVE_NVCUVID_HEADER) - ocv_cuda_SEARCH_NVCUVID_HEADER("dynlink_nvcuvid.h" HAVE_DYNLINK_NVCUVID_HEADER) - find_cuda_helper_libs(nvcuvid) - if(WIN32) - find_cuda_helper_libs(nvcuvenc) + if(WITH_NVCUVID) + ocv_cuda_SEARCH_NVCUVID_HEADER("nvcuvid.h" HAVE_NVCUVID_HEADER) + ocv_cuda_SEARCH_NVCUVID_HEADER("dynlink_nvcuvid.h" HAVE_DYNLINK_NVCUVID_HEADER) + find_cuda_helper_libs(nvcuvid) + if(CUDA_nvcuvid_LIBRARY AND (${HAVE_NVCUVID_HEADER} OR ${HAVE_DYNLINK_NVCUVID_HEADER})) + # make sure to have both header and library before enabling + set(HAVE_NVCUVID 1) + endif() endif() - if(CUDA_nvcuvid_LIBRARY AND (${HAVE_NVCUVID_HEADER} OR ${HAVE_DYNLINK_NVCUVID_HEADER})) - # make sure to have both header and library before enabling - set(HAVE_NVCUVID 1) - endif() - if(CUDA_nvcuvenc_LIBRARY) - set(HAVE_NVCUVENC 1) + if(WITH_NVCUVENC) + ocv_cuda_SEARCH_NVCUVID_HEADER("nvEncodeAPI.h" HAVE_NVCUVENC_HEADER) + if(WIN32) + find_cuda_helper_libs(nvencodeapi) + else() + find_cuda_helper_libs(nvidia-encode) + endif() + if((CUDA_nvencodeapi_LIBRARY OR CUDA_nvidia-encode_LIBRARY) AND ${HAVE_NVCUVENC_HEADER}) + set(HAVE_NVCUVENC 1) + endif() endif() endif() @@ -342,6 +354,8 @@ if(CUDA_FOUND) set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} --use_fast_math) endif() + OCV_OPTION(CUDA_ENABLE_DELAYLOAD "Enable delayed loading of CUDA DLLs" OFF VISIBLE_IF MSVC AND (CMAKE_GENERATOR MATCHES "Visual Studio")) + mark_as_advanced(CUDA_BUILD_CUBIN CUDA_BUILD_EMULATION CUDA_VERBOSE_BUILD CUDA_SDK_ROOT_DIR) macro(ocv_cuda_filter_options) @@ -413,6 +427,10 @@ if(CUDA_FOUND) set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcompiler -fno-finite-math-only) endif() + if(WIN32 AND NOT (CUDA_VERSION VERSION_LESS "11.2")) + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcudafe --display_error_number --diag-suppress 1394,1388) + endif() + if(CMAKE_CROSSCOMPILING AND (ARM OR AARCH64)) set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xlinker --unresolved-symbols=ignore-in-shared-libs) endif() @@ -537,4 +555,15 @@ if(HAVE_CUDA) set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} ${CMAKE_LIBRARY_PATH_FLAG}${p}) endif() endforeach() + + if(MSVC AND CUDA_ENABLE_DELAYLOAD) + file(GLOB CUDA_DLLS "${CUDA_TOOLKIT_ROOT_DIR}/bin/*.dll") + foreach(d ${CUDA_DLLS}) + cmake_path(GET "d" FILENAME DLL_NAME) + if(NOT ${DLL_NAME} MATCHES "cudart") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DELAYLOAD:${DLL_NAME}") + endif() + endforeach() + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /IGNORE:4199") + endif() endif() diff --git a/cmake/OpenCVDetectCXXCompiler.cmake b/cmake/OpenCVDetectCXXCompiler.cmake index 7f229cde96..8fe89b3fe0 100644 --- a/cmake/OpenCVDetectCXXCompiler.cmake +++ b/cmake/OpenCVDetectCXXCompiler.cmake @@ -100,6 +100,8 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(mips.*|MIPS.*)") set(MIPS 1) elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(riscv.*|RISCV.*)") set(RISCV 1) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(loongarch64.*|LOONGARCH64.*)") + set(LOONGARCH64 1) else() if(NOT OPENCV_SUPPRESS_MESSAGE_UNRECOGNIZED_SYSTEM_PROCESSOR) message(WARNING "OpenCV: unrecognized target processor configuration") diff --git a/cmake/OpenCVDetectDirectX.cmake b/cmake/OpenCVDetectDirectX.cmake index fbe4a71185..fa1bac235e 100644 --- a/cmake/OpenCVDetectDirectX.cmake +++ b/cmake/OpenCVDetectDirectX.cmake @@ -2,15 +2,18 @@ if(WIN32) try_compile(__VALID_DIRECTX "${OpenCV_BINARY_DIR}" "${OpenCV_SOURCE_DIR}/cmake/checks/directx.cpp" + LINK_LIBRARIES d3d11 OUTPUT_VARIABLE TRY_OUT ) if(NOT __VALID_DIRECTX) + message(STATUS "No support for DirectX (install Windows 8 SDK)") return() endif() try_compile(__VALID_DIRECTX_NV12 "${OpenCV_BINARY_DIR}" "${OpenCV_SOURCE_DIR}/cmake/checks/directx.cpp" COMPILE_DEFINITIONS "-DCHECK_NV12" + LINK_LIBRARIES d3d11 OUTPUT_VARIABLE TRY_OUT ) if(__VALID_DIRECTX_NV12) diff --git a/cmake/OpenCVFindCANN.cmake b/cmake/OpenCVFindCANN.cmake new file mode 100644 index 0000000000..b0b8e35c6b --- /dev/null +++ b/cmake/OpenCVFindCANN.cmake @@ -0,0 +1,109 @@ +ocv_check_environment_variables(CANN_INSTALL_DIR) + +if("cann${CANN_INSTALL_DIR}" STREQUAL "cann" AND DEFINED ENV{ASCEND_TOOLKIT_HOME}) + set(CANN_INSTALL_DIR $ENV{ASCEND_TOOLKIT_HOME}) + message(STATUS "CANN: updated CANN_INSTALL_DIR from ASCEND_TOOLKIT_HOME=$ENV{ASCEND_TOOLKIT_HOME}") +endif() + +if(CANN_INSTALL_DIR) + # Supported platforms: x86-64, arm64 + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64") + else() + set(HAVE_CANN OFF) + message(STATUS "CANN: CANN toolkit supports x86-64 and arm64 but not ${CMAKE_SYSTEM_PROCESSOR}. Turning off HAVE_CANN") + return() + endif() + + # Supported OS: linux (because of we need fork() to build models in child process) + # done via checks in cann.cpp + # FIXME: remove the check if a better model building solution is found + + # include + set(incs_cann "${CANN_INSTALL_DIR}/include") + list(APPEND incs_cann "${CANN_INSTALL_DIR}/opp") + + # libs + # * libascendcl.so + set(lib_ascendcl "${CANN_INSTALL_DIR}/acllib/lib64") + find_library(found_lib_ascendcl NAMES ascendcl PATHS ${lib_ascendcl} NO_DEFAULT_PATH) + if(found_lib_ascendcl) + set(lib_ascendcl ${found_lib_ascendcl}) + message(STATUS "CANN: libascendcl.so is found at ${lib_ascendcl}") + else() + message(STATUS "CANN: Missing libascendcl.so. Turning off HAVE_CANN") + set(HAVE_CANN OFF) + return() + endif() + # * libgraph.so + set(lib_graph "${CANN_INSTALL_DIR}/compiler/lib64") + find_library(found_lib_graph NAMES graph PATHS ${lib_graph} NO_DEFAULT_PATH) + if(found_lib_graph) + set(lib_graph ${found_lib_graph}) + message(STATUS "CANN: libgraph.so is found at ${lib_graph}") + else() + message(STATUS "CANN: Missing libgraph.so. Turning off HAVE_CANN") + set(HAVE_CANN OFF) + return() + endif() + # * libge_compiler.so + set(lib_ge_compiler "${CANN_INSTALL_DIR}/compiler/lib64") + find_library(found_lib_ge_compiler NAMES ge_compiler PATHS ${lib_ge_compiler} NO_DEFAULT_PATH) + if(found_lib_ge_compiler) + set(lib_ge_compiler ${found_lib_ge_compiler}) + message(STATUS "CANN: libge_compiler.so is found at ${lib_ge_compiler}") + else() + message(STATUS "CANN: Missing libge_compiler.so. Turning off HAVE_CANN") + set(HAVE_CANN OFF) + return() + endif() + # * libopsproto.so + set(lib_opsproto "${CANN_INSTALL_DIR}/opp/op_proto/built-in") + find_library(found_lib_opsproto NAMES opsproto PATHS ${lib_opsproto} NO_DEFAULT_PATH) + if(found_lib_opsproto) + set(lib_opsproto ${found_lib_opsproto}) + message(STATUS "CANN: libopsproto.so is found at ${lib_opsproto}") + else() + message(STATUS "CANN: Missing libopsproto.so. Turning off HAVE_CANN") + set(HAVE_CANN OFF) + return() + endif() + + + set(libs_cann "") + list(APPEND libs_cann ${lib_ascendcl}) + list(APPEND libs_cann ${lib_opsproto}) + list(APPEND libs_cann ${lib_graph}) + list(APPEND libs_cann ${lib_ge_compiler}) + + try_compile(VALID_ASCENDCL + "${OpenCV_BINARY_DIR}" + "${OpenCV_SOURCE_DIR}/cmake/checks/cann.cpp" + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${incs_cann}" + "-DLINK_LIBRARIES:STRING=${libs_cann}" + OUTPUT_VARIABLE ASCEND_TRY_OUT) + + if(NOT ${VALID_ASCENDCL}) + message(WARNING "Cannot use CANN") + set(HAVE_CANN OFF) + return() + endif() + + set(HAVE_CANN ON) +endif() + +if(HAVE_CANN) + set(CANN_INCLUDE_DIRS ${incs_cann}) + set(CANN_LIBRARIES ${libs_cann}) + ocv_add_external_target(cann "${CANN_INCLUDE_DIRS}" "${CANN_LIBRARIES}" "HAVE_CANN") + ocv_warnings_disable(CMAKE_C_FLAGS -Wignored-qualifiers) + ocv_warnings_disable(CMAKE_CXX_FLAGS -Wignored-qualifiers) +endif() + +MARK_AS_ADVANCED( + incs_cann + libs_cann + lib_ascendcl + lib_graph + lib_ge_compiler +) diff --git a/cmake/OpenCVFindLibsGrfmt.cmake b/cmake/OpenCVFindLibsGrfmt.cmake index 00886cc131..4e8a1de17a 100644 --- a/cmake/OpenCVFindLibsGrfmt.cmake +++ b/cmake/OpenCVFindLibsGrfmt.cmake @@ -221,8 +221,21 @@ if(WITH_JASPER AND NOT HAVE_OPENJPEG) endif() endif() +if(WITH_SPNG) + set(SPNG_LIBRARY libspng CACHE INTERNAL "") + set(SPNG_LIBRARIES ${SPNG_LIBRARY}) + add_subdirectory("${OpenCV_SOURCE_DIR}/3rdparty/libspng") + set(SPNG_INCLUDE_DIR "${${SPNG_LIBRARY}_SOURCE_DIR}" CACHE INTERNAL "") + set(SPNG_DEFINITIONS "") + ocv_parse_header("${SPNG_INCLUDE_DIR}/spng.h" SPNG_VERSION_LINES SPNG_VERSION_MAJOR SPNG_VERSION_MINOR SPNG_VERSION_PATCH) + + set(HAVE_SPNG YES) + set(SPNG_VERSION "${SPNG_VERSION_MAJOR}.${SPNG_VERSION_MINOR}.${SPNG_VERSION_PATCH}") + message(STATUS "imgcodecs: PNG codec will use SPNG, version: ${SPNG_VERSION} ") +endif() + # --- libpng (optional, should be searched after zlib) --- -if(WITH_PNG) +if(NOT HAVE_SPNG AND WITH_PNG) if(BUILD_PNG) ocv_clear_vars(PNG_FOUND) else() @@ -254,6 +267,7 @@ if(WITH_PNG) set(PNG_VERSION "${PNG_LIBPNG_VER_MAJOR}.${PNG_LIBPNG_VER_MINOR}.${PNG_LIBPNG_VER_RELEASE}") endif() + # --- OpenEXR (optional) --- if(WITH_OPENEXR) ocv_clear_vars(HAVE_OPENEXR) diff --git a/cmake/OpenCVFindTIMVX.cmake b/cmake/OpenCVFindTIMVX.cmake index 339f726bd9..ce084e45fb 100644 --- a/cmake/OpenCVFindTIMVX.cmake +++ b/cmake/OpenCVFindTIMVX.cmake @@ -30,7 +30,7 @@ if(TIMVX_INSTALL_DIR AND NOT BUILD_TIMVX) set(BUILD_TIMVX OFF) set(TIMVX_INC_DIR "${TIMVX_INSTALL_DIR}/include" CACHE INTERNAL "TIM-VX include directory") - find_library(TIMVX_LIB "tim-vx" PATHS "${TIMVX_INSTALL_DIR}/lib") + find_library(TIMVX_LIB "tim-vx" PATHS "${TIMVX_INSTALL_DIR}/lib" NO_DEFAULT_PATH) if(TIMVX_LIB) set(TIMVX_FOUND ON) else() diff --git a/cmake/OpenCVGenPkgconfig.cmake b/cmake/OpenCVGenPkgconfig.cmake index e608d61a65..e4c05ed3d9 100644 --- a/cmake/OpenCVGenPkgconfig.cmake +++ b/cmake/OpenCVGenPkgconfig.cmake @@ -103,7 +103,7 @@ add_custom_target(gen-pkgconfig ALL SOURCES "${CMAKE_BINARY_DIR}/unix-install/${ add_dependencies(developer_scripts gen-pkgconfig) -if(UNIX AND NOT ANDROID) +if((UNIX AND NOT ANDROID) OR MINGW) install(FILES ${CMAKE_BINARY_DIR}/unix-install/${OPENCV_PC_FILE_NAME} DESTINATION ${OPENCV_LIB_INSTALL_PATH}/pkgconfig COMPONENT dev) endif() diff --git a/cmake/OpenCVPCHSupport.cmake b/cmake/OpenCVPCHSupport.cmake index 08cd06def4..34a088b839 100644 --- a/cmake/OpenCVPCHSupport.cmake +++ b/cmake/OpenCVPCHSupport.cmake @@ -331,7 +331,8 @@ ENDMACRO(ADD_PRECOMPILED_HEADER) MACRO(GET_NATIVE_PRECOMPILED_HEADER _targetName _input) if(ENABLE_PRECOMPILED_HEADERS) - if(CMAKE_GENERATOR MATCHES "^Visual.*$") + if(CMAKE_GENERATOR MATCHES "^Visual.*$" + AND (CMAKE_VERSION VERSION_LESS "3.16" OR OPENCV_SKIP_CMAKE_BUILTIN_PCH)) # with 3.16+ we use target_precompile_headers set(${_targetName}_pch ${CMAKE_CURRENT_BINARY_DIR}/${_targetName}_pch.cpp) endif() endif() @@ -406,7 +407,9 @@ ENDMACRO(ADD_NATIVE_PRECOMPILED_HEADER) macro(ocv_add_precompiled_header_to_target the_target pch_header) if(PCHSupport_FOUND AND ENABLE_PRECOMPILED_HEADERS AND EXISTS "${pch_header}") - if(CMAKE_GENERATOR MATCHES "^Visual" OR CMAKE_GENERATOR MATCHES Xcode) + if(NOT CMAKE_VERSION VERSION_LESS "3.16" AND NOT OPENCV_SKIP_CMAKE_BUILTIN_PCH) + target_precompile_headers(${the_target} PRIVATE ${pch_header}) + elseif(CMAKE_GENERATOR MATCHES "^Visual" OR CMAKE_GENERATOR MATCHES Xcode) add_native_precompiled_header(${the_target} ${pch_header}) elseif(CV_GCC AND CMAKE_GENERATOR MATCHES "Makefiles|Ninja") add_precompiled_header(${the_target} ${pch_header}) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index 5d49b8a889..a35c285f94 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -868,6 +868,7 @@ macro(ocv_check_modules define) list(APPEND _libs_paths ${CMAKE_MATCH_1}) elseif(IS_ABSOLUTE "${flag}" OR flag STREQUAL "-lstdc++" + OR flag STREQUAL "-latomic" ) list(APPEND _libs "${flag}") elseif(flag MATCHES "^-l(.*)") @@ -1094,6 +1095,18 @@ macro(ocv_list_filterout lst regex) endforeach() endmacro() +# Usage: ocv_list_filterout_ex(list_name regex1 regex2 ...) +macro(ocv_list_filterout_ex lst) + foreach(regex ${ARGN}) + foreach(item ${${lst}}) + if(item MATCHES "${regex}") + list(REMOVE_ITEM ${lst} "${item}") + endif() + endforeach() + endforeach() +endmacro() + + # filter matching elements from the list macro(ocv_list_filter lst regex) set(dst ${ARGN}) diff --git a/cmake/android/android_gradle_projects.cmake b/cmake/android/android_gradle_projects.cmake index e07d26b5bb..4a98421c6a 100644 --- a/cmake/android/android_gradle_projects.cmake +++ b/cmake/android/android_gradle_projects.cmake @@ -3,7 +3,7 @@ set(ANDROID_GRADLE_PLUGIN_VERSION "3.2.1" CACHE STRING "Android Gradle Plugin ve message(STATUS "Android Gradle Plugin version: ${ANDROID_GRADLE_PLUGIN_VERSION}") set(KOTLIN_PLUGIN_VERSION "1.4.10" CACHE STRING "Kotlin Plugin version") -message(STATUS "kotlin Plugin version: ${KOTLIN_GRADLE_PLUGIN_VERSION}") +message(STATUS "Kotlin Plugin version: ${KOTLIN_PLUGIN_VERSION}") if(BUILD_KOTLIN_EXTENSIONS) set(KOTLIN_PLUGIN_DECLARATION "apply plugin: 'kotlin-android'" CACHE STRING "Kotlin Plugin version") @@ -50,9 +50,11 @@ endif() #string(REPLACE "\n" "\n${__spaces}" ANDROID_ABI_FILTER "${__spaces}${ANDROID_BUILD_ABI_FILTER}") #string(REPLACE REGEX "[ ]+$" "" ANDROID_ABI_FILTER "${ANDROID_ABI_FILTER}") set(ANDROID_ABI_FILTER "${ANDROID_BUILD_ABI_FILTER}") +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") 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) @@ -80,6 +82,15 @@ foreach(fname ${GRADLE_WRAPPER_FILES}) install(FILES "${OpenCV_SOURCE_DIR}/platforms/android/gradle-wrapper/${fname}" DESTINATION "${ANDROID_INSTALL_SAMPLES_DIR}/${__dir}" COMPONENT samples ${__permissions}) endforeach() +# force reusing of the same CMake version +if(NOT OPENCV_SKIP_ANDROID_FORCE_CMAKE) + if(NOT DEFINED _CMAKE_INSTALL_DIR) + get_filename_component(_CMAKE_INSTALL_DIR "${CMAKE_ROOT}" PATH) + get_filename_component(_CMAKE_INSTALL_DIR "${_CMAKE_INSTALL_DIR}" PATH) + endif() + ocv_update_file("${ANDROID_BUILD_BASE_DIR}/local.properties" "cmake.dir=${_CMAKE_INSTALL_DIR}") +endif() + file(WRITE "${ANDROID_BUILD_BASE_DIR}/settings.gradle" " include ':opencv' ") @@ -94,6 +105,7 @@ include ':opencv' project(':opencv').projectDir = new File(opencvsdk + '/sdk') ") +ocv_check_environment_variables(OPENCV_GRADLE_VERBOSE_OPTIONS) macro(add_android_project target path) get_filename_component(__dir "${path}" NAME) diff --git a/cmake/checks/cann.cpp b/cmake/checks/cann.cpp new file mode 100644 index 0000000000..08e463c4e5 --- /dev/null +++ b/cmake/checks/cann.cpp @@ -0,0 +1,20 @@ +#include +#include // fork() +#include + +int main(int /*argc*/, char** /*argv*/) +{ + int ret = aclInit(NULL); + if (ret != 0) + { + std::cerr << "Failed to initialize Ascend, ret = " << ret; + } + + ret = aclFinalize(); + if (ret != 0) + { + std::cerr << "Failed to de-initialize Ascend, ret = " << ret; + } + + return 0; +} diff --git a/cmake/checks/cpu_lasx.cpp b/cmake/checks/cpu_lasx.cpp new file mode 100644 index 0000000000..9d3b2a8725 --- /dev/null +++ b/cmake/checks/cpu_lasx.cpp @@ -0,0 +1,23 @@ +#include + +#if defined(__loongarch_asx) +# include +# define CV_LASX 1 +#endif + +#if defined CV_LASX +int test() +{ + const float src[] = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f }; + v8f32 val = (v8f32)__lasx_xvld((const float*)(src), 0); + return __lasx_xvpickve2gr_w(__lasx_xvftint_w_s (val), 7); +} +#else +#error "LASX is not supported" +#endif + +int main() +{ + printf("%d\n", test()); + return 0; +} diff --git a/cmake/checks/directx.cpp b/cmake/checks/directx.cpp index c617ac341a..be687c24f8 100644 --- a/cmake/checks/directx.cpp +++ b/cmake/checks/directx.cpp @@ -1,7 +1,6 @@ #include #include -#pragma comment (lib, "d3d11.lib") HINSTANCE g_hInst = NULL; D3D_DRIVER_TYPE g_driverType = D3D_DRIVER_TYPE_NULL; diff --git a/cmake/mirrors/gitcode.cmake b/cmake/mirrors/gitcode.cmake index abd7a29be4..c9d41e7458 100644 --- a/cmake/mirrors/gitcode.cmake +++ b/cmake/mirrors/gitcode.cmake @@ -20,10 +20,10 @@ ocv_update(TBB_PKG_NAME_GITCODE "tbb-${TBB_RELEASE_GITCODE}") ocv_update(TBB_PKG_MD5_GITCODE 4eeafdf16a90cb66e39a31c8d6c6804e) ocv_update(TBB_PKG_MD5_ORIGINAL 5af6f6c2a24c2043e62e47205e273b1f) # same as OPENCV_TBB_RELEASE_MD5 for TBB release of v2020.2 # ADE -ocv_update(ADE_RELEASE_GITCODE "v0.1.1f") +ocv_update(ADE_RELEASE_GITCODE "v0.1.2a") ocv_update(ADE_PKG_NAME_GITCODE "ade-${ADE_RELEASE_GITCODE}") -ocv_update(ADE_PKG_MD5_GITCODE c12909e0ccfa93138c820ba91ff37b3c) -ocv_update(ADE_PKG_MD5_ORIGINAL b624b995ec9c439cbc2e9e6ee940d3a2) # same as ade_md5 for ADE release of v0.1.1f +ocv_update(ADE_PKG_MD5_GITCODE 6c8015a886a98fd8a67635431fa171d8) +ocv_update(ADE_PKG_MD5_ORIGINAL fa4b3e25167319cb0fa9432ef8281945) # same as ade_md5 for ADE release of v0.1.2a # # Replace download links for packages in opencv/opencv_3rdparty: @@ -57,13 +57,14 @@ macro(ocv_download_url_gitcode_archive_commit_id) message(WARNING "Package ${DL_ID} from mirror gitcode.net is outdated and will be downloaded from github.com instead.") endif() endmacro() -macro(ocv_download_url_gitcode_archive_release) +macro(ocv_download_url_gitcode_archive_release SUBDIR) if(DL_HASH STREQUAL "${${DL_ID}_PKG_MD5_ORIGINAL}") string(REPLACE "/" ";" DL_URL_split ${DL_URL}) list(GET DL_URL_split 3 __OWNER) list(GET DL_URL_split 4 __REPO_NAME) set(DL_URL "https://gitcode.net/${__OWNER}/${__REPO_NAME}/-/archive/${${DL_ID}_RELEASE_GITCODE}/${__REPO_NAME}-") set(DL_HASH "${${DL_ID}_PKG_MD5_GITCODE}") + set(${SUBDIR} "${${DL_ID}_PKG_NAME_GITCODE}" PARENT_SCOPE) else() message(WARNING "Package ${DL_ID} from mirror gitcode.net is outdated and will be downloaded from github.com instead.") endif() @@ -76,11 +77,9 @@ elseif(DL_ID STREQUAL "wechat_qrcode") elseif((DL_ID STREQUAL "TENGINE") OR (DL_ID STREQUAL "NVIDIA_OPTICAL_FLOW") OR (DL_ID STREQUAL "TIM-VX")) ocv_download_url_gitcode_archive_commit_id() elseif(DL_ID STREQUAL "TBB") - ocv_download_url_gitcode_archive_release() - set(OPENCV_TBB_SUBDIR "${TBB_PKG_NAME_GITCODE}" PARENT_SCOPE) + ocv_download_url_gitcode_archive_release(OPENCV_TBB_SUBDIR) elseif(DL_ID STREQUAL "ADE") - ocv_download_url_gitcode_archive_release() - set(ade_subdir "${ADE_PKG_NAME_GITCODE}" PARENT_SCOPE) + ocv_download_url_gitcode_archive_release(ade_subdir) else() message(STATUS "ocv_download: Unknown download ID ${DL_ID} for using mirror gitcode.net. Use original source instead.") endif() diff --git a/cmake/templates/OpenCVConfig-CUDA.cmake.in b/cmake/templates/OpenCVConfig-CUDA.cmake.in index e71c9e2e31..25a20556ec 100644 --- a/cmake/templates/OpenCVConfig-CUDA.cmake.in +++ b/cmake/templates/OpenCVConfig-CUDA.cmake.in @@ -5,7 +5,7 @@ set(OpenCV_CUDA_VERSION "@CUDA_VERSION_STRING@") set(OpenCV_USE_CUBLAS "@HAVE_CUBLAS@") set(OpenCV_USE_CUFFT "@HAVE_CUFFT@") set(OpenCV_USE_NVCUVID "@HAVE_NVCUVID@") - +set(OpenCV_USE_NVCUVENC "@HAVE_NVCUVENC@") set(OpenCV_CUDNN_VERSION "@CUDNN_VERSION@") set(OpenCV_USE_CUDNN "@HAVE_CUDNN@") @@ -36,14 +36,6 @@ if(OpenCV_USE_CUFFT) list(APPEND OpenCV_CUDA_LIBS_ABSPATH ${CUDA_CUFFT_LIBRARIES}) endif() -if(OpenCV_USE_NVCUVID) - list(APPEND OpenCV_CUDA_LIBS_ABSPATH ${CUDA_nvcuvid_LIBRARIES}) -endif() - -if(WIN32) - list(APPEND OpenCV_CUDA_LIBS_ABSPATH ${CUDA_nvcuvenc_LIBRARIES}) -endif() - set(OpenCV_CUDA_LIBS_RELPATH "") foreach(l ${OpenCV_CUDA_LIBS_ABSPATH}) get_filename_component(_tmp ${l} PATH) diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index cb936612bd..db2d8792d6 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -106,6 +106,9 @@ /* PNG codec */ #cmakedefine HAVE_PNG +/* PNG codec */ +#cmakedefine HAVE_SPNG + /* Posix threads (pthreads) */ #cmakedefine HAVE_PTHREAD diff --git a/data/haarcascades/haarcascade_licence_plate_rus_16stages.xml b/data/haarcascades/haarcascade_license_plate_rus_16stages.xml similarity index 100% rename from data/haarcascades/haarcascade_licence_plate_rus_16stages.xml rename to data/haarcascades/haarcascade_license_plate_rus_16stages.xml diff --git a/doc/js_tutorials/js_imgproc/js_gradients/js_gradients.markdown b/doc/js_tutorials/js_imgproc/js_gradients/js_gradients.markdown index 21e36a0bd9..e97f93a78f 100644 --- a/doc/js_tutorials/js_imgproc/js_gradients/js_gradients.markdown +++ b/doc/js_tutorials/js_imgproc/js_gradients/js_gradients.markdown @@ -15,7 +15,7 @@ We will see each one of them. ### 1. Sobel and Scharr Derivatives -Sobel operators is a joint Gausssian smoothing plus differentiation operation, so it is more +Sobel operators is a joint Gaussian smoothing plus differentiation operation, so it is more resistant to noise. You can specify the direction of derivatives to be taken, vertical or horizontal (by the arguments, yorder and xorder respectively). You can also specify the size of kernel by the argument ksize. If ksize = -1, a 3x3 Scharr filter is used which gives better results than 3x3 Sobel @@ -97,4 +97,4 @@ Try it -\endhtmlonly \ No newline at end of file +\endhtmlonly diff --git a/doc/js_tutorials/js_setup/js_setup/js_setup.markdown b/doc/js_tutorials/js_setup/js_setup/js_setup.markdown index 2a7a111d8a..26a4e419bd 100644 --- a/doc/js_tutorials/js_setup/js_setup/js_setup.markdown +++ b/doc/js_tutorials/js_setup/js_setup/js_setup.markdown @@ -135,14 +135,7 @@ Building OpenCV.js from Source For example: @code{.bash} - python ./platforms/js/build_js.py build_js --cmake_option="-DOPENCV_EXTRA_MODULES_PATH=opencv_contrib/modules" - @endcode - --# [optional] To enable OpenCV contrib modules append `--cmake_option="-DOPENCV_EXTRA_MODULES_PATH=/path/to/opencv_contrib/modules/"` - - For example: - @code{.bash} - python ./platforms/js/build_js.py build_js --cmake_option="-DOPENCV_EXTRA_MODULES_PATH=opencv_contrib/modules" + emcmake python ./platforms/js/build_js.py build_js --cmake_option="-DOPENCV_EXTRA_MODULES_PATH=opencv_contrib/modules" @endcode -# [optional] To enable WebNN backend, append `--webnn` option. diff --git a/doc/opencv.bib b/doc/opencv.bib index a44af2aec3..a7e11de4f3 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -8,14 +8,18 @@ url = {https://www.doc.ic.ac.uk/~ajd/Publications/alcantarilla_etal_eccv2012.pdf} } @article{ANB13, - author = {Alcantarilla, Pablo F and Nuevo, Jes{\'u}s and Bartoli, Adrien}, + author = {Pablo Fern{\'{a}}ndez Alcantarilla and Jes{\'{u}}s Nuevo and Adrien Bartoli}, + editor = {Tilo Burghardt and Dima Damen and Walterio W. Mayol{-}Cuevas and Majid Mirmehdi}, title = {Fast Explicit Diffusion for Accelerated Features in Nonlinear Scale Spaces}, - year = {2011}, - pages = {1281--1298}, - journal = {Trans. Pattern Anal. Machine Intell}, - volume = {34}, - number = {7}, - url = {http://www.bmva.org/bmvc/2013/Papers/paper0013/paper0013.pdf} + booktitle = {British Machine Vision Conference, {BMVC} 2013, Bristol, UK, September 9-13, 2013}, + pages = {13.1--13.11}, + publisher = {{BMVA} Press}, + year = {2013}, + url = {https://doi.org/10.5244/C.27.13}, + doi = {10.5244/C.27.13}, + timestamp = {Sat, 09 Apr 2022 12:44:13 +0200}, + biburl = {https://dblp.org/rec/conf/bmvc/AlcantarillaNB13.bib}, + bibsource = {dblp computer science bibliography, https://dblp.org} } @inproceedings{Andreff99, author = {Andreff, Nicolas and Horaud, Radu and Espiau, Bernard}, diff --git a/doc/pattern_tools/svgfig.py b/doc/pattern_tools/svgfig.py index 4fa69806b4..37eaf77c83 100755 --- a/doc/pattern_tools/svgfig.py +++ b/doc/pattern_tools/svgfig.py @@ -34,18 +34,6 @@ try: except NameError: xrange = range # Python 3 - -if re.search("windows", platform.system(), re.I): - try: - import _winreg - _default_directory = _winreg.QueryValueEx(_winreg.OpenKey(_winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\Current Version\Explorer\Shell Folders"), "Desktop")[0] -# tmpdir = _winreg.QueryValueEx(_winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Environment"), "TEMP")[0] -# if tmpdir[0:13] != "%USERPROFILE%": -# tmpdir = os.path.expanduser("~") + tmpdir[13:] - except: - _default_directory = os.path.expanduser("~") + os.sep + "Desktop" - _default_fileName = "tmp.svg" _hacks = {} @@ -449,12 +437,9 @@ class SVG: return output - def interpret_fileName(self, fileName=None): - if fileName is None: - fileName = _default_fileName - if re.search("windows", platform.system(), re.I) and not os.path.isabs(fileName): - fileName = _default_directory + os.sep + fileName - return fileName + @staticmethod + def interpret_fileName(fileName=None): + return fileName or _default_fileName def save(self, fileName=None, encoding="utf-8", compresslevel=None): """Save to a file for viewing. Note that svg.save() overwrites the file named _default_fileName. diff --git a/doc/py_tutorials/py_calib3d/py_calibration/py_calibration.markdown b/doc/py_tutorials/py_calib3d/py_calibration/py_calibration.markdown index bba7b90b9f..182f1c845b 100644 --- a/doc/py_tutorials/py_calib3d/py_calibration/py_calibration.markdown +++ b/doc/py_tutorials/py_calib3d/py_calibration/py_calibration.markdown @@ -127,7 +127,7 @@ for fname in images: objpoints.append(objp) corners2 = cv.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria) - imgpoints.append(corners) + imgpoints.append(corners2) # Draw and display the corners cv.drawChessboardCorners(img, (7,6), corners2, ret) 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 2f032eaa57..bd0ac88ef8 100644 --- a/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown +++ b/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown @@ -59,7 +59,7 @@ pixels. Then to calculate the rotation and translation, we use the function, **cv.solvePnPRansac()**. Once we those transformation matrices, we use them to project our **axis points** to the image plane. In simple words, we find the points on image plane corresponding to each of (3,0,0),(0,3,0),(0,0,3) in 3D space. Once we get them, we draw lines from the first corner -to each of these points using our draw() function. Done !!! +to each of these points using our generateImage() function. Done !!! @code{.py} for fname in glob.glob('left*.jpg'): img = cv.imread(fname) @@ -89,9 +89,9 @@ See some results below. Notice that each axis is 3 squares long.: ### Render a Cube -If you want to draw a cube, modify the draw() function and axis points as follows. +If you want to draw a cube, modify the generateImage() function and axis points as follows. -Modified draw() function: +Modified generateImage() function: @code{.py} def draw(img, corners, imgpts): imgpts = np.int32(imgpts).reshape(-1,2) diff --git a/doc/py_tutorials/py_core/py_optimization/py_optimization.markdown b/doc/py_tutorials/py_core/py_optimization/py_optimization.markdown index 61f403bf54..d24613a643 100644 --- a/doc/py_tutorials/py_core/py_optimization/py_optimization.markdown +++ b/doc/py_tutorials/py_core/py_optimization/py_optimization.markdown @@ -14,7 +14,7 @@ So in this chapter, you will learn: Apart from OpenCV, Python also provides a module **time** which is helpful in measuring the time of execution. Another module **profile** helps to get a detailed report on the code, like how much time each function in the code took, how many times the function was called, etc. But, if you are using -IPython, all these features are integrated in an user-friendly manner. We will see some important +IPython, all these features are integrated in a user-friendly manner. We will see some important ones, and for more details, check links in the **Additional Resources** section. Measuring Performance with OpenCV diff --git a/doc/py_tutorials/py_imgproc/py_gradients/py_gradients.markdown b/doc/py_tutorials/py_imgproc/py_gradients/py_gradients.markdown index 9b07d14f50..0a52cd431c 100644 --- a/doc/py_tutorials/py_imgproc/py_gradients/py_gradients.markdown +++ b/doc/py_tutorials/py_imgproc/py_gradients/py_gradients.markdown @@ -17,7 +17,7 @@ We will see each one of them. ### 1. Sobel and Scharr Derivatives -Sobel operators is a joint Gausssian smoothing plus differentiation operation, so it is more +Sobel operators is a joint Gaussian smoothing plus differentiation operation, so it is more resistant to noise. You can specify the direction of derivatives to be taken, vertical or horizontal (by the arguments, yorder and xorder respectively). You can also specify the size of kernel by the argument ksize. If ksize = -1, a 3x3 Scharr filter is used which gives better results than 3x3 Sobel diff --git a/doc/tutorials/dnn/dnn_android/dnn_android.markdown b/doc/tutorials/dnn/dnn_android/dnn_android.markdown index 4eb1ff238e..42b81e44a9 100644 --- a/doc/tutorials/dnn/dnn_android/dnn_android.markdown +++ b/doc/tutorials/dnn/dnn_android/dnn_android.markdown @@ -2,7 +2,7 @@ @tableofcontents -@prev_tutorial{tutorial_dnn_halide_scheduling} +@prev_tutorial{tutorial_dnn_openvino} @next_tutorial{tutorial_dnn_yolo} | | | diff --git a/doc/tutorials/dnn/dnn_halide_scheduling/dnn_halide_scheduling.markdown b/doc/tutorials/dnn/dnn_halide_scheduling/dnn_halide_scheduling.markdown index 4d763b918f..8886923dd9 100644 --- a/doc/tutorials/dnn/dnn_halide_scheduling/dnn_halide_scheduling.markdown +++ b/doc/tutorials/dnn/dnn_halide_scheduling/dnn_halide_scheduling.markdown @@ -3,7 +3,7 @@ @tableofcontents @prev_tutorial{tutorial_dnn_halide} -@next_tutorial{tutorial_dnn_android} +@next_tutorial{tutorial_dnn_openvino} | | | | -: | :- | diff --git a/doc/tutorials/dnn/dnn_openvino/dnn_openvino.markdown b/doc/tutorials/dnn/dnn_openvino/dnn_openvino.markdown new file mode 100644 index 0000000000..57c9840386 --- /dev/null +++ b/doc/tutorials/dnn/dnn_openvino/dnn_openvino.markdown @@ -0,0 +1,28 @@ +OpenCV usage with OpenVINO {#tutorial_dnn_openvino} +===================== + +@prev_tutorial{tutorial_dnn_halide_scheduling} +@next_tutorial{tutorial_dnn_android} + +| | | +| -: | :- | +| Original author | Aleksandr Voron | +| Compatibility | OpenCV == 4.x | + +This tutorial provides OpenCV installation guidelines how to use OpenCV with OpenVINO. + +Since 2021.1.1 release OpenVINO does not provide pre-built OpenCV. +The change does not affect you if you are using OpenVINO runtime directly or OpenVINO samples: it does not have a strong dependency to OpenCV. +However, if you are using Open Model Zoo demos or OpenVINO runtime as OpenCV DNN backend you need to get the OpenCV build. + +There are 2 approaches how to get OpenCV: + +- Install pre-built OpenCV from another sources: system repositories, pip, conda, homebrew. Generic pre-built OpenCV package may have several limitations: + - OpenCV version may be out-of-date + - OpenCV may not contain G-API module with enabled OpenVINO support (e.g. some OMZ demos use G-API functionality) + - OpenCV may not be optimized for modern hardware (default builds need to cover wide range of hardware) + - OpenCV may not support Intel TBB, Intel Media SDK + - OpenCV DNN module may not use OpenVINO as an inference backend +- Build OpenCV from source code against specific version of OpenVINO. This approach solves the limitations mentioned above. + +The instruction how to follow both approaches is provided in [OpenCV wiki](https://github.com/opencv/opencv/wiki/BuildOpenCV4OpenVINO). diff --git a/doc/tutorials/dnn/table_of_content_dnn.markdown b/doc/tutorials/dnn/table_of_content_dnn.markdown index 3f74826dac..e878eb2357 100644 --- a/doc/tutorials/dnn/table_of_content_dnn.markdown +++ b/doc/tutorials/dnn/table_of_content_dnn.markdown @@ -4,6 +4,7 @@ Deep Neural Networks (dnn module) {#tutorial_table_of_content_dnn} - @subpage tutorial_dnn_googlenet - @subpage tutorial_dnn_halide - @subpage tutorial_dnn_halide_scheduling +- @subpage tutorial_dnn_openvino - @subpage tutorial_dnn_android - @subpage tutorial_dnn_yolo - @subpage tutorial_dnn_javascript diff --git a/doc/tutorials/features2d/homography/homography.markdown b/doc/tutorials/features2d/homography/homography.markdown index 0132822928..be4d611d2c 100644 --- a/doc/tutorials/features2d/homography/homography.markdown +++ b/doc/tutorials/features2d/homography/homography.markdown @@ -418,10 +418,18 @@ homography from camera displacement: The homography matrices are similar. If we compare the image 1 warped using both homography matrices: -![Left: image warped using the homography estimated. Right: using the homography computed from the camera displacement](images/homography_camera_displacement_compare.jpg) +![Left: image warped using the estimated homography. Right: using the homography computed from the camera displacement.](images/homography_camera_displacement_compare.jpg) Visually, it is hard to distinguish a difference between the result image from the homography computed from the camera displacement and the one estimated with @ref cv::findHomography function. +#### Exercise + +This demo shows you how to compute the homography transformation from two camera poses. Try to perform the same operations, but by computing N inter homography this time. Instead of computing one homography to directly warp the source image to the desired camera viewpoint, perform N warping operations to see the different transformations operating. + +You should get something similar to the following: + +![The first three images show the source image warped at three different interpolated camera viewpoints. The 4th image shows the "error image" between the warped source image at the final camera viewpoint and the desired image.](images/homography_camera_poses_interpolation.jpg) + ### Demo 4: Decompose the homography matrix {#tutorial_homography_Demo4} OpenCV 3 contains the function @ref cv::decomposeHomographyMat which allows to decompose the homography matrix to a set of rotations, translations and plane normals. diff --git a/doc/tutorials/features2d/homography/images/homography_camera_poses_interpolation.jpg b/doc/tutorials/features2d/homography/images/homography_camera_poses_interpolation.jpg new file mode 100644 index 0000000000..353260c880 Binary files /dev/null and b/doc/tutorials/features2d/homography/images/homography_camera_poses_interpolation.jpg differ diff --git a/doc/tutorials/gapi/oak_devices/oak_devices.markdown b/doc/tutorials/gapi/oak_devices/oak_devices.markdown new file mode 100644 index 0000000000..6046cdef25 --- /dev/null +++ b/doc/tutorials/gapi/oak_devices/oak_devices.markdown @@ -0,0 +1,26 @@ +Using DepthAI Hardware / OAK depth sensors {#tutorial_gapi_oak_devices} +======================================================================= + +@tableofcontents + +@prev_tutorial{tutorial_gapi_face_beautification} + +![Oak-D and Oak-D-Light cameras](pics/oak.jpg) + +Depth sensors compatible with Luxonis DepthAI library are supported through OpenCV Graph API (or G-API) module. RGB image and some other formats of output can be retrieved by using familiar interface of G-API module. + +In order to use DepthAI sensor with OpenCV you should do the following preliminary steps: +-# Install Luxonis DepthAI library [depthai-core](https://github.com/luxonis/depthai-core). + +-# Configure OpenCV with DepthAI library support by setting `WITH_OAK` flag in CMake. If DepthAI library is found in install folders OpenCV will be built with depthai-core (see a status `WITH_OAK` in CMake log). + +-# Build OpenCV. + +Source code +----------- + +You can find source code how to process heterogeneous graphs in the `modules/gapi/samples/oak_basic_infer.cpp` of the OpenCV source code library. + +@add_toggle_cpp + @include modules/gapi/samples/oak_basic_infer.cpp +@end_toggle diff --git a/doc/tutorials/gapi/oak_devices/pics/oak.jpg b/doc/tutorials/gapi/oak_devices/pics/oak.jpg new file mode 100644 index 0000000000..9ad30ce1e9 Binary files /dev/null and b/doc/tutorials/gapi/oak_devices/pics/oak.jpg differ diff --git a/doc/tutorials/gapi/table_of_content_gapi.markdown b/doc/tutorials/gapi/table_of_content_gapi.markdown index f9d7b0389a..1b33172b9e 100644 --- a/doc/tutorials/gapi/table_of_content_gapi.markdown +++ b/doc/tutorials/gapi/table_of_content_gapi.markdown @@ -40,3 +40,14 @@ how G-API module can be used for that. In this tutorial we build a complex hybrid Computer Vision/Deep Learning video processing pipeline with G-API. + + +- @subpage tutorial_gapi_oak_devices + + *Languages:* C++ + + *Compatibility:* \> OpenCV 4.6 + + *Author:* Alessandro de Oliveira Faria (A.K.A. CABELO) + + In this tutorial we showed how to use the Luxonis DepthAI library with G-API. diff --git a/doc/tutorials/imgproc/generalized_hough_ballard_guil/generalized_hough_ballard_guil.markdown b/doc/tutorials/imgproc/generalized_hough_ballard_guil/generalized_hough_ballard_guil.markdown new file mode 100644 index 0000000000..695c476d4b --- /dev/null +++ b/doc/tutorials/imgproc/generalized_hough_ballard_guil/generalized_hough_ballard_guil.markdown @@ -0,0 +1,97 @@ +Object detection with Generalized Ballard and Guil Hough Transform {#tutorial_generalized_hough_ballard_guil} +================================================================== + +@tableofcontents + +@prev_tutorial{tutorial_hough_circle} +@next_tutorial{tutorial_remap} + +| | | +| -: | :- | +| Original author | Markus Heck | +| Compatibility | OpenCV >= 3.4 | + +Goal +---- + +In this tutorial you will learn how to: + +- Use @ref cv::GeneralizedHoughBallard and @ref cv::GeneralizedHoughGuil to detect an object + +Example +------- + +### What does this program do? + +1. Load the image and template + +![image](images/generalized_hough_mini_image.jpg) +![template](images/generalized_hough_mini_template.jpg) + +2. Instantiate @ref cv::GeneralizedHoughBallard with the help of `createGeneralizedHoughBallard()` +3. Instantiate @ref cv::GeneralizedHoughGuil with the help of `createGeneralizedHoughGuil()` +4. Set the required parameters for both GeneralizedHough variants +5. Detect and show found results + +@note +- Both variants can't be instantiated directly. Using the create methods is required. +- Guil Hough is very slow. Calculating the results for the "mini" files used in this tutorial + takes only a few seconds. With image and template in a higher resolution, as shown below, + my notebook requires about 5 minutes to calculate a result. + +![image](images/generalized_hough_image.jpg) +![template](images/generalized_hough_template.jpg) + +### Code + +The complete code for this tutorial is shown below. +@include samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp + +Explanation +----------- + +### Load image, template and setup variables + +@snippet samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp generalized-hough-transform-load-and-setup + +The position vectors will contain the matches the detectors will find. +Every entry contains four floating point values: +position vector + +- *[0]*: x coordinate of center point +- *[1]*: y coordinate of center point +- *[2]*: scale of detected object compared to template +- *[3]*: rotation of detected object in degree in relation to template + +An example could look as follows: `[200, 100, 0.9, 120]` + +### Setup parameters + +@snippet samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp generalized-hough-transform-setup-parameters + +Finding the optimal values can end up in trial and error and depends on many factors, such as the image resolution. + +### Run detection + +@snippet samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp generalized-hough-transform-run + +As mentioned above, this step will take some time, especially with larger images and when using Guil. + +### Draw results and show image + +@snippet samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp generalized-hough-transform-draw-results + +Result +------ + +![result image](images/generalized_hough_result_img.jpg) + +The blue rectangle shows the result of @ref cv::GeneralizedHoughBallard and the green rectangles the results of @ref +cv::GeneralizedHoughGuil. + +Getting perfect results like in this example is unlikely if the parameters are not perfectly adapted to the sample. +An example with less perfect parameters is shown below. +For the Ballard variant, only the center of the result is marked as a black dot on this image. The rectangle would be +the same as on the previous image. + +![less perfect result](images/generalized_hough_less_perfect_result_img.jpg) diff --git a/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_image.jpg b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_image.jpg new file mode 100644 index 0000000000..06f53823b2 Binary files /dev/null and b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_image.jpg differ diff --git a/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_less_perfect_result_img.jpg b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_less_perfect_result_img.jpg new file mode 100644 index 0000000000..4f6bf315a8 Binary files /dev/null and b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_less_perfect_result_img.jpg differ diff --git a/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_mini_image.jpg b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_mini_image.jpg new file mode 100644 index 0000000000..bcc8b9e414 Binary files /dev/null and b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_mini_image.jpg differ diff --git a/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_mini_template.jpg b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_mini_template.jpg new file mode 100644 index 0000000000..69ab30eed3 Binary files /dev/null and b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_mini_template.jpg differ diff --git a/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_result_img.jpg b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_result_img.jpg new file mode 100644 index 0000000000..782f730fbc Binary files /dev/null and b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_result_img.jpg differ diff --git a/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_template.jpg b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_template.jpg new file mode 100644 index 0000000000..393db734ef Binary files /dev/null and b/doc/tutorials/imgproc/generalized_hough_ballard_guil/images/generalized_hough_template.jpg differ diff --git a/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.markdown b/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.markdown index bde1d4acfa..396df9dce7 100644 --- a/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.markdown +++ b/doc/tutorials/imgproc/imgtrans/hough_circle/hough_circle.markdown @@ -4,7 +4,7 @@ Hough Circle Transform {#tutorial_hough_circle} @tableofcontents @prev_tutorial{tutorial_hough_lines} -@next_tutorial{tutorial_remap} +@next_tutorial{tutorial_generalized_hough_ballard_guil} | | | | -: | :- | diff --git a/doc/tutorials/imgproc/imgtrans/remap/remap.markdown b/doc/tutorials/imgproc/imgtrans/remap/remap.markdown index 172aebcea7..cbf23998e1 100644 --- a/doc/tutorials/imgproc/imgtrans/remap/remap.markdown +++ b/doc/tutorials/imgproc/imgtrans/remap/remap.markdown @@ -3,7 +3,7 @@ Remapping {#tutorial_remap} @tableofcontents -@prev_tutorial{tutorial_hough_circle} +@prev_tutorial{tutorial_generalized_hough_ballard_guil} @next_tutorial{tutorial_warp_affine} | | | diff --git a/doc/tutorials/imgproc/table_of_content_imgproc.markdown b/doc/tutorials/imgproc/table_of_content_imgproc.markdown index edffd706bd..bb537f428d 100644 --- a/doc/tutorials/imgproc/table_of_content_imgproc.markdown +++ b/doc/tutorials/imgproc/table_of_content_imgproc.markdown @@ -23,6 +23,7 @@ Transformations - @subpage tutorial_canny_detector - @subpage tutorial_hough_lines - @subpage tutorial_hough_circle +- @subpage tutorial_generalized_hough_ballard_guil - @subpage tutorial_remap - @subpage tutorial_warp_affine diff --git a/doc/tutorials/introduction/windows_install/windows_install.markdown b/doc/tutorials/introduction/windows_install/windows_install.markdown index 87f2e51eb8..eabf31482f 100644 --- a/doc/tutorials/introduction/windows_install/windows_install.markdown +++ b/doc/tutorials/introduction/windows_install/windows_install.markdown @@ -26,7 +26,7 @@ Installation by Using the Pre-built Libraries {#tutorial_windows_install_prebuil ============================================= -# Launch a web browser of choice and go to our [page on - Sourceforge](http://sourceforge.net/projects/opencvlibrary/files/opencv-win/). + Sourceforge](http://sourceforge.net/projects/opencvlibrary/files/). -# Choose a build you want to use and download it. -# Make sure you have admin rights. Unpack the self-extracting archive. -# You can check the installation at the chosen path as you can see below. @@ -370,18 +370,18 @@ Set the OpenCV environment variable and add it to the systems path {#tutorial_wi First, we set an environment variable to make our work easier. This will hold the build directory of our OpenCV library that we use in our projects. Start up a command window and enter: @code - setx -m OPENCV_DIR D:\OpenCV\Build\x86\vc11 (suggested for Visual Studio 2012 - 32 bit Windows) - setx -m OPENCV_DIR D:\OpenCV\Build\x64\vc11 (suggested for Visual Studio 2012 - 64 bit Windows) + setx OpenCV_DIR D:\OpenCV\build\x64\vc14 (suggested for Visual Studio 2015 - 64 bit Windows) + setx OpenCV_DIR D:\OpenCV\build\x86\vc14 (suggested for Visual Studio 2015 - 32 bit Windows) - setx -m OPENCV_DIR D:\OpenCV\Build\x86\vc12 (suggested for Visual Studio 2013 - 32 bit Windows) - setx -m OPENCV_DIR D:\OpenCV\Build\x64\vc12 (suggested for Visual Studio 2013 - 64 bit Windows) + setx OpenCV_DIR D:\OpenCV\build\x64\vc15 (suggested for Visual Studio 2017 - 64 bit Windows) + setx OpenCV_DIR D:\OpenCV\build\x86\vc15 (suggested for Visual Studio 2017 - 32 bit Windows) - setx -m OPENCV_DIR D:\OpenCV\Build\x64\vc14 (suggested for Visual Studio 2015 - 64 bit Windows) + setx OpenCV_DIR D:\OpenCV\build\x64\vc16 (suggested for Visual Studio 2019 - 64 bit Windows) + setx OpenCV_DIR D:\OpenCV\build\x86\vc16 (suggested for Visual Studio 2019 - 32 bit Windows) @endcode Here the directory is where you have your OpenCV binaries (*extracted* or *built*). You can have different platform (e.g. x64 instead of x86) or compiler type, so substitute appropriate value. -Inside this, you should have two folders called *lib* and *bin*. The -m should be added if you wish -to make the settings computer wise, instead of user wise. +Inside this, you should have two folders called *lib* and *bin*. If you built static libraries then you are done. Otherwise, you need to add the *bin* folders path to the systems path. This is because you will use the OpenCV library in form of *"Dynamic-link diff --git a/modules/3d/include/opencv2/3d.hpp b/modules/3d/include/opencv2/3d.hpp index e838737d1a..99be47cfe3 100644 --- a/modules/3d/include/opencv2/3d.hpp +++ b/modules/3d/include/opencv2/3d.hpp @@ -613,7 +613,7 @@ public: param: the current vector of parameters err: output vector of errors: err_i = actual_f_i - ideal_f_i - J: output Jacobian: J_ij = d(err_i)/d(param_j) + J: output Jacobian: J_ij = d(ideal_f_i)/d(param_j) Param vector values may be changed by the callback only if they are fixed. Changing non-fixed variables may lead to incorrect results. @@ -750,7 +750,7 @@ a vector\ . - @ref RHO - PROSAC-based robust method @param ransacReprojThreshold Maximum allowed reprojection error to treat a point pair as an inlier (used in the RANSAC and RHO methods only). That is, if -\f[\| \texttt{dstPoints} _i - \texttt{convertPointsHomogeneous} ( \texttt{H} * \texttt{srcPoints} _i) \|_2 > \texttt{ransacReprojThreshold}\f] +\f[\| \texttt{dstPoints} _i - \texttt{convertPointsHomogeneous} ( \texttt{H} \cdot \texttt{srcPoints} _i) \|_2 > \texttt{ransacReprojThreshold}\f] then the point \f$i\f$ is considered as an outlier. If srcPoints and dstPoints are measured in pixels, it usually makes sense to set this parameter somewhere in the range of 1 to 10. @param mask Optional output mask set by a robust method ( RANSAC or LMeDS ). Note that the input @@ -853,7 +853,7 @@ be used in OpenGL. Note, there is always more than one sequence of rotations abo principal axes that results in the same orientation of an object, e.g. see @cite Slabaugh . Returned tree rotation matrices and corresponding three Euler angles are only one of the possible solutions. -The function is based on RQDecomp3x3 . +The function is based on #RQDecomp3x3 . */ CV_EXPORTS_W void decomposeProjectionMatrix( InputArray projMatrix, OutputArray cameraMatrix, OutputArray rotMatrix, OutputArray transVect, @@ -899,10 +899,10 @@ The functions compute: \f[\begin{array}{l} \texttt{rvec3} = \mathrm{rodrigues} ^{-1} \left ( \mathrm{rodrigues} ( \texttt{rvec2} ) \cdot \mathrm{rodrigues} ( \texttt{rvec1} ) \right ) \\ \texttt{tvec3} = \mathrm{rodrigues} ( \texttt{rvec2} ) \cdot \texttt{tvec1} + \texttt{tvec2} \end{array} ,\f] where \f$\mathrm{rodrigues}\f$ denotes a rotation vector to a rotation matrix transformation, and -\f$\mathrm{rodrigues}^{-1}\f$ denotes the inverse transformation. See Rodrigues for details. +\f$\mathrm{rodrigues}^{-1}\f$ denotes the inverse transformation. See #Rodrigues for details. Also, the functions can compute the derivatives of the output vectors with regards to the input -vectors (see matMulDeriv ). The functions are used inside #stereoCalibrate but can also be used in +vectors (see #matMulDeriv ). The functions are used inside #stereoCalibrate but can also be used in your own code where Levenberg-Marquardt or another gradient-based solver is used to optimize a function that contains a matrix multiplication. */ @@ -1361,7 +1361,7 @@ the found fundamental matrix. Normally just one matrix is found. But in case of algorithm, the function may return up to 3 solutions ( \f$9 \times 3\f$ matrix that stores all 3 matrices sequentially). -The calculated fundamental matrix may be passed further to computeCorrespondEpilines that finds the +The calculated fundamental matrix may be passed further to #computeCorrespondEpilines that finds the epipolar lines corresponding to the specified points. It can also be passed to #stereoRectifyUncalibrated to compute the rectification transformation. : @code @@ -1431,7 +1431,7 @@ This function estimates essential matrix based on the five-point algorithm solve where \f$E\f$ is an essential matrix, \f$p_1\f$ and \f$p_2\f$ are corresponding points in the first and the second images, respectively. The result of this function may be passed further to -#decomposeEssentialMat or #recoverPose to recover the relative pose between cameras. +#decomposeEssentialMat or #recoverPose to recover the relative pose between cameras. */ CV_EXPORTS_W Mat findEssentialMat( @@ -1807,12 +1807,12 @@ CV_EXPORTS_W void triangulatePoints( InputArray projMatr1, InputArray projMatr2, @param newPoints1 The optimized points1. @param newPoints2 The optimized points2. -The function implements the Optimal Triangulation Method (see Multiple View Geometry for details). +The function implements the Optimal Triangulation Method (see Multiple View Geometry @cite HartleyZ00 for details). For each given point correspondence points1[i] \<-\> points2[i], and a fundamental matrix F, it computes the corrected correspondences newPoints1[i] \<-\> newPoints2[i] that minimize the geometric error \f$d(points1[i], newPoints1[i])^2 + d(points2[i],newPoints2[i])^2\f$ (where \f$d(a,b)\f$ is the geometric distance between points \f$a\f$ and \f$b\f$ ) subject to the epipolar constraint -\f$newPoints2^T * F * newPoints1 = 0\f$ . +\f$newPoints2^T \cdot F \cdot newPoints1 = 0\f$ . */ CV_EXPORTS_W void correctMatches( InputArray F, InputArray points1, InputArray points2, OutputArray newPoints1, OutputArray newPoints2 ); @@ -2236,7 +2236,7 @@ where cameraMatrix can be chosen arbitrarily. of 4, 5, 8, 12 or 14 elements. If the vector is NULL/empty, the zero distortion coefficients are assumed. @param R Optional rectification transformation in the object space (3x3 matrix). R1 or R2 , computed by #stereoRectify can be passed here. If the matrix is empty, the identity transformation -is assumed. In cvInitUndistortMap R assumed to be an identity matrix. +is assumed. In #initUndistortRectifyMap R assumed to be an identity matrix. @param newCameraMatrix New camera matrix \f$A'=\vecthreethree{f_x'}{0}{c_x'}{0}{f_y'}{c_y'}{0}{0}{1}\f$. @param size Undistorted image size. @param m1type Type of the first output map that can be CV_32FC1, CV_32FC2 or CV_16SC2, see #convertMaps diff --git a/modules/3d/misc/java/test/Cv3dTest.java b/modules/3d/misc/java/test/Cv3dTest.java index 00fadfebc0..9ee5b7c200 100644 --- a/modules/3d/misc/java/test/Cv3dTest.java +++ b/modules/3d/misc/java/test/Cv3dTest.java @@ -555,4 +555,5 @@ public class Cv3dTest extends OpenCVTestCase { assertTrue(src.toList().get(i).equals(dst.toList().get(i))); } } + } diff --git a/modules/3d/src/five-point.cpp b/modules/3d/src/five-point.cpp index a46e59c111..98094d1577 100644 --- a/modules/3d/src/five-point.cpp +++ b/modules/3d/src/five-point.cpp @@ -431,9 +431,9 @@ Mat findEssentialMat( InputArray _points1, InputArray _points2, InputArray _came { CV_INSTRUMENT_REGION(); - if (method >= 32 && method <= 38) + if (method >= USAC_DEFAULT && method <= USAC_MAGSAC) return usac::findEssentialMat(_points1, _points2, _cameraMatrix, - method, prob, threshold, _mask); + method, prob, threshold, _mask, maxIters); Mat points1, points2, cameraMatrix; _points1.getMat().convertTo(points1, CV_64F); diff --git a/modules/3d/src/solvepnp.cpp b/modules/3d/src/solvepnp.cpp index bd55fea5e4..01eb4d1b0b 100644 --- a/modules/3d/src/solvepnp.cpp +++ b/modules/3d/src/solvepnp.cpp @@ -152,12 +152,13 @@ public: int runKernel( InputArray _m1, InputArray _m2, OutputArray _model ) const CV_OVERRIDE { Mat opoints = _m1.getMat(), ipoints = _m2.getMat(); - + Mat iter_rvec = rvec.clone(); + Mat iter_tvec = tvec.clone(); bool correspondence = solvePnP( _m1, _m2, cameraMatrix, distCoeffs, - rvec, tvec, useExtrinsicGuess, flags ); + iter_rvec, iter_tvec, useExtrinsicGuess, flags ); Mat _local_model; - hconcat(rvec, tvec, _local_model); + hconcat(iter_rvec, iter_tvec, _local_model); _local_model.copyTo(_model); return correspondence; @@ -336,7 +337,13 @@ bool solvePnPRansac(InputArray _opoints, InputArray _ipoints, ipoints_inliers.resize(npoints1); try { - result = solvePnP(opoints_inliers, ipoints_inliers, cameraMatrix, + if (flags == SOLVEPNP_ITERATIVE && !useExtrinsicGuess) + { + rvec = _local_model.col(0).clone(); + tvec = _local_model.col(1).clone(); + useExtrinsicGuess = true; + } + result = solvePnP(opoints_inliers, ipoints_inliers, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess, (flags == SOLVEPNP_P3P || flags == SOLVEPNP_AP3P) ? SOLVEPNP_EPNP : flags) ? 1 : -1; } diff --git a/modules/3d/src/usac.hpp b/modules/3d/src/usac.hpp index f88233f024..52ebe03792 100644 --- a/modules/3d/src/usac.hpp +++ b/modules/3d/src/usac.hpp @@ -962,7 +962,8 @@ bool solvePnPRansac( InputArray objectPoints, InputArray imagePoints, Mat findEssentialMat( InputArray points1, InputArray points2, InputArray cameraMatrix1, int method, double prob, - double threshold, OutputArray mask); + double threshold, OutputArray mask, + int maxIters); Mat estimateAffine2D(InputArray from, InputArray to, OutputArray inliers, int method, double ransacReprojThreshold, int maxIters, diff --git a/modules/3d/src/usac/dls_solver.cpp b/modules/3d/src/usac/dls_solver.cpp index 0abb26cecc..8f109d51bf 100644 --- a/modules/3d/src/usac/dls_solver.cpp +++ b/modules/3d/src/usac/dls_solver.cpp @@ -160,7 +160,7 @@ public: double wr[27], wi[27] = {0}; // 27 = mat_order std::vector work(lwork), eig_vecs(729); char jobvl = 'N', jobvr = 'V'; // only left eigen vectors are computed - dgeev_(&jobvl, &jobvr, &mat_order, (double*)solution_polynomial.data, &lda, wr, wi, nullptr, &ldvl, + OCV_LAPACK_FUNC(dgeev)(&jobvl, &jobvr, &mat_order, (double*)solution_polynomial.data, &lda, wr, wi, nullptr, &ldvl, &eig_vecs[0], &ldvr, &work[0], &lwork, &info); if (info != 0) return 0; #endif diff --git a/modules/3d/src/usac/essential_solver.cpp b/modules/3d/src/usac/essential_solver.cpp index 2e41246506..4d388916b4 100644 --- a/modules/3d/src/usac/essential_solver.cpp +++ b/modules/3d/src/usac/essential_solver.cpp @@ -161,7 +161,7 @@ public: int mat_order = 10, info, lda = 10, ldvl = 10, ldvr = 1, lwork = 100; double wr[10], wi[10] = {0}, eig_vecs[100], work[100]; // 10 = mat_order, 100 = lwork char jobvl = 'V', jobvr = 'N'; // only left eigen vectors are computed - dgeev_(&jobvl, &jobvr, &mat_order, action_mat_data, &lda, wr, wi, eig_vecs, &ldvl, + OCV_LAPACK_FUNC(dgeev)(&jobvl, &jobvr, &mat_order, action_mat_data, &lda, wr, wi, eig_vecs, &ldvl, nullptr, &ldvr, work, &lwork, &info); if (info != 0) return 0; #endif diff --git a/modules/3d/src/usac/estimator.cpp b/modules/3d/src/usac/estimator.cpp index 6be3acd760..edc00b1819 100644 --- a/modules/3d/src/usac/estimator.cpp +++ b/modules/3d/src/usac/estimator.cpp @@ -24,7 +24,7 @@ public: int estimateModelNonMinimalSample(const std::vector &sample, int sample_size, std::vector &models, const std::vector &weights) const override { return non_min_solver->estimate (sample, sample_size, models, weights); - }; + } int getMaxNumSolutions () const override { return min_solver->getMaxNumberOfSolutions(); } @@ -119,7 +119,7 @@ public: int estimateModelNonMinimalSample(const std::vector &sample, int sample_size, std::vector &models, const std::vector &weights) const override { return non_min_solver->estimate(sample, sample_size, models, weights); - }; + } int getMaxNumSolutions () const override { return min_solver->getMaxNumberOfSolutions(); } diff --git a/modules/3d/src/usac/ransac_solvers.cpp b/modules/3d/src/usac/ransac_solvers.cpp index 0ecc74427d..36053a6c4c 100644 --- a/modules/3d/src/usac/ransac_solvers.cpp +++ b/modules/3d/src/usac/ransac_solvers.cpp @@ -555,9 +555,9 @@ Mat findFundamentalMat( InputArray points1, InputArray points2, int method, doub } Mat findEssentialMat (InputArray points1, InputArray points2, InputArray cameraMatrix1, - int method, double prob, double thr, OutputArray mask) { + int method, double prob, double thr, OutputArray mask, int maxIters) { Ptr params; - setParameters(method, params, EstimationMethod::Essential, thr, 1000, prob, mask.needed()); + setParameters(method, params, EstimationMethod::Essential, thr, maxIters, prob, mask.needed()); Ptr ransac_output; if (run(params, points1, points2, params->getRandomGeneratorState(), ransac_output, cameraMatrix1, cameraMatrix1, noArray(), noArray())) { diff --git a/modules/3d/test/test_usac.cpp b/modules/3d/test/test_usac.cpp index 971e943d11..98aeaa712b 100644 --- a/modules/3d/test/test_usac.cpp +++ b/modules/3d/test/test_usac.cpp @@ -299,8 +299,11 @@ TEST(usac_Fundamental, regression_19639) EXPECT_TRUE(m.empty()); } +CV_ENUM(UsacMethod, USAC_DEFAULT, USAC_ACCURATE, USAC_PROSAC, USAC_FAST, USAC_MAGSAC) +typedef TestWithParam usac_Essential; -TEST(usac_Essential, accuracy) { +TEST_P(usac_Essential, accuracy) { + int method = GetParam(); std::vector gt_inliers; const int pts_size = 1500; cv::RNG &rng = cv::theRNG(); @@ -312,26 +315,58 @@ TEST(usac_Essential, accuracy) { int inl_size = generatePoints(rng, pts1, pts2, K1, K2, false /*two calib*/, pts_size, TestSolver ::Fundam, inl_ratio, 0.01 /*noise std, works bad with high noise*/, gt_inliers); const double conf = 0.99, thr = 1.; - for (auto flag : flags) { - cv::Mat mask, E; - try { - E = cv::findEssentialMat(pts1, pts2, K1, flag, conf, thr, 1000/*maxIters*/, mask); - } catch (cv::Exception &e) { - if (e.code != cv::Error::StsNotImplemented) - FAIL() << "Essential matrix estimation failed!\n"; - else continue; - } - // calibrate points - cv::Mat cpts1_3d, cpts2_3d; - cv::vconcat(pts1, cv::Mat::ones(1, pts1.cols, pts1.type()), cpts1_3d); - cv::vconcat(pts2, cv::Mat::ones(1, pts2.cols, pts2.type()), cpts2_3d); - cpts1_3d = K1.inv() * cpts1_3d; cpts2_3d = K1.inv() * cpts2_3d; - checkInliersMask(TestSolver::Essen, inl_size, thr / ((K1.at(0,0) + K1.at(1,1)) / 2), - cpts1_3d.rowRange(0,2), cpts2_3d.rowRange(0,2), E, mask); + cv::Mat mask, E; + try { + E = cv::findEssentialMat(pts1, pts2, K1, method, conf, thr, 1000/*maxIters*/, mask); + } catch (cv::Exception &e) { + if (e.code != cv::Error::StsNotImplemented) + FAIL() << "Essential matrix estimation failed!\n"; + else continue; } + // calibrate points + cv::Mat cpts1_3d, cpts2_3d; + cv::vconcat(pts1, cv::Mat::ones(1, pts1.cols, pts1.type()), cpts1_3d); + cv::vconcat(pts2, cv::Mat::ones(1, pts2.cols, pts2.type()), cpts2_3d); + cpts1_3d = K1.inv() * cpts1_3d; cpts2_3d = K1.inv() * cpts2_3d; + checkInliersMask(TestSolver::Essen, inl_size, thr / ((K1.at(0,0) + K1.at(1,1)) / 2), + cpts1_3d.rowRange(0,2), cpts2_3d.rowRange(0,2), E, mask); } } +TEST_P(usac_Essential, maxiters) { + int method = GetParam(); + cv::RNG &rng = cv::theRNG(); + cv::Mat mask; + cv::Mat K1 = cv::Mat(cv::Matx33d(1, 0, 0, + 0, 1, 0, + 0, 0, 1.)); + const double conf = 0.99, thr = 0.5; + int roll_results_sum = 0; + + for (int iters = 0; iters < 10; iters++) { + cv::Mat E1, E2; + try { + cv::Mat pts1 = cv::Mat(2, 50, CV_64F); + cv::Mat pts2 = cv::Mat(2, 50, CV_64F); + rng.fill(pts1, cv::RNG::UNIFORM, 0.0, 1.0); + rng.fill(pts2, cv::RNG::UNIFORM, 0.0, 1.0); + + E1 = cv::findEssentialMat(pts1, pts2, K1, method, conf, thr, 1, mask); + E2 = cv::findEssentialMat(pts1, pts2, K1, method, conf, thr, 1000, mask); + + if (E1.dims != E2.dims) { continue; } + roll_results_sum += cv::norm(E1, E2, NORM_L1) != 0; + } catch (cv::Exception &e) { + if (e.code != cv::Error::StsNotImplemented) + FAIL() << "Essential matrix estimation failed!\n"; + else continue; + } + EXPECT_NE(roll_results_sum, 0); + } +} + +INSTANTIATE_TEST_CASE_P(Calib3d, usac_Essential, UsacMethod::all()); + TEST(usac_P3P, accuracy) { std::vector gt_inliers; const int pts_size = 3000; diff --git a/modules/calib/include/opencv2/calib.hpp b/modules/calib/include/opencv2/calib.hpp index 9c0595b940..08c7215080 100644 --- a/modules/calib/include/opencv2/calib.hpp +++ b/modules/calib/include/opencv2/calib.hpp @@ -471,7 +471,7 @@ coordinate space. In the old interface all the per-view vectors are concatenated old interface all the per-view vectors are concatenated. @param imageSize Image size in pixels used to initialize the principal point. @param aspectRatio If it is zero or negative, both \f$f_x\f$ and \f$f_y\f$ are estimated independently. -Otherwise, \f$f_x = f_y * \texttt{aspectRatio}\f$ . +Otherwise, \f$f_x = f_y \cdot \texttt{aspectRatio}\f$ . The function estimates and returns an initial camera intrinsic matrix for the camera calibration process. Currently, the function only supports planar calibration patterns, which are patterns where each @@ -504,7 +504,7 @@ are found and they are placed in a certain order (row by row, left to right in e Otherwise, if the function fails to find all the corners or reorder them, it returns 0. For example, a regular chessboard has 8 x 8 squares and 7 x 7 internal corners, that is, points where the black squares touch each other. The detected coordinates are approximate, and to determine their positions -more accurately, the function calls cornerSubPix. You also may use the function cornerSubPix with +more accurately, the function calls #cornerSubPix. You also may use the function #cornerSubPix with different parameters if returned coordinates are not accurate enough. Sample usage of detecting and drawing chessboard corners: : @@ -1090,7 +1090,7 @@ CV_EXPORTS_AS(stereoCalibrateExtended) double stereoCalibrate( InputArrayOfArray InputArrayOfArrays imagePoints1, InputArrayOfArrays imagePoints2, InputOutputArray cameraMatrix1, InputOutputArray distCoeffs1, InputOutputArray cameraMatrix2, InputOutputArray distCoeffs2, - Size imageSize, InputOutputArray R,InputOutputArray T, OutputArray E, OutputArray F, + Size imageSize, InputOutputArray R, InputOutputArray T, OutputArray E, OutputArray F, OutputArray perViewErrors, int flags = CALIB_FIX_INTRINSIC, TermCriteria criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-6) ); @@ -1606,7 +1606,7 @@ rectified views. And if the flag is not set, the function may still shift the im horizontal or vertical direction (depending on the orientation of epipolar lines) to maximize the useful image area. @param newImageSize New image resolution after rectification. The same size should be passed to -initUndistortRectifyMap (see the stereo_calib.cpp sample in OpenCV samples directory). When (0,0) +#initUndistortRectifyMap (see the stereo_calib.cpp sample in OpenCV samples directory). When (0,0) is passed (default), it is set to the original imageSize . Setting it to larger value can help you preserve details in the original image, especially when there is a big radial distortion. @param balance Sets the new focal length in range between the min focal length and the max focal diff --git a/modules/calib/misc/java/test/CalibTest.java b/modules/calib/misc/java/test/CalibTest.java index 6f73d13d09..8cdd3c65c6 100644 --- a/modules/calib/misc/java/test/CalibTest.java +++ b/modules/calib/misc/java/test/CalibTest.java @@ -126,7 +126,7 @@ public class CalibTest extends OpenCVTestCase { assertEquals((1 << 22), Calib.CALIB_USE_EXTRINSIC_GUESS); } - /*public void testEstimateNewCameraMatrixForUndistortRectify() { + public void testEstimateNewCameraMatrixForUndistortRectify() { Mat K = new Mat().eye(3, 3, CvType.CV_64FC1); Mat K_new = new Mat().eye(3, 3, CvType.CV_64FC1); Mat K_new_truth = new Mat().eye(3, 3, CvType.CV_64FC1); @@ -142,14 +142,15 @@ public class CalibTest extends OpenCVTestCase { D.put(2,0,-0.021509225493198905); D.put(3,0,0.0043378096628297145); - K_new_truth.put(0,0, 387.4809086880343); - K_new_truth.put(0,2, 1036.669802754649); - K_new_truth.put(1,1, 373.6375700303157); - K_new_truth.put(1,2, 538.8373261247601); + K_new_truth.put(0,0, 387.5118215642316); + K_new_truth.put(0,2, 1033.936556777084); + K_new_truth.put(1,1, 373.6673784974842); + K_new_truth.put(1,2, 538.794152656429); Calib.fisheye_estimateNewCameraMatrixForUndistortRectify(K,D,new Size(1920,1080), new Mat().eye(3, 3, CvType.CV_64F), K_new, 0.0, new Size(1920,1080)); assertMatEqual(K_new, K_new_truth, EPS); - }*/ + } + } diff --git a/modules/calib/src/chessboard.hpp b/modules/calib/src/chessboard.hpp index 4c862eca4c..f49b83572f 100644 --- a/modules/calib/src/chessboard.hpp +++ b/modules/calib/src/chessboard.hpp @@ -43,7 +43,7 @@ class FastX : public cv::Feature2D public: FastX(const Parameters &config = Parameters()); - virtual ~FastX(){}; + virtual ~FastX(){} void reconfigure(const Parameters ¶); @@ -74,8 +74,8 @@ class FastX : public cv::Feature2D std::vector > calcAngles(const std::vector &rotated_images, std::vector &keypoints)const; // define pure virtual methods - virtual int descriptorSize()const override{return 0;}; - virtual int descriptorType()const override{return 0;}; + virtual int descriptorSize()const override{return 0;} + virtual int descriptorType()const override{return 0;} virtual void operator()( cv::InputArray image, cv::InputArray mask, std::vector& keypoints, cv::OutputArray descriptors, bool useProvidedKeypoints=false )const { descriptors.clear(); @@ -620,10 +620,10 @@ class Chessboard: public cv::Feature2D */ void swap(Chessboard::Board &other); - bool operator==(const Chessboard::Board& other) const {return rows*cols == other.rows*other.cols;}; - bool operator< (const Chessboard::Board& other) const {return rows*cols < other.rows*other.cols;}; - bool operator> (const Chessboard::Board& other) const {return rows*cols > other.rows*other.cols;}; - bool operator>= (const cv::Size& size)const { return rows*cols >= size.width*size.height; }; + bool operator==(const Chessboard::Board& other) const {return rows*cols == other.rows*other.cols;} + bool operator< (const Chessboard::Board& other) const {return rows*cols < other.rows*other.cols;} + bool operator> (const Chessboard::Board& other) const {return rows*cols > other.rows*other.cols;} + bool operator>= (const cv::Size& size)const { return rows*cols >= size.width*size.height; } /** * \brief Returns a specific corner @@ -824,8 +824,8 @@ class Chessboard: public cv::Feature2D Chessboard::Board detectImpl(const cv::Mat& image,std::vector &feature_maps,const cv::Mat& mask)const; // define pure virtual methods - virtual int descriptorSize()const override{return 0;}; - virtual int descriptorType()const override{return 0;}; + virtual int descriptorSize()const override{return 0;} + virtual int descriptorType()const override{return 0;} virtual void operator()( cv::InputArray image, cv::InputArray mask, std::vector& keypoints, cv::OutputArray descriptors, bool useProvidedKeypoints=false )const { descriptors.clear(); diff --git a/modules/calib/src/fisheye.cpp b/modules/calib/src/fisheye.cpp index c829cf1939..a7eb6d202b 100644 --- a/modules/calib/src/fisheye.cpp +++ b/modules/calib/src/fisheye.cpp @@ -405,7 +405,7 @@ void cv::fisheye::undistortPoints( InputArray distorted, OutputArray undistorted if (!isEps || fabs(theta_d) > criteria.epsilon) { - // compensate distortion iteratively + // compensate distortion iteratively using Newton method for (int j = 0; j < maxCount; j++) { @@ -613,7 +613,7 @@ void cv::fisheye::estimateNewCameraMatrixForUndistortRectify(InputArray K, Input : K.getMat().at(0,0)/K.getMat().at(1,1); // convert to identity ratio - cn[0] *= aspect_ratio; + cn[1] *= aspect_ratio; for(size_t i = 0; i < points.total(); ++i) pptr[i][1] *= aspect_ratio; diff --git a/modules/calib/test/test_cameracalibration.cpp b/modules/calib/test/test_cameracalibration.cpp index 62cb27e96c..74f591059e 100644 --- a/modules/calib/test/test_cameracalibration.cpp +++ b/modules/calib/test/test_cameracalibration.cpp @@ -1237,7 +1237,10 @@ protected: Mat& cameraMatrix1, Mat& distCoeffs1, Mat& cameraMatrix2, Mat& distCoeffs2, Size imageSize, Mat& R, Mat& T, - Mat& E, Mat& F, TermCriteria criteria, int flags ) = 0; + Mat& E, Mat& F, + std::vector& rotationMatrices, std::vector& translationVectors, + vector& perViewErrors1, vector& perViewErrors2, + TermCriteria criteria, int flags ) = 0; virtual void rectify( const Mat& cameraMatrix1, const Mat& distCoeffs1, const Mat& cameraMatrix2, const Mat& distCoeffs2, Size imageSize, const Mat& R, const Mat& T, @@ -1253,6 +1256,10 @@ protected: virtual void correct( const Mat& F, const Mat &points1, const Mat &points2, Mat &newPoints1, Mat &newPoints2 ) = 0; +#if 0 // not ported: #22519 + int compare(double* val, double* refVal, int len, + double eps, const char* paramName); +#endif void run(int); }; @@ -1319,12 +1326,23 @@ bool CV_StereoCalibrationTest::checkPandROI( int test_case_idx, const Mat& M, co return true; } +#if 0 // not ported: #22519 +int CV_StereoCalibrationTest::compare(double* val, double* ref_val, int len, + double eps, const char* param_name ) +{ + return cvtest::cmpEps2_64f( ts, val, ref_val, len, eps, param_name ); +} +#endif + void CV_StereoCalibrationTest::run( int ) { const int ntests = 1; const double maxReprojErr = 2; const double maxScanlineDistErr_c = 3; const double maxScanlineDistErr_uc = 4; +#if 0 // not ported: #22519 + const double maxDiffBtwRmsErrors = 1e-4; +#endif FILE* f = 0; for(int testcase = 1; testcase <= ntests; testcase++) @@ -1401,13 +1419,23 @@ void CV_StereoCalibrationTest::run( int ) objpt[i].push_back(Point3f((float)(j%patternSize.width), (float)(j/patternSize.width), 0.f)); } + vector rotMats1(nframes); + vector transVecs1(nframes); + vector rotMats2(nframes); + vector transVecs2(nframes); + vector rmsErrorPerView1(nframes); + vector rmsErrorPerView2(nframes); + vector rmsErrorPerViewFromReprojectedImgPts1(nframes); + vector rmsErrorPerViewFromReprojectedImgPts2(nframes); + // rectify (calibrated) Mat M1 = Mat::eye(3,3,CV_64F), M2 = Mat::eye(3,3,CV_64F), D1(5,1,CV_64F), D2(5,1,CV_64F), R, T, E, F; M1.at(0,2) = M2.at(0,2)=(imgsize.width-1)*0.5; M1.at(1,2) = M2.at(1,2)=(imgsize.height-1)*0.5; D1 = Scalar::all(0); D2 = Scalar::all(0); - double err = calibrateStereoCamera(objpt, imgpt1, imgpt2, M1, D1, M2, D2, imgsize, R, T, E, F, + double rmsErrorFromStereoCalib = calibrateStereoCamera(objpt, imgpt1, imgpt2, M1, D1, M2, D2, imgsize, R, T, E, F, + rotMats1, transVecs1, rmsErrorPerView1, rmsErrorPerView2, TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 1e-6), CALIB_SAME_FOCAL_LENGTH //+ CV_CALIB_FIX_ASPECT_RATIO @@ -1416,14 +1444,94 @@ void CV_StereoCalibrationTest::run( int ) + CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5 //+ CV_CALIB_FIX_K6 ); - - err /= nframes*npoints; - if( err > maxReprojErr ) +#if 1 // not ported: #22519 + rmsErrorFromStereoCalib /= nframes*npoints; +#endif + if (rmsErrorFromStereoCalib > maxReprojErr) { - ts->printf( cvtest::TS::LOG, "The average reprojection error is too big (=%g), testcase %d\n", err, testcase); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); + ts->printf(cvtest::TS::LOG, "The average reprojection error is too big (=%g), testcase %d\n", + rmsErrorFromStereoCalib, testcase); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); return; } +#if 0 // not ported: #22519 + double rmsErrorFromReprojectedImgPts = 0.0f; + if (rotMats1.empty() || transVecs1.empty()) + { + rmsErrorPerViewFromReprojectedImgPts1 = rmsErrorPerView1; + rmsErrorPerViewFromReprojectedImgPts2 = rmsErrorPerView2; + rmsErrorFromReprojectedImgPts = rmsErrorFromStereoCalib; + } + else + { + vector reprojectedImgPts[2] = {vector(nframes), vector(nframes)}; + size_t totalPoints = 0; + double totalErr[2] = { 0, 0 }, viewErr[2]; + for (size_t i = 0; i < objpt.size(); ++i) { + RotMat r1 = rotMats1[i]; + Vec3d t1 = transVecs1[i]; + + RotMat r2 = Mat(R * r1); + Mat T2t = R * t1; + Vec3d t2 = Mat(T2t + T); + + projectPoints(objpt[i], r1, t1, M1, D1, reprojectedImgPts[0]); + projectPoints(objpt[i], r2, t2, M2, D2, reprojectedImgPts[1]); + + viewErr[0] = cv::norm(imgpt1[i], reprojectedImgPts[0], cv::NORM_L2SQR); + viewErr[1] = cv::norm(imgpt2[i], reprojectedImgPts[1], cv::NORM_L2SQR); + + size_t n = objpt[i].size(); + totalErr[0] += viewErr[0]; + totalErr[1] += viewErr[1]; + totalPoints += n; + + rmsErrorPerViewFromReprojectedImgPts1[i] = sqrt(viewErr[0] / n); + rmsErrorPerViewFromReprojectedImgPts2[i] = sqrt(viewErr[1] / n); + } + rmsErrorFromReprojectedImgPts = std::sqrt((totalErr[0] + totalErr[1]) / (2 * totalPoints)); + + } + + if (abs(rmsErrorFromStereoCalib - rmsErrorFromReprojectedImgPts) > maxDiffBtwRmsErrors) + { + ts->printf(cvtest::TS::LOG, + "The difference of the average reprojection error from the calibration function and from the " + "reprojected image points is too big (|%g - %g| = %g), testcase %d\n", + rmsErrorFromStereoCalib, rmsErrorFromReprojectedImgPts, + (rmsErrorFromStereoCalib - rmsErrorFromReprojectedImgPts), testcase); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + return; + } + + /* ----- Compare per view rms re-projection errors ----- */ + CV_Assert(rmsErrorPerView1.size() == (size_t)nframes); + CV_Assert(rmsErrorPerViewFromReprojectedImgPts1.size() == (size_t)nframes); + CV_Assert(rmsErrorPerView2.size() == (size_t)nframes); + CV_Assert(rmsErrorPerViewFromReprojectedImgPts2.size() == (size_t)nframes); + int code1 = compare(&rmsErrorPerView1[0], &rmsErrorPerViewFromReprojectedImgPts1[0], nframes, + maxDiffBtwRmsErrors, "per view errors vector"); + int code2 = compare(&rmsErrorPerView2[0], &rmsErrorPerViewFromReprojectedImgPts2[0], nframes, + maxDiffBtwRmsErrors, "per view errors vector"); + if (code1 < 0) + { + ts->printf(cvtest::TS::LOG, + "Some of the per view rms reprojection errors differ between calibration function and reprojected " + "points, for the first camera, testcase %d\n", + testcase); + ts->set_failed_test_info(code1); + return; + } + if (code2 < 0) + { + ts->printf(cvtest::TS::LOG, + "Some of the per view rms reprojection errors differ between calibration function and reprojected " + "points, for the second camera, testcase %d\n", + testcase); + ts->set_failed_test_info(code2); + return; + } +#endif Mat R1, R2, P1, P2, Q; Rect roi1, roi2; @@ -1641,7 +1749,10 @@ protected: Mat& cameraMatrix1, Mat& distCoeffs1, Mat& cameraMatrix2, Mat& distCoeffs2, Size imageSize, Mat& R, Mat& T, - Mat& E, Mat& F, TermCriteria criteria, int flags ); + Mat& E, Mat& F, + std::vector& rotationMatrices, std::vector& translationVectors, + vector& perViewErrors1, vector& perViewErrors2, + TermCriteria criteria, int flags ); virtual void rectify( const Mat& cameraMatrix1, const Mat& distCoeffs1, const Mat& cameraMatrix2, const Mat& distCoeffs2, Size imageSize, const Mat& R, const Mat& T, @@ -1665,11 +1776,64 @@ double CV_StereoCalibrationTest_CPP::calibrateStereoCamera( const vector& rotationMatrices, std::vector& translationVectors, + vector& perViewErrors1, vector& perViewErrors2, + TermCriteria criteria, int flags ) { +#if 1 // not ported: #22519 + CV_UNUSED(rotationMatrices); + CV_UNUSED(translationVectors); + CV_UNUSED(perViewErrors1); + CV_UNUSED(perViewErrors2); return stereoCalibrate( objectPoints, imagePoints1, imagePoints2, cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, R, T, E, F, flags, criteria ); +#else + vector rvecs, tvecs; + Mat perViewErrorsMat; + + double avgErr = stereoCalibrate( objectPoints, imagePoints1, imagePoints2, + cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, + imageSize, R, T, E, F, + rvecs, tvecs, perViewErrorsMat, + flags, criteria ); + + size_t numImgs = imagePoints1.size(); + + if (perViewErrors1.size() != numImgs) + { + perViewErrors1.resize(numImgs); + } + if (perViewErrors2.size() != numImgs) + { + perViewErrors2.resize(numImgs); + } + + for (size_t i = 0; i < numImgs; i++) + { + perViewErrors1[i] = perViewErrorsMat.at((int)i, 0); + perViewErrors2[i] = perViewErrorsMat.at((int)i, 1); + } + + if (rotationMatrices.size() != numImgs) + { + rotationMatrices.resize(numImgs); + } + if (translationVectors.size() != numImgs) + { + translationVectors.resize(numImgs); + } + + for (size_t i = 0; i < numImgs; i++) + { + Mat r9; + cv::Rodrigues( rvecs[i], r9 ); + r9.convertTo(rotationMatrices[i], CV_64F); + tvecs[i].convertTo(translationVectors[i], CV_64F); + } + return avgErr; +#endif } void CV_StereoCalibrationTest_CPP::rectify( const Mat& cameraMatrix1, const Mat& distCoeffs1, diff --git a/modules/calib/test/test_fisheye.cpp b/modules/calib/test/test_fisheye.cpp index 23cfa98889..7c93206153 100644 --- a/modules/calib/test/test_fisheye.cpp +++ b/modules/calib/test/test_fisheye.cpp @@ -152,6 +152,15 @@ TEST_F(fisheyeTest, distortUndistortPoints) TEST_F(fisheyeTest, undistortImage) { + // we use it to reduce patch size for images in testdata + auto throwAwayHalf = [](Mat img) + { + int whalf = img.cols / 2, hhalf = img.rows / 2; + Rect tl(0, 0, whalf, hhalf), br(whalf, hhalf, whalf, hhalf); + img(tl) = 0; + img(br) = 0; + }; + cv::Matx33d theK = this->K; cv::Mat theD = cv::Mat(this->D); std::string file = combine(datasets_repository_path, "/calib-3_stereo_from_JY/left/stereo_pair_014.jpg"); @@ -161,32 +170,41 @@ TEST_F(fisheyeTest, undistortImage) newK(0, 0) = 100; newK(1, 1) = 100; cv::fisheye::undistortImage(distorted, undistorted, theK, theD, newK); - cv::Mat correct = cv::imread(combine(datasets_repository_path, "new_f_100.png")); - if (correct.empty()) - CV_Assert(cv::imwrite(combine(datasets_repository_path, "new_f_100.png"), undistorted)); - else - EXPECT_MAT_NEAR(correct, undistorted, 1e-10); + std::string imageFilename = combine(datasets_repository_path, "new_f_100.png"); + cv::Mat correct = cv::imread(imageFilename); + ASSERT_FALSE(correct.empty()) << "Correct image " << imageFilename.c_str() << " can not be read" << std::endl; + + throwAwayHalf(correct); + throwAwayHalf(undistorted); + + EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } { double balance = 1.0; cv::fisheye::estimateNewCameraMatrixForUndistortRectify(theK, theD, distorted.size(), cv::noArray(), newK, balance); cv::fisheye::undistortImage(distorted, undistorted, theK, theD, newK); - cv::Mat correct = cv::imread(combine(datasets_repository_path, "balance_1.0.png")); - if (correct.empty()) - CV_Assert(cv::imwrite(combine(datasets_repository_path, "balance_1.0.png"), undistorted)); - else - EXPECT_MAT_NEAR(correct, undistorted, 1e-10); + std::string imageFilename = combine(datasets_repository_path, "balance_1.0.png"); + cv::Mat correct = cv::imread(imageFilename); + ASSERT_FALSE(correct.empty()) << "Correct image " << imageFilename.c_str() << " can not be read" << std::endl; + + throwAwayHalf(correct); + throwAwayHalf(undistorted); + + EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } { double balance = 0.0; cv::fisheye::estimateNewCameraMatrixForUndistortRectify(theK, theD, distorted.size(), cv::noArray(), newK, balance); cv::fisheye::undistortImage(distorted, undistorted, theK, theD, newK); - cv::Mat correct = cv::imread(combine(datasets_repository_path, "balance_0.0.png")); - if (correct.empty()) - CV_Assert(cv::imwrite(combine(datasets_repository_path, "balance_0.0.png"), undistorted)); - else - EXPECT_MAT_NEAR(correct, undistorted, 1e-10); + std::string imageFilename = combine(datasets_repository_path, "balance_0.0.png"); + cv::Mat correct = cv::imread(imageFilename); + ASSERT_FALSE(correct.empty()) << "Correct image " << imageFilename.c_str() << " can not be read" << std::endl; + + throwAwayHalf(correct); + throwAwayHalf(undistorted); + + EXPECT_MAT_NEAR(correct, undistorted, 1e-10); } } @@ -288,7 +306,9 @@ TEST_F(fisheyeTest, undistortAndDistortImage) EXPECT_MAT_NEAR(dist_point_4, dist_point_4_gt, 1e-2); EXPECT_MAT_NEAR(dist_point_5, dist_point_5_gt, 1e-2); - CV_Assert(cv::imwrite(combine(datasets_repository_path, "new_distortion.png"), image_projected)); + // Add the "--test_debug" to arguments for file output + if (cvtest::debugLevel > 0) + cv::imwrite(combine(datasets_repository_path, "new_distortion.png"), image_projected); } TEST_F(fisheyeTest, jacobians) @@ -619,19 +639,19 @@ TEST_F(fisheyeTest, stereoRectify) 0.002076471801477729, 0.006463478587068991, 0.9999769555891836 ); cv::Matx34d P1_ref( - 420.8551870450913, 0, 586.501617798451, 0, - 0, 420.8551870450913, 374.7667511986098, 0, + 420.9684016542647, 0, 586.3059567784627, 0, + 0, 420.9684016542647, 374.8571836462291, 0, 0, 0, 1, 0 ); cv::Matx34d P2_ref( - 420.8551870450913, 0, 586.501617798451, -41.77758076597302, - 0, 420.8551870450913, 374.7667511986098, 0, + 420.9684016542647, 0, 586.3059567784627, -41.78881938824554, + 0, 420.9684016542647, 374.8571836462291, 0, 0, 0, 1, 0 ); cv::Matx44d Q_ref( - 1, 0, 0, -586.501617798451, - 0, 1, 0, -374.7667511986098, - 0, 0, 0, 420.8551870450913, + 1, 0, 0, -586.3059567784627, + 0, 1, 0, -374.8571836462291, + 0, 0, 0, 420.9684016542647, 0, 0, 10.07370889670733, -0 ); @@ -686,7 +706,9 @@ TEST_F(fisheyeTest, stereoRectify) cv::Mat rectification; merge4(l, r, lundist, rundist, rectification); - cv::imwrite(cv::format("fisheye_rectification_AB_%03d.png", i), rectification); + // Add the "--test_debug" to arguments for file output + if (cvtest::debugLevel > 0) + cv::imwrite(cv::format("fisheye_rectification_AB_%03d.png", i), rectification); } } @@ -861,6 +883,115 @@ TEST_F(fisheyeTest, CalibrationWithDifferentPointsNumber) cv::noArray(), cv::noArray(), flag, cv::TermCriteria(3, 20, 1e-6)); } +#if 0 // not ported: #22519 +TEST_F(fisheyeTest, stereoCalibrateWithPerViewTransformations) +{ + const int n_images = 34; + + const std::string folder = combine(datasets_repository_path, "calib-3_stereo_from_JY"); + + std::vector > leftPoints(n_images); + std::vector > rightPoints(n_images); + std::vector > objectPoints(n_images); + + cv::FileStorage fs_left(combine(folder, "left.xml"), cv::FileStorage::READ); + CV_Assert(fs_left.isOpened()); + for(int i = 0; i < n_images; ++i) + fs_left[cv::format("image_%d", i )] >> leftPoints[i]; + fs_left.release(); + + cv::FileStorage fs_right(combine(folder, "right.xml"), cv::FileStorage::READ); + CV_Assert(fs_right.isOpened()); + for(int i = 0; i < n_images; ++i) + fs_right[cv::format("image_%d", i )] >> rightPoints[i]; + fs_right.release(); + + cv::FileStorage fs_object(combine(folder, "object.xml"), cv::FileStorage::READ); + CV_Assert(fs_object.isOpened()); + for(int i = 0; i < n_images; ++i) + fs_object[cv::format("image_%d", i )] >> objectPoints[i]; + fs_object.release(); + + cv::Matx33d K1, K2, theR; + cv::Vec3d theT; + cv::Vec4d D1, D2; + + std::vector rvecs, tvecs; + + int flag = 0; + flag |= cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC; + flag |= cv::fisheye::CALIB_CHECK_COND; + flag |= cv::fisheye::CALIB_FIX_SKEW; + + double rmsErrorStereoCalib = cv::fisheye::stereoCalibrate(objectPoints, leftPoints, rightPoints, + K1, D1, K2, D2, imageSize, theR, theT, rvecs, tvecs, flag, + cv::TermCriteria(3, 12, 0)); + + std::vector reprojectedImgPts[2] = {std::vector(n_images), std::vector(n_images)}; + size_t totalPoints = 0; + double totalMSError[2] = { 0, 0 }; + for( size_t i = 0; i < n_images; i++ ) + { + cv::Matx33d viewRotMat1, viewRotMat2; + cv::Vec3d viewT1, viewT2; + cv::Mat rVec; + cv::Rodrigues( rvecs[i], rVec ); + rVec.convertTo(viewRotMat1, CV_64F); + tvecs[i].convertTo(viewT1, CV_64F); + + viewRotMat2 = theR * viewRotMat1; + cv::Vec3d T2t = theR * viewT1; + viewT2 = T2t + theT; + + cv::Vec3d viewRotVec1, viewRotVec2; + cv::Rodrigues(viewRotMat1, viewRotVec1); + cv::Rodrigues(viewRotMat2, viewRotVec2); + + double alpha1 = K1(0, 1) / K1(0, 0); + double alpha2 = K2(0, 1) / K2(0, 0); + cv::fisheye::projectPoints(objectPoints[i], reprojectedImgPts[0], viewRotVec1, viewT1, K1, D1, alpha1); + cv::fisheye::projectPoints(objectPoints[i], reprojectedImgPts[1], viewRotVec2, viewT2, K2, D2, alpha2); + + double viewMSError[2] = { + cv::norm(leftPoints[i], reprojectedImgPts[0], cv::NORM_L2SQR), + cv::norm(rightPoints[i], reprojectedImgPts[1], cv::NORM_L2SQR) + }; + + size_t n = objectPoints[i].size(); + totalMSError[0] += viewMSError[0]; + totalMSError[1] += viewMSError[1]; + totalPoints += n; + } + double rmsErrorFromReprojectedImgPts = std::sqrt((totalMSError[0] + totalMSError[1]) / (2 * totalPoints)); + + cv::Matx33d R_correct( 0.9975587205950972, 0.06953016383322372, 0.006492709911733523, + -0.06956823121068059, 0.9975601387249519, 0.005833595226966235, + -0.006071257768382089, -0.006271040135405457, 0.9999619062167968); + cv::Vec3d T_correct(-0.099402724724121, 0.00270812139265413, 0.00129330292472699); + cv::Matx33d K1_correct (561.195925927249, 0, 621.282400272412, + 0, 562.849402029712, 380.555455380889, + 0, 0, 1); + + cv::Matx33d K2_correct (560.395452535348, 0, 678.971652040359, + 0, 561.90171021422, 380.401340535339, + 0, 0, 1); + + cv::Vec4d D1_correct (-7.44253716539556e-05, -0.00702662033932424, 0.00737569823650885, -0.00342230256441771); + cv::Vec4d D2_correct (-0.0130785435677431, 0.0284434505383497, -0.0360333869900506, 0.0144724062347222); + + EXPECT_MAT_NEAR(theR, R_correct, 1e-10); + EXPECT_MAT_NEAR(theT, T_correct, 1e-10); + + EXPECT_MAT_NEAR(K1, K1_correct, 1e-10); + EXPECT_MAT_NEAR(K2, K2_correct, 1e-10); + + EXPECT_MAT_NEAR(D1, D1_correct, 1e-10); + EXPECT_MAT_NEAR(D2, D2_correct, 1e-10); + + EXPECT_NEAR(rmsErrorStereoCalib, rmsErrorFromReprojectedImgPts, 1e-4); +} +#endif + TEST_F(fisheyeTest, estimateNewCameraMatrixForUndistortRectify) { cv::Size size(1920, 1080); @@ -880,13 +1011,13 @@ TEST_F(fisheyeTest, estimateNewCameraMatrixForUndistortRectify) cv::Mat K_new_truth(3, 3, cv::DataType::type); - K_new_truth.at(0, 0) = 387.4809086880343; + K_new_truth.at(0, 0) = 387.5118215642316; K_new_truth.at(0, 1) = 0.0; - K_new_truth.at(0, 2) = 1036.669802754649; + K_new_truth.at(0, 2) = 1033.936556777084; K_new_truth.at(1, 0) = 0.0; - K_new_truth.at(1, 1) = 373.6375700303157; - K_new_truth.at(1, 2) = 538.8373261247601; + K_new_truth.at(1, 1) = 373.6673784974842; + K_new_truth.at(1, 2) = 538.794152656429; K_new_truth.at(2, 0) = 0.0; K_new_truth.at(2, 1) = 0.0; diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index f7807e37ec..d34dd9fff5 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -1102,6 +1102,13 @@ around both axes. */ CV_EXPORTS_W void flip(InputArray src, OutputArray dst, int flipCode); +/** @brief Flips a n-dimensional at given axis + * @param src input array + * @param dst output array that has the same shape of src + * @param axis axis that performs a flip on. 0 <= axis < src.dims. + */ +CV_EXPORTS_W void flipND(InputArray src, OutputArray dst, int axis); + enum RotateFlags { ROTATE_90_CLOCKWISE = 0, //!& fs, const String& name = String()) const; + CV_WRAP void write(FileStorage& fs, const String& name) const; /** @brief Reads algorithm parameters from a file storage */ diff --git a/modules/core/include/opencv2/core/bindings_utils.hpp b/modules/core/include/opencv2/core/bindings_utils.hpp index 4f7eb532b9..001d91c381 100644 --- a/modules/core/include/opencv2/core/bindings_utils.hpp +++ b/modules/core/include/opencv2/core/bindings_utils.hpp @@ -35,6 +35,14 @@ String dumpInt(int argument) return cv::format("Int: %d", argument); } +CV_WRAP static inline +String dumpInt64(int64 argument) +{ + std::ostringstream oss("Int64: ", std::ios::ate); + oss << argument; + return oss.str(); +} + CV_WRAP static inline String dumpSizeT(size_t argument) { @@ -219,6 +227,22 @@ AsyncArray testAsyncException() return p.getArrayResult(); } +CV_WRAP static inline +String dumpVec2i(const cv::Vec2i value = cv::Vec2i(42, 24)) { + return format("Vec2i(%d, %d)", value[0], value[1]); +} + +struct CV_EXPORTS_W_SIMPLE ClassWithKeywordProperties { + CV_PROP_RW int lambda; + CV_PROP int except; + + CV_WRAP explicit ClassWithKeywordProperties(int lambda_arg = 24, int except_arg = 42) + { + lambda = lambda_arg; + except = except_arg; + } +}; + namespace nested { CV_WRAP static inline bool testEchoBooleanFunction(bool flag) { return flag; diff --git a/modules/core/include/opencv2/core/check.hpp b/modules/core/include/opencv2/core/check.hpp index a32b8111ce..c9ce97b6ae 100644 --- a/modules/core/include/opencv2/core/check.hpp +++ b/modules/core/include/opencv2/core/check.hpp @@ -65,6 +65,7 @@ struct CheckContext { static const cv::detail::CheckContext CV__CHECK_LOCATION_VARNAME(id) = \ { CV__CHECK_FUNCTION, CV__CHECK_FILENAME, __LINE__, testOp, "" message, "" p1_str, "" p2_str } +CV_EXPORTS void CV_NORETURN check_failed_auto(const bool v1, const bool v2, const CheckContext& ctx); CV_EXPORTS void CV_NORETURN check_failed_auto(const int v1, const int v2, const CheckContext& ctx); CV_EXPORTS void CV_NORETURN check_failed_auto(const size_t v1, const size_t v2, const CheckContext& ctx); CV_EXPORTS void CV_NORETURN check_failed_auto(const float v1, const float v2, const CheckContext& ctx); @@ -74,6 +75,9 @@ CV_EXPORTS void CV_NORETURN check_failed_MatDepth(const int v1, const int v2, co CV_EXPORTS void CV_NORETURN check_failed_MatType(const int v1, const int v2, const CheckContext& ctx); CV_EXPORTS void CV_NORETURN check_failed_MatChannels(const int v1, const int v2, const CheckContext& ctx); +CV_EXPORTS void CV_NORETURN check_failed_true(const bool v, const CheckContext& ctx); +CV_EXPORTS void CV_NORETURN check_failed_false(const bool v, const CheckContext& ctx); + CV_EXPORTS void CV_NORETURN check_failed_auto(const int v, const CheckContext& ctx); CV_EXPORTS void CV_NORETURN check_failed_auto(const size_t v, const CheckContext& ctx); CV_EXPORTS void CV_NORETURN check_failed_auto(const float v, const CheckContext& ctx); @@ -134,6 +138,12 @@ CV_EXPORTS void CV_NORETURN check_failed_MatChannels(const int v, const CheckCon /// Example: v == A || v == B #define CV_Check(v, test_expr, msg) CV__CHECK_CUSTOM_TEST(_, auto, v, (test_expr), #v, #test_expr, msg) +/// Example: v == true +#define CV_CheckTrue(v, msg) CV__CHECK_CUSTOM_TEST(_, true, v, v, #v, "", msg) + +/// Example: v == false +#define CV_CheckFalse(v, msg) CV__CHECK_CUSTOM_TEST(_, false, v, (!(v)), #v, "", msg) + /// Some complex conditions: CV_Check(src2, src2.empty() || (src2.type() == src1.type() && src2.size() == src1.size()), "src2 should have same size/type as src1") // TODO define pretty-printers diff --git a/modules/core/include/opencv2/core/cuda/common.hpp b/modules/core/include/opencv2/core/cuda/common.hpp index 80b2ff08b1..836254228b 100644 --- a/modules/core/include/opencv2/core/cuda/common.hpp +++ b/modules/core/include/opencv2/core/cuda/common.hpp @@ -65,8 +65,10 @@ namespace cv { namespace cuda { static inline void checkCudaError(cudaError_t err, const char* file, const int line, const char* func) { - if (cudaSuccess != err) + if (cudaSuccess != err) { + cudaGetLastError(); // reset the last stored error to cudaSuccess cv::error(cv::Error::GpuApiCallError, cudaGetErrorString(err), func, file, line); + } } }} @@ -95,26 +97,6 @@ namespace cv { namespace cuda { return (total + grain - 1) / grain; } - - template inline void bindTexture(const textureReference* tex, const PtrStepSz& img) - { - cudaChannelFormatDesc desc = cudaCreateChannelDesc(); - cudaSafeCall( cudaBindTexture2D(0, tex, img.ptr(), &desc, img.cols, img.rows, img.step) ); - } - - template inline void createTextureObjectPitch2D(cudaTextureObject_t* tex, PtrStepSz& img, const cudaTextureDesc& texDesc) - { - cudaResourceDesc resDesc; - memset(&resDesc, 0, sizeof(resDesc)); - resDesc.resType = cudaResourceTypePitch2D; - resDesc.res.pitch2D.devPtr = static_cast(img.ptr()); - resDesc.res.pitch2D.height = img.rows; - resDesc.res.pitch2D.width = img.cols; - resDesc.res.pitch2D.pitchInBytes = img.step; - resDesc.res.pitch2D.desc = cudaCreateChannelDesc(); - - cudaSafeCall( cudaCreateTextureObject(tex, &resDesc, &texDesc, NULL) ); - } } }} diff --git a/modules/core/include/opencv2/core/cv_cpu_dispatch.h b/modules/core/include/opencv2/core/cv_cpu_dispatch.h index 12e4cb47b8..3235b6317e 100644 --- a/modules/core/include/opencv2/core/cv_cpu_dispatch.h +++ b/modules/core/include/opencv2/core/cv_cpu_dispatch.h @@ -172,6 +172,11 @@ # define CV_MSA 1 #endif +#ifdef CV_CPU_COMPILE_LASX +# include +# define CV_LASX 1 +#endif + #ifdef __EMSCRIPTEN__ # define CV_WASM_SIMD 1 # include @@ -370,3 +375,7 @@ struct VZeroUpperGuard { #ifndef CV_RVV # define CV_RVV 0 #endif + +#ifndef CV_LASX +# define CV_LASX 0 +#endif diff --git a/modules/core/include/opencv2/core/cv_cpu_helper.h b/modules/core/include/opencv2/core/cv_cpu_helper.h index 91b853de0c..41fc9d50fa 100644 --- a/modules/core/include/opencv2/core/cv_cpu_helper.h +++ b/modules/core/include/opencv2/core/cv_cpu_helper.h @@ -525,5 +525,26 @@ #endif #define __CV_CPU_DISPATCH_CHAIN_RVV(fn, args, mode, ...) CV_CPU_CALL_RVV(fn, args); __CV_EXPAND(__CV_CPU_DISPATCH_CHAIN_ ## mode(fn, args, __VA_ARGS__)) +#if !defined CV_DISABLE_OPTIMIZATION && defined CV_ENABLE_INTRINSICS && defined CV_CPU_COMPILE_LASX +# define CV_TRY_LASX 1 +# define CV_CPU_FORCE_LASX 1 +# define CV_CPU_HAS_SUPPORT_LASX 1 +# define CV_CPU_CALL_LASX(fn, args) return (cpu_baseline::fn args) +# define CV_CPU_CALL_LASX_(fn, args) return (opt_LASX::fn args) +#elif !defined CV_DISABLE_OPTIMIZATION && defined CV_ENABLE_INTRINSICS && defined CV_CPU_DISPATCH_COMPILE_LASX +# define CV_TRY_LASX 1 +# define CV_CPU_FORCE_LASX 0 +# define CV_CPU_HAS_SUPPORT_LASX (cv::checkHardwareSupport(CV_CPU_LASX)) +# define CV_CPU_CALL_LASX(fn, args) if (CV_CPU_HAS_SUPPORT_LASX) return (opt_LASX::fn args) +# define CV_CPU_CALL_LASX_(fn, args) if (CV_CPU_HAS_SUPPORT_LASX) return (opt_LASX::fn args) +#else +# define CV_TRY_LASX 0 +# define CV_CPU_FORCE_LASX 0 +# define CV_CPU_HAS_SUPPORT_LASX 0 +# define CV_CPU_CALL_LASX(fn, args) +# define CV_CPU_CALL_LASX_(fn, args) +#endif +#define __CV_CPU_DISPATCH_CHAIN_LASX(fn, args, mode, ...) CV_CPU_CALL_LASX(fn, args); __CV_EXPAND(__CV_CPU_DISPATCH_CHAIN_ ## mode(fn, args, __VA_ARGS__)) + #define CV_CPU_CALL_BASELINE(fn, args) return (cpu_baseline::fn args) #define __CV_CPU_DISPATCH_CHAIN_BASELINE(fn, args, mode, ...) CV_CPU_CALL_BASELINE(fn, args) /* last in sequence */ diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 8879ec1cec..79847578b4 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -279,6 +279,8 @@ namespace cv { #define CV_CPU_RVV 210 +#define CV_CPU_LASX 230 + // CPU features groups #define CV_CPU_AVX512_SKX 256 #define CV_CPU_AVX512_COMMON 257 @@ -336,6 +338,8 @@ enum CpuFeatures { CPU_RVV = 210, + CPU_LASX = 230, + CPU_AVX512_SKX = 256, //!< Skylake-X with AVX-512F/CD/BW/DQ/VL CPU_AVX512_COMMON = 257, //!< Common instructions AVX-512F/CD for all CPUs that support AVX-512 CPU_AVX512_KNL = 258, //!< Knights Landing with AVX-512F/CD/ER/PF @@ -808,7 +812,7 @@ __CV_ENUM_FLAGS_BITWISE_XOR_EQ (EnumType, EnumType) # define CV_CONSTEXPR #endif -// Integer types portatibility +// Integer types portability #ifdef OPENCV_STDINT_HEADER #include OPENCV_STDINT_HEADER #elif defined(__cplusplus) diff --git a/modules/core/include/opencv2/core/fast_math.hpp b/modules/core/include/opencv2/core/fast_math.hpp index cd5de0b546..9ee7dba672 100644 --- a/modules/core/include/opencv2/core/fast_math.hpp +++ b/modules/core/include/opencv2/core/fast_math.hpp @@ -128,8 +128,12 @@ #define CV_INLINE_ISNAN_FLT(value) CV_INLINE_ISNAN_DBL(value) #endif - #if !defined(OPENCV_USE_FASTMATH_BUILTINS) && \ - (defined __GNUC__ || defined __clang__ || defined _MSC_VER) + #if !defined(OPENCV_USE_FASTMATH_BUILTINS) \ + && ( \ + defined(__x86_64__) || defined(__i686__) \ + || defined(__arm__) \ + || defined(__PPC64__) \ + ) /* Let builtin C math functions when available. Dedicated hardware is available to round and convert FP values. */ #define OPENCV_USE_FASTMATH_BUILTINS 1 @@ -229,6 +233,15 @@ CV_INLINE int cvFloor( double value ) #if defined CV__FASTMATH_ENABLE_GCC_MATH_BUILTINS || \ defined CV__FASTMATH_ENABLE_CLANG_MATH_BUILTINS return (int)__builtin_floor(value); +#elif defined __loongarch64 + int i; + double tmp; + __asm__ ("ftintrm.l.d %[tmp], %[in] \n\t" + "movfr2gr.d %[i], %[tmp] \n\t" + : [i] "=r" (i), [tmp] "=f" (tmp) + : [in] "f" (value) + :); + return i; #else int i = (int)value; return i - (i > value); @@ -247,6 +260,15 @@ CV_INLINE int cvCeil( double value ) #if defined CV__FASTMATH_ENABLE_GCC_MATH_BUILTINS || \ defined CV__FASTMATH_ENABLE_CLANG_MATH_BUILTINS return (int)__builtin_ceil(value); +#elif defined __loongarch64 + int i; + double tmp; + __asm__ ("ftintrp.l.d %[tmp], %[in] \n\t" + "movfr2gr.d %[i], %[tmp] \n\t" + : [i] "=r" (i), [tmp] "=f" (tmp) + : [in] "f" (value) + :); + return i; #else int i = (int)value; return i + (i < value); @@ -281,7 +303,7 @@ CV_INLINE int cvIsInf( double value ) { #if defined CV_INLINE_ISINF_DBL CV_INLINE_ISINF_DBL(value); -#elif defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__PPC64__) +#elif defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__PPC64__) || defined(__loongarch64) Cv64suf ieee754; ieee754.f = value; return (ieee754.u & 0x7fffffff00000000) == @@ -332,6 +354,15 @@ CV_INLINE int cvFloor( float value ) #if defined CV__FASTMATH_ENABLE_GCC_MATH_BUILTINS || \ defined CV__FASTMATH_ENABLE_CLANG_MATH_BUILTINS return (int)__builtin_floorf(value); +#elif defined __loongarch + int i; + float tmp; + __asm__ ("ftintrm.w.s %[tmp], %[in] \n\t" + "movfr2gr.s %[i], %[tmp] \n\t" + : [i] "=r" (i), [tmp] "=f" (tmp) + : [in] "f" (value) + :); + return i; #else int i = (int)value; return i - (i > value); @@ -350,6 +381,15 @@ CV_INLINE int cvCeil( float value ) #if defined CV__FASTMATH_ENABLE_GCC_MATH_BUILTINS || \ defined CV__FASTMATH_ENABLE_CLANG_MATH_BUILTINS return (int)__builtin_ceilf(value); +#elif defined __loongarch + int i; + float tmp; + __asm__ ("ftintrp.w.s %[tmp], %[in] \n\t" + "movfr2gr.s %[i], %[tmp] \n\t" + : [i] "=r" (i), [tmp] "=f" (tmp) + : [in] "f" (value) + :); + return i; #else int i = (int)value; return i + (i < value); diff --git a/modules/core/include/opencv2/core/hal/intrin.hpp b/modules/core/include/opencv2/core/hal/intrin.hpp index a04de3a12d..207b8cab4e 100644 --- a/modules/core/include/opencv2/core/hal/intrin.hpp +++ b/modules/core/include/opencv2/core/hal/intrin.hpp @@ -50,6 +50,12 @@ #include #include "opencv2/core/cvdef.h" +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + #define OPENCV_HAL_ADD(a, b) ((a) + (b)) #define OPENCV_HAL_AND(a, b) ((a) & (b)) #define OPENCV_HAL_NOP(a) (a) @@ -229,10 +235,19 @@ using namespace CV_CPU_OPTIMIZATION_HAL_NAMESPACE; #elif CV_WASM_SIMD && !defined(CV_FORCE_SIMD128_CPP) #include "opencv2/core/hal/intrin_wasm.hpp" -#elif CV_RVV && !defined(CV_FORCE_SIMD128_CPP) && !defined(CV_RVV_SCALABLE) -#include "opencv2/core/hal/intrin_rvv.hpp" -#elif CV_RVV && !defined(CV_FORCE_SIMD128_CPP) && CV_RVV_SCALABLE +#elif CV_RVV && !defined(CV_FORCE_SIMD128_CPP) +#if defined(CV_RVV_SCALABLE) #include "opencv2/core/hal/intrin_rvv_scalable.hpp" +#else +#include "opencv2/core/hal/intrin_rvv.hpp" +#endif + +#elif CV_LASX + #if !defined(CV_FORCE_SIMD128_CPP) + #define CV_FORCE_SIMD128_CPP 1 + #endif +#include "opencv2/core/hal/intrin_cpp.hpp" + #else #include "opencv2/core/hal/intrin_cpp.hpp" @@ -267,6 +282,14 @@ using namespace CV_CPU_OPTIMIZATION_HAL_NAMESPACE; #endif +#if CV_LASX + +#define CV__SIMD_FORWARD 256 +#include "opencv2/core/hal/intrin_forward.hpp" +#include "opencv2/core/hal/intrin_lasx.hpp" + +#endif + //! @cond IGNORED namespace cv { @@ -522,6 +545,11 @@ using namespace CV__SIMD_NAMESPACE; #endif +//! @cond IGNORED +#ifndef CV_SIMD_64F +#define CV_SIMD_64F 0 +#endif + namespace CV__SIMD_NAMESPACE { //! @addtogroup core_hal_intrin //! @{ @@ -537,7 +565,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_setall_f32(float v) { return VXPREFIX(_setall_f32)(v); } inline v_int64 vx_setall_s64(int64 v) { return VXPREFIX(_setall_s64)(v); } inline v_uint64 vx_setall_u64(uint64 v) { return VXPREFIX(_setall_u64)(v); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_setall_f64(double v) { return VXPREFIX(_setall_f64)(v); } #endif //! @} @@ -554,7 +582,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_setzero_f32() { return VXPREFIX(_setzero_f32)(); } inline v_int64 vx_setzero_s64() { return VXPREFIX(_setzero_s64)(); } inline v_uint64 vx_setzero_u64() { return VXPREFIX(_setzero_u64)(); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_setzero_f64() { return VXPREFIX(_setzero_f64)(); } #endif //! @} @@ -571,7 +599,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_load(const float * ptr) { return VXPREFIX(_load)(ptr); } inline v_int64 vx_load(const int64 * ptr) { return VXPREFIX(_load)(ptr); } inline v_uint64 vx_load(const uint64 * ptr) { return VXPREFIX(_load)(ptr); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_load(const double * ptr) { return VXPREFIX(_load)(ptr); } #endif //! @} @@ -588,7 +616,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_load_aligned(const float * ptr) { return VXPREFIX(_load_aligned)(ptr); } inline v_int64 vx_load_aligned(const int64 * ptr) { return VXPREFIX(_load_aligned)(ptr); } inline v_uint64 vx_load_aligned(const uint64 * ptr) { return VXPREFIX(_load_aligned)(ptr); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_load_aligned(const double * ptr) { return VXPREFIX(_load_aligned)(ptr); } #endif //! @} @@ -605,7 +633,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_load_low(const float * ptr) { return VXPREFIX(_load_low)(ptr); } inline v_int64 vx_load_low(const int64 * ptr) { return VXPREFIX(_load_low)(ptr); } inline v_uint64 vx_load_low(const uint64 * ptr) { return VXPREFIX(_load_low)(ptr); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_load_low(const double * ptr) { return VXPREFIX(_load_low)(ptr); } #endif //! @} @@ -622,7 +650,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_load_halves(const float * ptr0, const float * ptr1) { return VXPREFIX(_load_halves)(ptr0, ptr1); } inline v_int64 vx_load_halves(const int64 * ptr0, const int64 * ptr1) { return VXPREFIX(_load_halves)(ptr0, ptr1); } inline v_uint64 vx_load_halves(const uint64 * ptr0, const uint64 * ptr1) { return VXPREFIX(_load_halves)(ptr0, ptr1); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_load_halves(const double * ptr0, const double * ptr1) { return VXPREFIX(_load_halves)(ptr0, ptr1); } #endif //! @} @@ -639,7 +667,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_lut(const float* ptr, const int* idx) { return VXPREFIX(_lut)(ptr, idx); } inline v_int64 vx_lut(const int64 * ptr, const int* idx) { return VXPREFIX(_lut)(ptr, idx); } inline v_uint64 vx_lut(const uint64 * ptr, const int* idx) { return VXPREFIX(_lut)(ptr, idx); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_lut(const double* ptr, const int* idx) { return VXPREFIX(_lut)(ptr, idx); } #endif //! @} @@ -656,7 +684,7 @@ namespace CV__SIMD_NAMESPACE { inline v_float32 vx_lut_pairs(const float* ptr, const int* idx) { return VXPREFIX(_lut_pairs)(ptr, idx); } inline v_int64 vx_lut_pairs(const int64 * ptr, const int* idx) { return VXPREFIX(_lut_pairs)(ptr, idx); } inline v_uint64 vx_lut_pairs(const uint64 * ptr, const int* idx) { return VXPREFIX(_lut_pairs)(ptr, idx); } -#if CV_SIMD_64F +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F inline v_float64 vx_lut_pairs(const double* ptr, const int* idx) { return VXPREFIX(_lut_pairs)(ptr, idx); } #endif //! @} @@ -758,7 +786,6 @@ namespace CV__SIMD_NAMESPACE { OPENCV_HAL_WRAP_BIN_OP_LOGIC(v_int32) OPENCV_HAL_WRAP_BIN_OP_LOGIC(v_int64) - #define OPENCV_HAL_WRAP_BIN_OP_MUL(_Tpvec) \ inline _Tpvec v_mul(const _Tpvec& a, const _Tpvec& b) \ { \ @@ -886,10 +913,6 @@ namespace CV__SIMD_NAMESPACE { #undef VXPREFIX } // namespace -//! @cond IGNORED -#ifndef CV_SIMD_64F -#define CV_SIMD_64F 0 -#endif #ifndef CV_SIMD_FP16 #define CV_SIMD_FP16 0 //!< Defined to 1 on native support of operations with float16x8_t / float16x16_t (SIMD256) types @@ -909,4 +932,8 @@ CV_CPU_OPTIMIZATION_HAL_NAMESPACE_END //! @endcond +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic pop +#endif + #endif diff --git a/modules/core/include/opencv2/core/hal/intrin_lasx.hpp b/modules/core/include/opencv2/core/hal/intrin_lasx.hpp new file mode 100644 index 0000000000..37f2e3f81d --- /dev/null +++ b/modules/core/include/opencv2/core/hal/intrin_lasx.hpp @@ -0,0 +1,3236 @@ +// 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_INTRIN_LASX_HPP +#define OPENCV_HAL_INTRIN_LASX_HPP + +#include +#include + +#define CV_SIMD256 1 +#define CV_SIMD256_64F 1 +#define CV_SIMD256_FP16 0 + +namespace cv +{ + +//! @cond IGNORED + +CV_CPU_OPTIMIZATION_HAL_NAMESPACE_BEGIN + +///////// Utils //////////// + +inline __m256i _v256_setr_b(char v0, char v1, char v2, char v3, char v4, char v5, char v6, char v7, char v8, char v9, + char v10, char v11, char v12, char v13, char v14, char v15, char v16, char v17, char v18, char v19, + char v20, char v21, char v22, char v23, char v24, char v25, char v26, char v27, char v28, char v29, + char v30, char v31) +{ + return (__m256i)v32i8{ v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, + v30, v31 }; +} + +inline __m256i _v256_set_b(char v0, char v1, char v2, char v3, char v4, char v5, char v6, char v7, char v8, char v9, + char v10, char v11, char v12, char v13, char v14, char v15, char v16, char v17, char v18, char v19, + char v20, char v21, char v22, char v23, char v24, char v25, char v26, char v27, char v28, char v29, + char v30, char v31) +{ + return (__m256i)v32i8{ v31, v30, + v29, v28, v27, v26, v25, v24, v23, v22, v21, v20, + v19, v18, v17, v16, v15, v14, v13, v12, v11, v10, + v9, v8, v7, v6, v5, v4, v3, v2, v1, v0 }; +} + +inline __m256i _v256_setr_h(short v0, short v1, short v2, short v3, short v4, short v5, short v6, short v7, + short v8, short v9, short v10, short v11, short v12, short v13, short v14, short v15) +{ + return (__m256i)v16i16{ v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 }; +} + +inline __m256i _v256_setr_w(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) +{ + return (__m256i)v8i32{ v0, v1, v2, v3, v4, v5, v6, v7 }; +} + +inline __m256i _v256_set_w(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) +{ + return (__m256i)v8i32{ v7, v6, v5, v4, v3, v2, v1, v0 }; +} + +inline __m256i _v256_setall_w(int v0) +{ + return (__m256i)v8i32{ v0, v0, v0, v0, v0, v0, v0, v0 }; +} + +inline __m256i _v256_setr_d(int64 v0, int64 v1, int64 v2, int64 v3) +{ + return (__m256i)v4i64{ v0, v1, v2, v3 }; +} + +inline __m256i _v256_set_d(int64 v0, int64 v1, int64 v2, int64 v3) +{ + return (__m256i)v4i64{ v3, v2, v1, v0 }; +} + +inline __m256 _v256_setr_ps(float v0, float v1, float v2, float v3, float v4, float v5, float v6, float v7) +{ + return (__m256)v8f32{ v0, v1, v2, v3, v4, v5, v6, v7 }; +} + +inline __m256 _v256_setall_ps(float f32) +{ + return (__m256)v8f32{ f32, f32, f32, f32, f32, f32, f32, f32 }; +} + +inline __m256d _v256_setr_pd(double v0, double v1, double v2, double v3) +{ + return (__m256d)v4f64{ v0, v1, v2, v3 }; +} + +inline __m256d _v256_setall_pd(double f64) +{ + return (__m256d)v4f64{ f64, f64, f64, f64 }; +} + +inline __m256i _lasx_packus_h(const __m256i& a, const __m256i& b) +{ + __m256i u8min = __lasx_xvreplgr2vr_h(0); + __m256i u8max = __lasx_xvreplgr2vr_h(255); + __m256i sat_a = __lasx_xvmax_h(a, u8min); + sat_a = __lasx_xvmin_h(sat_a, u8max); + __m256i sat_b = __lasx_xvmax_h(b, u8min); + sat_b = __lasx_xvmin_h(sat_b, u8max); + __m256i byteIndex = _v256_setr_b(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30); + return __lasx_xvshuf_b(sat_b, sat_a, byteIndex); +} + +inline __m256i _lasx_packs_h(const __m256i& a, const __m256i& b) +{ + __m256i s8min = __lasx_xvreplgr2vr_h(-128); + __m256i s8max = __lasx_xvreplgr2vr_h(127); + __m256i sat_a = __lasx_xvmax_h(a, s8min); + sat_a = __lasx_xvmin_h(sat_a, s8max); + __m256i sat_b = __lasx_xvmax_h(b, s8min); + sat_b = __lasx_xvmin_h(sat_b, s8max); + __m256i byteIndex = _v256_setr_b(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30); + return __lasx_xvshuf_b(sat_b, sat_a, byteIndex); +} + +inline __m256i _lasx_packus_w(const __m256i& a, const __m256i& b) +{ + __m256i u16min = __lasx_xvreplgr2vr_w(0); + __m256i u16max = __lasx_xvreplgr2vr_w(0xffff); + __m256i sat_a = __lasx_xvmax_w(a, u16min); + sat_a = __lasx_xvmin_w(sat_a, u16max); + __m256i sat_b = __lasx_xvmax_w(b, u16min); + sat_b = __lasx_xvmin_w(sat_b, u16max); + __m256i hwordIndex = _v256_setr_h(0, 2, 4, 6, 8, 10, 12, 14, + 0, 2, 4, 6, 8, 10, 12, 14); + return __lasx_xvshuf_h(hwordIndex, sat_b, sat_a); +} + +inline __m256i _lasx_packs_w(const __m256i& a, const __m256i& b) +{ + __m256i s16min = __lasx_xvreplgr2vr_w(-0x8000); + __m256i s16max = __lasx_xvreplgr2vr_w(0x7fff); + __m256i sat_a = __lasx_xvmax_w(a, s16min); + sat_a = __lasx_xvmin_w(sat_a, s16max); + __m256i sat_b = __lasx_xvmax_w(b, s16min); + sat_b = __lasx_xvmin_w(sat_b, s16max); + __m256i hwordIndex = _v256_setr_h(0, 2, 4, 6, 8, 10, 12, 14, + 0, 2, 4, 6, 8, 10, 12, 14); + return __lasx_xvshuf_h(hwordIndex, sat_b, sat_a); +} + +inline __m256i _v256_combine(const __m128i& lo, const __m128i& hi) +{ return __lasx_xvpermi_q(*((__m256i*)&lo), *((__m256i*)&hi), 0x02); } + +inline __m256 _v256_combine(const __m128& lo, const __m128& hi) +{ return __m256(__lasx_xvpermi_q(*((__m256i*)&lo), *((__m256i*)&hi), 0x02)); } + +inline __m256d _v256_combine(const __m128d& lo, const __m128d& hi) +{ return __m256d(__lasx_xvpermi_q(*((__m256i*)&lo), *((__m256i*)&hi), 0x02)); } + +inline __m256i _v256_shuffle_odd_64(const __m256i& v) +{ return __lasx_xvpermi_d(v, 0xd8); } + +inline __m256d _v256_shuffle_odd_64(const __m256d& v) +{ return __m256d(__lasx_xvpermi_d(*((__m256i*)&v), 0xd8)); } + +//LASX: only use for permute WITHOUT zero clearing +template +inline __m256i _v256_permute2x128(const __m256i& a, const __m256i& b) +{ return __lasx_xvpermi_q(a, b, imm); } + +template +inline __m256 _v256_permute2x128(const __m256& a, const __m256& b) +{ return __m256(__lasx_xvpermi_q(*((__m256i*)&a), *((__m256i*)&b), imm)); } + +template +inline __m256d _v256_permute2x128(const __m256d& a, const __m256d& b) +{ return __m256d(__lasx_xvpermi_q(*((__m256i*)&a), *((__m256i*)&b), imm)); } + +template +inline _Tpvec v256_permute2x128(const _Tpvec& a, const _Tpvec& b) +{ return _Tpvec(_v256_permute2x128(a.val, b.val)); } + +template +inline __m256i _v256_permute4x64(const __m256i& a) +{ return __lasx_xvpermi_d(a, imm); } + +template +inline __m256d _v256_permute4x64(const __m256d& a) +{ return __m256d(__lasx_xvpermi_d(*((__m256i*)&a), imm)); } + +template +inline _Tpvec v256_permute4x64(const _Tpvec& a) +{ return _Tpvec(_v256_permute4x64(a.val)); } + +inline __m128i _v256_extract_high(const __m256i& v) +{ __m256i temp256i = __lasx_xvpermi_q(v, v, 0x31); + return *((__m128i*)&temp256i); } + +inline __m128 _v256_extract_high(const __m256& v) +{ return __m128(_v256_extract_high(*((__m256i*)&v))); } + +inline __m128d _v256_extract_high(const __m256d& v) +{ return __m128d(_v256_extract_high(*((__m256i*)&v))); } + +inline __m128i _v256_extract_low(const __m256i& v) +{ return *((__m128i*)&v); } + +inline __m128 _v256_extract_low(const __m256& v) +{ return __m128(_v256_extract_low(*((__m256i*)&v))); } + +inline __m128d _v256_extract_low(const __m256d& v) +{ return __m128d(_v256_extract_low(*((__m256i*)&v))); } + +inline __m256i _v256_packs_epu32(const __m256i& a, const __m256i& b) +{ + const __m256i maxv = __lasx_xvreplgr2vr_w(65535); + __m256i am = __lasx_xvmin_wu(a, maxv); + __m256i bm = __lasx_xvmin_wu(b, maxv); + return _lasx_packus_w(am, bm); +} + +template +inline int _v256_extract_b(const __m256i& a) +{ + int des[1] = {0}; + __lasx_xvstelm_b(a, des, 0, i); + return des[0]; +} + +template +inline int _v256_extract_h(const __m256i& a) +{ + int des[1] = {0}; + __lasx_xvstelm_h(a, des, 0, i); + return des[0]; +} + +template +inline int _v256_extract_w(const __m256i& a) +{ + return __lasx_xvpickve2gr_w(a, i); +} + +template +inline int64 _v256_extract_d(const __m256i& a) +{ + return __lasx_xvpickve2gr_d(a, i); +} + +///////// Types //////////// + +struct v_uint8x32 +{ + typedef uchar lane_type; + enum { nlanes = 32 }; + __m256i val; + + explicit v_uint8x32(__m256i v) : val(v) {} + v_uint8x32(uchar v0, uchar v1, uchar v2, uchar v3, + uchar v4, uchar v5, uchar v6, uchar v7, + uchar v8, uchar v9, uchar v10, uchar v11, + uchar v12, uchar v13, uchar v14, uchar v15, + uchar v16, uchar v17, uchar v18, uchar v19, + uchar v20, uchar v21, uchar v22, uchar v23, + uchar v24, uchar v25, uchar v26, uchar v27, + uchar v28, uchar v29, uchar v30, uchar v31) + { + val = _v256_setr_b((char)v0, (char)v1, (char)v2, (char)v3, + (char)v4, (char)v5, (char)v6 , (char)v7, (char)v8, (char)v9, + (char)v10, (char)v11, (char)v12, (char)v13, (char)v14, (char)v15, + (char)v16, (char)v17, (char)v18, (char)v19, (char)v20, (char)v21, + (char)v22, (char)v23, (char)v24, (char)v25, (char)v26, (char)v27, + (char)v28, (char)v29, (char)v30, (char)v31); + } + /* coverity[uninit_ctor]: suppress warning */ + v_uint8x32() {} + + uchar get0() const { + uchar des[1] = {0}; + __lasx_xvstelm_b(val, des, 0, 0); + return des[0]; + } +}; + +struct v_int8x32 +{ + typedef schar lane_type; + enum { nlanes = 32 }; + __m256i val; + + explicit v_int8x32(__m256i v) : val(v) {} + v_int8x32(schar v0, schar v1, schar v2, schar v3, + schar v4, schar v5, schar v6, schar v7, + schar v8, schar v9, schar v10, schar v11, + schar v12, schar v13, schar v14, schar v15, + schar v16, schar v17, schar v18, schar v19, + schar v20, schar v21, schar v22, schar v23, + schar v24, schar v25, schar v26, schar v27, + schar v28, schar v29, schar v30, schar v31) + { + val = _v256_setr_b(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, + v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31); + } + /* coverity[uninit_ctor]: suppress warning */ + v_int8x32() {} + + schar get0() const { + schar des[1] = {0}; + __lasx_xvstelm_b(val, des, 0, 0); + return des[0]; + } +}; + +struct v_uint16x16 +{ + typedef ushort lane_type; + enum { nlanes = 16 }; + __m256i val; + + explicit v_uint16x16(__m256i v) : val(v) {} + v_uint16x16(ushort v0, ushort v1, ushort v2, ushort v3, + ushort v4, ushort v5, ushort v6, ushort v7, + ushort v8, ushort v9, ushort v10, ushort v11, + ushort v12, ushort v13, ushort v14, ushort v15) + { + val = _v256_setr_h((short)v0, (short)v1, (short)v2, (short)v3, + (short)v4, (short)v5, (short)v6, (short)v7, (short)v8, (short)v9, + (short)v10, (short)v11, (short)v12, (short)v13, (short)v14, (short)v15); + } + /* coverity[uninit_ctor]: suppress warning */ + v_uint16x16() {} + + ushort get0() const { + ushort des[1] = {0}; + __lasx_xvstelm_h(val, des, 0, 0); + return des[0]; + } +}; + +struct v_int16x16 +{ + typedef short lane_type; + enum { nlanes = 16 }; + __m256i val; + + explicit v_int16x16(__m256i v) : val(v) {} + v_int16x16(short v0, short v1, short v2, short v3, + short v4, short v5, short v6, short v7, + short v8, short v9, short v10, short v11, + short v12, short v13, short v14, short v15) + { + val = _v256_setr_h(v0, v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15); + } + /* coverity[uninit_ctor]: suppress warning */ + v_int16x16() {} + + short get0() const { + short des[1] = {0}; + __lasx_xvstelm_h(val, des, 0, 0); + return des[0]; + } +}; + +struct v_uint32x8 +{ + typedef unsigned lane_type; + enum { nlanes = 8 }; + __m256i val; + + explicit v_uint32x8(__m256i v) : val(v) {} + v_uint32x8(unsigned v0, unsigned v1, unsigned v2, unsigned v3, + unsigned v4, unsigned v5, unsigned v6, unsigned v7) + { + val = _v256_setr_w((unsigned)v0, (unsigned)v1, (unsigned)v2, + (unsigned)v3, (unsigned)v4, (unsigned)v5, (unsigned)v6, (unsigned)v7); + } + /* coverity[uninit_ctor]: suppress warning */ + v_uint32x8() {} + + unsigned get0() const { return __lasx_xvpickve2gr_wu(val, 0); } +}; + +struct v_int32x8 +{ + typedef int lane_type; + enum { nlanes = 8 }; + __m256i val; + + explicit v_int32x8(__m256i v) : val(v) {} + v_int32x8(int v0, int v1, int v2, int v3, + int v4, int v5, int v6, int v7) + { + val = _v256_setr_w(v0, v1, v2, v3, v4, v5, v6, v7); + } + /* coverity[uninit_ctor]: suppress warning */ + v_int32x8() {} + + int get0() const { return __lasx_xvpickve2gr_w(val, 0); } +}; + +struct v_float32x8 +{ + typedef float lane_type; + enum { nlanes = 8 }; + __m256 val; + + explicit v_float32x8(__m256 v) : val(v) {} + explicit v_float32x8(__m256i v) { val = *((__m256*)&v); } + v_float32x8(float v0, float v1, float v2, float v3, + float v4, float v5, float v6, float v7) + { + val = _v256_setr_ps(v0, v1, v2, v3, v4, v5, v6, v7); + } + /* coverity[uninit_ctor]: suppress warning */ + v_float32x8() {} + + float get0() const { + float des[1] = {0}; + __lasx_xvstelm_w(*((__m256i*)&val), des, 0, 0); + return des[0]; + } + + int get0toint() const { + int des[1] = {0}; + __lasx_xvstelm_w(*((__m256i*)&val), des, 0, 0); + return des[0]; + } +}; + +struct v_uint64x4 +{ + typedef uint64 lane_type; + enum { nlanes = 4 }; + __m256i val; + + explicit v_uint64x4(__m256i v) : val(v) {} + v_uint64x4(uint64 v0, uint64 v1, uint64 v2, uint64 v3) + { val = _v256_setr_d((int64)v0, (int64)v1, (int64)v2, (int64)v3); } + /* coverity[uninit_ctor]: suppress warning */ + v_uint64x4() {} + + uint64 get0() const + { + return __lasx_xvpickve2gr_du(val, 0); + } +}; + +struct v_int64x4 +{ + typedef int64 lane_type; + enum { nlanes = 4 }; + __m256i val; + + explicit v_int64x4(__m256i v) : val(v) {} + v_int64x4(int64 v0, int64 v1, int64 v2, int64 v3) + { val = _v256_setr_d(v0, v1, v2, v3); } + /* coverity[uninit_ctor]: suppress warning */ + v_int64x4() {} + + int64 get0() const + { + return __lasx_xvpickve2gr_d(val, 0); + } +}; + +struct v_float64x4 +{ + typedef double lane_type; + enum { nlanes = 4 }; + __m256d val; + + explicit v_float64x4(__m256d v) : val(v) {} + explicit v_float64x4(__m256i v) { val = *((__m256d*)&v); } + v_float64x4(double v0, double v1, double v2, double v3) + { val = _v256_setr_pd(v0, v1, v2, v3); } + /* coverity[uninit_ctor]: suppress warning */ + v_float64x4() {} + + double get0() const { + double des[1] = {0}; + __lasx_xvstelm_d(*((__m256i*)&val), des, 0, 0); + return des[0]; + } + + int64 get0toint64() const { + int64 des[1] = {0}; + __lasx_xvstelm_d(*((__m256i*)&val), des, 0, 0); + return des[0]; + } +}; + +//////////////// Load and store operations /////////////// + +#define OPENCV_HAL_IMPL_LASX_LOADSTORE(_Tpvec, _Tp) \ + inline _Tpvec v256_load(const _Tp* ptr) \ + { return _Tpvec(__lasx_xvld(ptr, 0)); } \ + inline _Tpvec v256_load_aligned(const _Tp* ptr) \ + { return _Tpvec(__lasx_xvld(ptr, 0)); } \ + inline _Tpvec v256_load_low(const _Tp* ptr) \ + { \ + __m128i v128 = __lsx_vld(ptr, 0); \ + return _Tpvec(*((__m256i*)&v128)); \ + } \ + inline _Tpvec v256_load_halves(const _Tp* ptr0, const _Tp* ptr1) \ + { \ + __m128i vlo = __lsx_vld(ptr0, 0); \ + __m128i vhi = __lsx_vld(ptr1, 0); \ + return _Tpvec(_v256_combine(vlo, vhi)); \ + } \ + inline void v_store(_Tp* ptr, const _Tpvec& a) \ + { __lasx_xvst(a.val, ptr, 0); } \ + inline void v_store_aligned(_Tp* ptr, const _Tpvec& a) \ + { __lasx_xvst(a.val, ptr, 0); } \ + inline void v_store_aligned_nocache(_Tp* ptr, const _Tpvec& a) \ + { __lasx_xvst(a.val, ptr, 0); } \ + inline void v_store(_Tp* ptr, const _Tpvec& a, hal::StoreMode mode) \ + { \ + if( mode == hal::STORE_UNALIGNED ) \ + __lasx_xvst(a.val, ptr, 0); \ + else if( mode == hal::STORE_ALIGNED_NOCACHE ) \ + __lasx_xvst(a.val, ptr, 0); \ + else \ + __lasx_xvst(a.val, ptr, 0); \ + } \ + inline void v_store_low(_Tp* ptr, const _Tpvec& a) \ + { __lsx_vst(_v256_extract_low(a.val), ptr, 0); } \ + inline void v_store_high(_Tp* ptr, const _Tpvec& a) \ + { __lsx_vst(_v256_extract_high(a.val), ptr, 0); } + +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_uint8x32, uchar) +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_int8x32, schar) +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_uint16x16, ushort) +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_int16x16, short) +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_uint32x8, unsigned) +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_int32x8, int) +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_uint64x4, uint64) +OPENCV_HAL_IMPL_LASX_LOADSTORE(v_int64x4, int64) + + +#define OPENCV_HAL_IMPL_LASX_LOADSTORE_FLT(_Tpvec, _Tp, halfreg) \ + inline _Tpvec v256_load(const _Tp* ptr) \ + { return _Tpvec(__lasx_xvld(ptr, 0)); } \ + inline _Tpvec v256_load_aligned(const _Tp* ptr) \ + { return _Tpvec(__lasx_xvld(ptr, 0)); } \ + inline _Tpvec v256_load_low(const _Tp* ptr) \ + { \ + __m128i v128 = __lsx_vld(ptr, 0); \ + return _Tpvec(*((__m256i*)&v128)); \ + } \ + inline _Tpvec v256_load_halves(const _Tp* ptr0, const _Tp* ptr1) \ + { \ + halfreg vlo = __lsx_vld(ptr0, 0); \ + halfreg vhi = __lsx_vld(ptr1, 0); \ + return _Tpvec(_v256_combine(vlo, vhi)); \ + } \ + inline void v_store(_Tp* ptr, const _Tpvec& a) \ + { __lasx_xvst(a.val, ptr, 0); } \ + inline void v_store_aligned(_Tp* ptr, const _Tpvec& a) \ + { __lasx_xvst(a.val, ptr, 0); } \ + inline void v_store_aligned_nocache(_Tp* ptr, const _Tpvec& a) \ + { __lasx_xvst(a.val, ptr, 0); } \ + inline void v_store(_Tp* ptr, const _Tpvec& a, hal::StoreMode mode) \ + { \ + if( mode == hal::STORE_UNALIGNED ) \ + __lasx_xvst(a.val, ptr, 0); \ + else if( mode == hal::STORE_ALIGNED_NOCACHE ) \ + __lasx_xvst(a.val, ptr, 0); \ + else \ + __lasx_xvst(a.val, ptr, 0); \ + } \ + inline void v_store_low(_Tp* ptr, const _Tpvec& a) \ + { __lsx_vst(_v256_extract_low(a.val), ptr, 0); } \ + inline void v_store_high(_Tp* ptr, const _Tpvec& a) \ + { __lsx_vst(_v256_extract_high(a.val), ptr, 0); } + +OPENCV_HAL_IMPL_LASX_LOADSTORE_FLT(v_float32x8, float, __m128i) +OPENCV_HAL_IMPL_LASX_LOADSTORE_FLT(v_float64x4, double, __m128i) + + +inline __m256i _lasx_256_castps_si256(const __m256& v) +{ return __m256i(v); } + +inline __m256i _lasx_256_castpd_si256(const __m256d& v) +{ return __m256i(v); } + +#define OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, _Tpvecf, suffix, cast) \ + inline _Tpvec v_reinterpret_as_##suffix(const _Tpvecf& a) \ + { return _Tpvec(cast(a.val)); } + +#define OPENCV_HAL_IMPL_LASX_INIT(_Tpvec, _Tp, suffix, ssuffix, ctype_s) \ + inline _Tpvec v256_setzero_##suffix() \ + { return _Tpvec(__lasx_xvreplgr2vr_d(0)); } \ + inline _Tpvec v256_setall_##suffix(_Tp v) \ + { return _Tpvec(__lasx_xvreplgr2vr_##ssuffix((ctype_s)v)); } \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint8x32, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int8x32, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint16x16, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int16x16, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint32x8, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int32x8, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint64x4, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int64x4, suffix, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_float32x8, suffix, _lasx_256_castps_si256) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_float64x4, suffix, _lasx_256_castpd_si256) + +OPENCV_HAL_IMPL_LASX_INIT(v_uint8x32, uchar, u8, b, int) +OPENCV_HAL_IMPL_LASX_INIT(v_int8x32, schar, s8, b, int) +OPENCV_HAL_IMPL_LASX_INIT(v_uint16x16, ushort, u16, h, int) +OPENCV_HAL_IMPL_LASX_INIT(v_int16x16, short, s16, h, int) +OPENCV_HAL_IMPL_LASX_INIT(v_uint32x8, unsigned, u32, w, int) +OPENCV_HAL_IMPL_LASX_INIT(v_int32x8, int, s32, w, int) +OPENCV_HAL_IMPL_LASX_INIT(v_uint64x4, uint64, u64, d, long int) +OPENCV_HAL_IMPL_LASX_INIT(v_int64x4, int64, s64, d, long int) + + +inline __m256 _lasx_256_castsi256_ps(const __m256i &v) +{ return __m256(v); } + +inline __m256d _lasx_256_castsi256_pd(const __m256i &v) +{ return __m256d(v); } + +#define OPENCV_HAL_IMPL_LASX_INIT_FLT(_Tpvec, _Tp, suffix, zsuffix, cast) \ + inline _Tpvec v256_setzero_##suffix() \ + { return _Tpvec(__lasx_xvreplgr2vr_d(0)); } \ + inline _Tpvec v256_setall_##suffix(_Tp v) \ + { return _Tpvec(_v256_setall_##zsuffix(v)); } \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint8x32, suffix, cast) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int8x32, suffix, cast) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint16x16, suffix, cast) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int16x16, suffix, cast) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint32x8, suffix, cast) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int32x8, suffix, cast) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_uint64x4, suffix, cast) \ + OPENCV_HAL_IMPL_LASX_CAST(_Tpvec, v_int64x4, suffix, cast) + +OPENCV_HAL_IMPL_LASX_INIT_FLT(v_float32x8, float, f32, ps, _lasx_256_castsi256_ps) +OPENCV_HAL_IMPL_LASX_INIT_FLT(v_float64x4, double, f64, pd, _lasx_256_castsi256_pd) + +inline v_float32x8 v_reinterpret_as_f32(const v_float32x8& a) +{ return a; } +inline v_float32x8 v_reinterpret_as_f32(const v_float64x4& a) +{ return v_float32x8(_lasx_256_castps_si256(__m256(a.val))); } + +inline v_float64x4 v_reinterpret_as_f64(const v_float64x4& a) +{ return a; } +inline v_float64x4 v_reinterpret_as_f64(const v_float32x8& a) +{ return v_float64x4(_lasx_256_castpd_si256(__m256d(a.val))); } + + +//////////////// Variant Value reordering /////////////// + +// unpacks +#define OPENCV_HAL_IMPL_LASX_UNPACK(_Tpvec, suffix) \ + inline _Tpvec v256_unpacklo(const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(__lasx_xvilvl_##suffix(__m256i(b.val), __m256i(a.val))); } \ + inline _Tpvec v256_unpackhi(const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(__lasx_xvilvh_##suffix(__m256i(b.val), __m256i(a.val))); } + +OPENCV_HAL_IMPL_LASX_UNPACK(v_uint8x32, b) +OPENCV_HAL_IMPL_LASX_UNPACK(v_int8x32, b) +OPENCV_HAL_IMPL_LASX_UNPACK(v_uint16x16, h) +OPENCV_HAL_IMPL_LASX_UNPACK(v_int16x16, h) +OPENCV_HAL_IMPL_LASX_UNPACK(v_uint32x8, w) +OPENCV_HAL_IMPL_LASX_UNPACK(v_int32x8, w) +OPENCV_HAL_IMPL_LASX_UNPACK(v_uint64x4, d) +OPENCV_HAL_IMPL_LASX_UNPACK(v_int64x4, d) +OPENCV_HAL_IMPL_LASX_UNPACK(v_float32x8, w) +OPENCV_HAL_IMPL_LASX_UNPACK(v_float64x4, d) + + +// shuffle +// todo: emulate 64bit +#define OPENCV_HAL_IMPL_LASX_SHUFFLE(_Tpvec, intrin) \ + template \ + inline _Tpvec v256_shuffle(const _Tpvec& a) \ + { return _Tpvec(__lasx_xvshuf4i_##intrin(a.val, m)); } + +OPENCV_HAL_IMPL_LASX_SHUFFLE(v_uint32x8, w) +OPENCV_HAL_IMPL_LASX_SHUFFLE(v_int32x8, w) + +template +inline v_float32x8 v256_shuffle(const v_float32x8 &a) +{ return v_float32x8(__lasx_xvshuf4i_w(*((__m256i*)&a.val), m)); } + +template +inline v_float64x4 v256_shuffle(const v_float64x4 &a) +{ + int imm8 = m & 0b0001; //0 or 1 + if (m & 0x0b0010) imm8 |= 0b0100; + //else imm8 |= 0b0000; + if (m & 0x0b0100) imm8 |= 0b110000; //2 or 3 + else imm8 |= 0b100000; + if (m & 0x0b1000) imm8 |= 0b11000000; + else imm8 |= 0b10000000; + + return v_float64x4(__lasx_xvpermi_d(*((__m256i*)&a.val), imm8)); +} +template +inline void v256_zip(const _Tpvec& a, const _Tpvec& b, _Tpvec& ab0, _Tpvec& ab1) +{ + ab0 = v256_unpacklo(a, b); + ab1 = v256_unpackhi(a, b); +} + +template +inline _Tpvec v256_combine_diagonal(const _Tpvec& a, const _Tpvec& b) +{ return _Tpvec(__lasx_xvpermi_q(a.val, b.val, 0x12)); } + +inline v_float32x8 v256_combine_diagonal(const v_float32x8& a, const v_float32x8& b) +{ return v_float32x8(__lasx_xvpermi_q(a.val, b.val, 0x12)); } + +inline v_float64x4 v256_combine_diagonal(const v_float64x4& a, const v_float64x4& b) +{ return v_float64x4(__lasx_xvpermi_q(a.val, b.val, 0x12)); } + +template +inline _Tpvec v256_alignr_128(const _Tpvec& a, const _Tpvec& b) +{ return v256_permute2x128<0x03>(a, b); } + +inline __m256i _v256_alignr_b(const __m256i &a, const __m256i &b, const int imm) +{ + if (imm == 8) { + return __lasx_xvshuf4i_d(b, a, 0x9); // b.d1 a.d0 b.d3 a.d2 + } else { + __m256i byteIndex = _v256_setr_b(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + return __lasx_xvshuf_b(a, b, __lasx_xvadd_b(__lasx_xvreplgr2vr_b(imm), byteIndex)); + } +} + +template +inline _Tpvec v256_alignr_64(const _Tpvec& a, const _Tpvec& b) +{ return _Tpvec(_v256_alignr_b(a.val, b.val, 8)); } +inline v_float64x4 v256_alignr_64(const v_float64x4& a, const v_float64x4& b) +{ return v_float64x4(__lasx_xvshuf4i_d(b.val, a.val, 0x9)); } // b.d1 a.d0 b.d3 a.d2 +// todo: emulate float32 + +template +inline _Tpvec v256_swap_halves(const _Tpvec& a) +{ return v256_permute2x128<1>(a, a); } + +template +inline _Tpvec v256_reverse_64(const _Tpvec& a) +{ return v256_permute4x64<0x1b>(a); } + + +// ZIP +#define OPENCV_HAL_IMPL_LASX_ZIP(_Tpvec) \ + inline _Tpvec v_combine_low(const _Tpvec& a, const _Tpvec& b) \ + { return v256_permute2x128<0x02>(a, b); } \ + inline _Tpvec v_combine_high(const _Tpvec& a, const _Tpvec& b) \ + { return v256_permute2x128<0x13>(a, b); } \ + inline void v_recombine(const _Tpvec& a, const _Tpvec& b, \ + _Tpvec& c, _Tpvec& d) \ + { \ + _Tpvec a1b0 = v256_alignr_128(a, b); \ + c = v256_combine_diagonal(a, a1b0); \ + d = v256_combine_diagonal(a1b0, b); \ + } \ + inline void v_zip(const _Tpvec& a, const _Tpvec& b, \ + _Tpvec& ab0, _Tpvec& ab1) \ + { \ + _Tpvec ab0ab2, ab1ab3; \ + v256_zip(a, b, ab0ab2, ab1ab3); \ + v_recombine(ab0ab2, ab1ab3, ab0, ab1); \ + } + +OPENCV_HAL_IMPL_LASX_ZIP(v_uint8x32) +OPENCV_HAL_IMPL_LASX_ZIP(v_int8x32) +OPENCV_HAL_IMPL_LASX_ZIP(v_uint16x16) +OPENCV_HAL_IMPL_LASX_ZIP(v_int16x16) +OPENCV_HAL_IMPL_LASX_ZIP(v_uint32x8) +OPENCV_HAL_IMPL_LASX_ZIP(v_int32x8) +OPENCV_HAL_IMPL_LASX_ZIP(v_uint64x4) +OPENCV_HAL_IMPL_LASX_ZIP(v_int64x4) +OPENCV_HAL_IMPL_LASX_ZIP(v_float32x8) +OPENCV_HAL_IMPL_LASX_ZIP(v_float64x4) + +////////// Arithmetic, bitwise and comparison operations ///////// + +/** Arithmetics **/ +#define OPENCV_HAL_IMPL_LASX_BIN_OP(bin_op, _Tpvec, intrin) \ + inline _Tpvec operator bin_op (const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(intrin(a.val, b.val)); } \ + inline _Tpvec& operator bin_op##= (_Tpvec& a, const _Tpvec& b) \ + { a.val = intrin(a.val, b.val); return a; } + +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_uint8x32, __lasx_xvsadd_bu) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_uint8x32, __lasx_xvssub_bu) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_int8x32, __lasx_xvsadd_b) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_int8x32, __lasx_xvssub_b) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_uint16x16, __lasx_xvsadd_hu) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_uint16x16, __lasx_xvssub_hu) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_int16x16, __lasx_xvsadd_h) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_int16x16, __lasx_xvssub_h) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_uint32x8, __lasx_xvadd_w) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_uint32x8, __lasx_xvsub_w) +OPENCV_HAL_IMPL_LASX_BIN_OP(*, v_uint32x8, __lasx_xvmul_w) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_int32x8, __lasx_xvadd_w) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_int32x8, __lasx_xvsub_w) +OPENCV_HAL_IMPL_LASX_BIN_OP(*, v_int32x8, __lasx_xvmul_w) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_uint64x4, __lasx_xvadd_d) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_uint64x4, __lasx_xvsub_d) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_int64x4, __lasx_xvadd_d) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_int64x4, __lasx_xvsub_d) + +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_float32x8, __lasx_xvfadd_s) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_float32x8, __lasx_xvfsub_s) +OPENCV_HAL_IMPL_LASX_BIN_OP(*, v_float32x8, __lasx_xvfmul_s) +OPENCV_HAL_IMPL_LASX_BIN_OP(/, v_float32x8, __lasx_xvfdiv_s) +OPENCV_HAL_IMPL_LASX_BIN_OP(+, v_float64x4, __lasx_xvfadd_d) +OPENCV_HAL_IMPL_LASX_BIN_OP(-, v_float64x4, __lasx_xvfsub_d) +OPENCV_HAL_IMPL_LASX_BIN_OP(*, v_float64x4, __lasx_xvfmul_d) +OPENCV_HAL_IMPL_LASX_BIN_OP(/, v_float64x4, __lasx_xvfdiv_d) + +// saturating multiply 8-bit, 16-bit +inline v_uint8x32 operator * (const v_uint8x32& a, const v_uint8x32& b) +{ + v_uint16x16 c, d; + v_mul_expand(a, b, c, d); + return v_pack(c, d); +} +inline v_int8x32 operator * (const v_int8x32& a, const v_int8x32& b) +{ + v_int16x16 c, d; + v_mul_expand(a, b, c, d); + return v_pack(c, d); +} +inline v_uint16x16 operator * (const v_uint16x16& a, const v_uint16x16& b) +{ + __m256i pl = __lasx_xvmul_h(a.val, b.val); + __m256i ph = __lasx_xvmuh_hu(a.val, b.val); + __m256i p0 = __lasx_xvilvl_h(ph, pl); + __m256i p1 = __lasx_xvilvh_h(ph, pl); + return v_uint16x16(_v256_packs_epu32(p0, p1)); +} +inline v_int16x16 operator * (const v_int16x16& a, const v_int16x16& b) +{ + __m256i pl = __lasx_xvmul_h(a.val, b.val); + __m256i ph = __lasx_xvmuh_h(a.val, b.val); + __m256i p0 = __lasx_xvilvl_h(ph, pl); + __m256i p1 = __lasx_xvilvh_h(ph, pl); + return v_int16x16(_lasx_packs_w(p0, p1)); +} +inline v_uint8x32& operator *= (v_uint8x32& a, const v_uint8x32& b) +{ a = a * b; return a; } +inline v_int8x32& operator *= (v_int8x32& a, const v_int8x32& b) +{ a = a * b; return a; } +inline v_uint16x16& operator *= (v_uint16x16& a, const v_uint16x16& b) +{ a = a * b; return a; } +inline v_int16x16& operator *= (v_int16x16& a, const v_int16x16& b) +{ a = a * b; return a; } + +/** Non-saturating arithmetics **/ + +#define OPENCV_HAL_IMPL_LASX_BIN_FUNC(func, _Tpvec, intrin) \ + inline _Tpvec func(const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(intrin(a.val, b.val)); } + +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_add_wrap, v_uint8x32, __lasx_xvadd_b) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_add_wrap, v_int8x32, __lasx_xvadd_b) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_add_wrap, v_uint16x16, __lasx_xvadd_h) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_add_wrap, v_int16x16, __lasx_xvadd_h) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_sub_wrap, v_uint8x32, __lasx_xvsub_b) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_sub_wrap, v_int8x32, __lasx_xvsub_b) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_sub_wrap, v_uint16x16, __lasx_xvsub_h) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_sub_wrap, v_int16x16, __lasx_xvsub_h) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_mul_wrap, v_uint16x16, __lasx_xvmul_h) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_mul_wrap, v_int16x16, __lasx_xvmul_h) + +inline v_uint8x32 v_mul_wrap(const v_uint8x32& a, const v_uint8x32& b) +{ + __m256i ad = __lasx_xvsrai_h(a.val, 8); + __m256i bd = __lasx_xvsrai_h(b.val, 8); + __m256i p0 = __lasx_xvmul_h(a.val, b.val); + __m256i p1 = __lasx_xvslli_h(__lasx_xvmul_h(ad, bd), 8); + + const __m256i b01 = __lasx_xvreplgr2vr_w(0xFF00FF00); + return v_uint8x32(__lasx_xvbitsel_v(p0, p1, b01)); +} +inline v_int8x32 v_mul_wrap(const v_int8x32& a, const v_int8x32& b) +{ + return v_reinterpret_as_s8(v_mul_wrap(v_reinterpret_as_u8(a), v_reinterpret_as_u8(b))); +} + +// Multiply and expand +inline void v_mul_expand(const v_uint8x32& a, const v_uint8x32& b, + v_uint16x16& c, v_uint16x16& d) +{ + v_uint16x16 a0, a1, b0, b1; + v_expand(a, a0, a1); + v_expand(b, b0, b1); + c = v_mul_wrap(a0, b0); + d = v_mul_wrap(a1, b1); +} + +inline void v_mul_expand(const v_int8x32& a, const v_int8x32& b, + v_int16x16& c, v_int16x16& d) +{ + v_int16x16 a0, a1, b0, b1; + v_expand(a, a0, a1); + v_expand(b, b0, b1); + c = v_mul_wrap(a0, b0); + d = v_mul_wrap(a1, b1); +} + +inline void v_mul_expand(const v_int16x16& a, const v_int16x16& b, + v_int32x8& c, v_int32x8& d) +{ + v_int16x16 vhi = v_int16x16(__lasx_xvmuh_h(a.val, b.val)); + + v_int16x16 v0, v1; + v_zip(v_mul_wrap(a, b), vhi, v0, v1); + + c = v_reinterpret_as_s32(v0); + d = v_reinterpret_as_s32(v1); +} + +inline void v_mul_expand(const v_uint16x16& a, const v_uint16x16& b, + v_uint32x8& c, v_uint32x8& d) +{ + v_uint16x16 vhi = v_uint16x16(__lasx_xvmuh_hu(a.val, b.val)); + + v_uint16x16 v0, v1; + v_zip(v_mul_wrap(a, b), vhi, v0, v1); + + c = v_reinterpret_as_u32(v0); + d = v_reinterpret_as_u32(v1); +} + +inline void v_mul_expand(const v_uint32x8& a, const v_uint32x8& b, + v_uint64x4& c, v_uint64x4& d) +{ + __m256i v0 = __lasx_xvmulwev_d_wu(a.val, b.val); + __m256i v1 = __lasx_xvmulwod_d_wu(a.val, b.val); + v_zip(v_uint64x4(v0), v_uint64x4(v1), c, d); +} + +inline v_int16x16 v_mul_hi(const v_int16x16& a, const v_int16x16& b) { return v_int16x16(__lasx_xvmuh_h(a.val, b.val)); } +inline v_uint16x16 v_mul_hi(const v_uint16x16& a, const v_uint16x16& b) { return v_uint16x16(__lasx_xvmuh_hu(a.val, b.val)); } + +/** Bitwise shifts **/ +#define OPENCV_HAL_IMPL_LASX_SHIFT_OP(_Tpuvec, _Tpsvec, suffix, srai) \ + inline _Tpuvec operator << (const _Tpuvec& a, int imm) \ + { return _Tpuvec(__lasx_xvsll_##suffix(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } \ + inline _Tpsvec operator << (const _Tpsvec& a, int imm) \ + { return _Tpsvec(__lasx_xvsll_##suffix(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } \ + inline _Tpuvec operator >> (const _Tpuvec& a, int imm) \ + { return _Tpuvec(__lasx_xvsrl_##suffix(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } \ + inline _Tpsvec operator >> (const _Tpsvec& a, int imm) \ + { return _Tpsvec(srai(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } \ + template \ + inline _Tpuvec v_shl(const _Tpuvec& a) \ + { return _Tpuvec(__lasx_xvsll_##suffix(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } \ + template \ + inline _Tpsvec v_shl(const _Tpsvec& a) \ + { return _Tpsvec(__lasx_xvsll_##suffix(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } \ + template \ + inline _Tpuvec v_shr(const _Tpuvec& a) \ + { return _Tpuvec(__lasx_xvsrl_##suffix(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } \ + template \ + inline _Tpsvec v_shr(const _Tpsvec& a) \ + { return _Tpsvec(srai(a.val, __lasx_xvreplgr2vr_##suffix(imm))); } + +OPENCV_HAL_IMPL_LASX_SHIFT_OP(v_uint16x16, v_int16x16, h, __lasx_xvsra_h) +OPENCV_HAL_IMPL_LASX_SHIFT_OP(v_uint32x8, v_int32x8, w, __lasx_xvsra_w) + +inline __m256i _v256_srai_dx(const __m256i a, const __m256i shift) +{ + __m256i d = __lasx_xvreplgr2vr_d((int64)1 << 63); + __m256i r = __lasx_xvsrl_d(__lasx_xvadd_d(a, d), shift); + return __lasx_xvsub_d(r, __lasx_xvsrl_d(d, shift)); +} +OPENCV_HAL_IMPL_LASX_SHIFT_OP(v_uint64x4, v_int64x4, d, _v256_srai_dx) + + +/** Bitwise logic **/ +#define OPENCV_HAL_IMPL_LASX_LOGIC_OP(_Tpvec, suffix, not_const) \ + OPENCV_HAL_IMPL_LASX_BIN_OP(&, _Tpvec, __lasx_xvand_##suffix) \ + OPENCV_HAL_IMPL_LASX_BIN_OP(|, _Tpvec, __lasx_xvor_##suffix) \ + OPENCV_HAL_IMPL_LASX_BIN_OP(^, _Tpvec, __lasx_xvxor_##suffix) \ + inline _Tpvec operator ~ (const _Tpvec& a) \ + { return _Tpvec(__lasx_xvxor_##suffix(a.val, not_const)); } + +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_uint8x32, v, __lasx_xvreplgr2vr_w(-1)) +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_int8x32, v, __lasx_xvreplgr2vr_w(-1)) +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_uint16x16, v, __lasx_xvreplgr2vr_w(-1)) +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_int16x16, v, __lasx_xvreplgr2vr_w(-1)) +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_uint32x8, v, __lasx_xvreplgr2vr_w(-1)) +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_int32x8, v, __lasx_xvreplgr2vr_w(-1)) +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_uint64x4, v, __lasx_xvreplgr2vr_d(-1)) +OPENCV_HAL_IMPL_LASX_LOGIC_OP(v_int64x4, v, __lasx_xvreplgr2vr_d(-1)) + +#define OPENCV_HAL_IMPL_LASX_FLOAT_BIN_OP(bin_op, _Tpvec, intrin, cast) \ + inline _Tpvec operator bin_op (const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(intrin(*((__m256i*)(&a.val)), *((__m256i*)(&b.val)))); } \ + inline _Tpvec& operator bin_op##= (_Tpvec& a, const _Tpvec& b) \ + { __m256i c = intrin(*((__m256i*)(&a.val)), *((__m256i*)(&b.val))); a.val = cast(c); return a; } + +#define OPENCV_HAL_IMPL_LASX_FLOAT_LOGIC_OP(_Tpvec, suffix, not_const, cast) \ + OPENCV_HAL_IMPL_LASX_FLOAT_BIN_OP(&, _Tpvec, __lasx_xvand_##suffix, cast) \ + OPENCV_HAL_IMPL_LASX_FLOAT_BIN_OP(|, _Tpvec, __lasx_xvor_##suffix, cast) \ + OPENCV_HAL_IMPL_LASX_FLOAT_BIN_OP(^, _Tpvec, __lasx_xvxor_##suffix, cast) \ + inline _Tpvec operator ~ (const _Tpvec& a) \ + { return _Tpvec(__lasx_xvxor_##suffix(*((__m256i*)(&a.val)), not_const)); } + +OPENCV_HAL_IMPL_LASX_FLOAT_LOGIC_OP(v_float32x8, v, __lasx_xvreplgr2vr_w(-1), _lasx_256_castsi256_ps) +OPENCV_HAL_IMPL_LASX_FLOAT_LOGIC_OP(v_float64x4, v, __lasx_xvreplgr2vr_d(-1), _lasx_256_castsi256_pd) + +/** Select **/ +#define OPENCV_HAL_IMPL_LASX_SELECT(_Tpvec) \ + inline _Tpvec v_select(const _Tpvec& mask, const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(__lasx_xvbitsel_v(b.val, a.val, mask.val)); } + +OPENCV_HAL_IMPL_LASX_SELECT(v_uint8x32) +OPENCV_HAL_IMPL_LASX_SELECT(v_int8x32) +OPENCV_HAL_IMPL_LASX_SELECT(v_uint16x16) +OPENCV_HAL_IMPL_LASX_SELECT(v_int16x16) +OPENCV_HAL_IMPL_LASX_SELECT(v_uint32x8) +OPENCV_HAL_IMPL_LASX_SELECT(v_int32x8) + +inline v_float32x8 v_select(const v_float32x8 &mask, const v_float32x8 &a, const v_float32x8 &b) +{ return v_float32x8(__lasx_xvbitsel_v(*((__m256i*)&b.val), *((__m256i*)&a.val), *((__m256i*)&mask.val))); } + +inline v_float64x4 v_select(const v_float64x4 &mask, const v_float64x4 &a, const v_float64x4 &b) +{ return v_float64x4(__lasx_xvbitsel_v(*((__m256i*)&b.val), *((__m256i*)&a.val), *((__m256i*)&mask.val))); } + +/** Comparison **/ +#define OPENCV_HAL_IMPL_LASX_CMP_OP_OV(_Tpvec) \ + inline _Tpvec operator != (const _Tpvec& a, const _Tpvec& b) \ + { return ~(a == b); } \ + inline _Tpvec operator < (const _Tpvec& a, const _Tpvec& b) \ + { return b > a; } \ + inline _Tpvec operator >= (const _Tpvec& a, const _Tpvec& b) \ + { return ~(a < b); } \ + inline _Tpvec operator <= (const _Tpvec& a, const _Tpvec& b) \ + { return b >= a; } + +#define OPENCV_HAL_IMPL_LASX_CMP_OP_INT(_Tpuvec, _Tpsvec, suffix, usuffix) \ + inline _Tpuvec operator == (const _Tpuvec& a, const _Tpuvec& b) \ + { return _Tpuvec(__lasx_xvseq_##suffix(a.val, b.val)); } \ + inline _Tpuvec operator > (const _Tpuvec& a, const _Tpuvec& b) \ + { \ + return _Tpuvec(__lasx_xvslt_##usuffix(b.val, a.val)); \ + } \ + inline _Tpsvec operator == (const _Tpsvec& a, const _Tpsvec& b) \ + { return _Tpsvec(__lasx_xvseq_##suffix(a.val, b.val)); } \ + inline _Tpsvec operator > (const _Tpsvec& a, const _Tpsvec& b) \ + { return _Tpsvec(__lasx_xvslt_##suffix(b.val, a.val)); } \ + OPENCV_HAL_IMPL_LASX_CMP_OP_OV(_Tpuvec) \ + OPENCV_HAL_IMPL_LASX_CMP_OP_OV(_Tpsvec) + +OPENCV_HAL_IMPL_LASX_CMP_OP_INT(v_uint8x32, v_int8x32, b, bu) +OPENCV_HAL_IMPL_LASX_CMP_OP_INT(v_uint16x16, v_int16x16, h, hu) +OPENCV_HAL_IMPL_LASX_CMP_OP_INT(v_uint32x8, v_int32x8, w, wu) + +#define OPENCV_HAL_IMPL_LASX_CMP_OP_64BIT(_Tpvec, suffix) \ + inline _Tpvec operator == (const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(__lasx_xvseq_##suffix(a.val, b.val)); } \ + inline _Tpvec operator != (const _Tpvec& a, const _Tpvec& b) \ + { return ~(a == b); } + +OPENCV_HAL_IMPL_LASX_CMP_OP_64BIT(v_uint64x4, d) +OPENCV_HAL_IMPL_LASX_CMP_OP_64BIT(v_int64x4, d) + +#define OPENCV_HAL_IMPL_LASX_CMP_FLT(bin_op, suffix, _Tpvec, ssuffix) \ + inline _Tpvec operator bin_op (const _Tpvec& a, const _Tpvec& b) \ + { return _Tpvec(__lasx_##suffix##_##ssuffix(a.val, b.val)); } + +#define OPENCV_HAL_IMPL_LASX_CMP_OP_FLT(_Tpvec, ssuffix) \ + OPENCV_HAL_IMPL_LASX_CMP_FLT(==, xvfcmp_ceq, _Tpvec, ssuffix) \ + OPENCV_HAL_IMPL_LASX_CMP_FLT(!=, xvfcmp_cne, _Tpvec, ssuffix) \ + OPENCV_HAL_IMPL_LASX_CMP_FLT(<, xvfcmp_clt, _Tpvec, ssuffix) \ + OPENCV_HAL_IMPL_LASX_CMP_FLT(<=, xvfcmp_cle, _Tpvec, ssuffix) + +OPENCV_HAL_IMPL_LASX_CMP_OP_FLT(v_float32x8, s) +OPENCV_HAL_IMPL_LASX_CMP_OP_FLT(v_float64x4, d) + +inline v_float32x8 operator > (const v_float32x8 &a, const v_float32x8 &b) +{ return v_float32x8(__lasx_xvfcmp_clt_s(b.val, a.val)); } + +inline v_float32x8 operator >= (const v_float32x8 &a, const v_float32x8 &b) +{ return v_float32x8(__lasx_xvfcmp_cle_s(b.val, a.val)); } + +inline v_float64x4 operator > (const v_float64x4 &a, const v_float64x4 &b) +{ return v_float64x4(__lasx_xvfcmp_clt_d(b.val, a.val)); } + +inline v_float64x4 operator >= (const v_float64x4 &a, const v_float64x4 &b) +{ return v_float64x4(__lasx_xvfcmp_cle_d(b.val, a.val)); } + +inline v_float32x8 v_not_nan(const v_float32x8& a) +{ return v_float32x8(__lasx_xvfcmp_cor_s(a.val, a.val)); } +inline v_float64x4 v_not_nan(const v_float64x4& a) +{ return v_float64x4(__lasx_xvfcmp_cor_d(a.val, a.val)); } + +/** min/max **/ +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_uint8x32, __lasx_xvmin_bu) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_uint8x32, __lasx_xvmax_bu) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_int8x32, __lasx_xvmin_b) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_int8x32, __lasx_xvmax_b) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_uint16x16, __lasx_xvmin_hu) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_uint16x16, __lasx_xvmax_hu) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_int16x16, __lasx_xvmin_h) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_int16x16, __lasx_xvmax_h) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_uint32x8, __lasx_xvmin_wu) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_uint32x8, __lasx_xvmax_wu) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_int32x8, __lasx_xvmin_w) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_int32x8, __lasx_xvmax_w) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_float32x8, __lasx_xvfmin_s) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_float32x8, __lasx_xvfmax_s) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_min, v_float64x4, __lasx_xvfmin_d) +OPENCV_HAL_IMPL_LASX_BIN_FUNC(v_max, v_float64x4, __lasx_xvfmax_d) + +/** Rotate **/ +template +inline v_uint8x32 v_rotate_left(const v_uint8x32& a, const v_uint8x32& b) +{ + enum {IMM_R = (16 - imm) & 0xFF}; + enum {IMM_R2 = (32 - imm) & 0xFF}; + + if (imm == 0) return a; + if (imm == 32) return b; + if (imm > 32) return v_uint8x32(); + + __m256i swap = _v256_permute2x128<0x21>(a.val, b.val); + if (imm == 16) return v_uint8x32(swap); + if (imm < 16) return v_uint8x32(_v256_alignr_b(a.val, swap, IMM_R)); + return v_uint8x32(_v256_alignr_b(swap, b.val, IMM_R2)); // imm < 32 +} + +template +inline v_uint8x32 v_rotate_right(const v_uint8x32& a, const v_uint8x32& b) +{ + enum {IMM_L = (imm - 16) & 0xFF}; + + if (imm == 0) return a; + if (imm == 32) return b; + if (imm > 32) return v_uint8x32(); + + __m256i swap = _v256_permute2x128<0x03>(a.val, b.val); + if (imm == 16) return v_uint8x32(swap); + if (imm < 16) return v_uint8x32(_v256_alignr_b(swap, a.val, imm)); + return v_uint8x32(_v256_alignr_b(b.val, swap, IMM_L)); +} + +template +inline v_uint8x32 v_rotate_left(const v_uint8x32& a) +{ + enum {IMM_L = (imm - 16) & 0xFF}; + enum {IMM_R = (16 - imm) & 0xFF}; + + if (imm == 0) return a; + if (imm > 32) return v_uint8x32(); + + // ESAC control[3] ? [127:0] = 0 + __m256i vzero = __lasx_xvreplgr2vr_w(0); + __m256i swapz = __lasx_xvpermi_q(a.val, vzero, 0x20);; + if (imm == 16) return v_uint8x32(swapz); + if (imm < 16) return v_uint8x32(_v256_alignr_b(a.val, swapz, IMM_R)); + return v_uint8x32(__lasx_xvbsll_v(swapz, IMM_L)); +} + +template +inline v_uint8x32 v_rotate_right(const v_uint8x32& a) +{ + enum {IMM_L = (imm - 16) & 0xFF}; + + if (imm == 0) return a; + if (imm > 32) return v_uint8x32(); + + // ESAC control[3] ? [127:0] = 0 + __m256i vzero = __lasx_xvreplgr2vr_w(0); + __m256i swapz = __lasx_xvpermi_q(vzero, a.val, 0x21);; + if (imm == 16) return v_uint8x32(swapz); + if (imm < 16) return v_uint8x32(_v256_alignr_b(swapz, a.val, imm)); + return v_uint8x32(__lasx_xvbsrl_v(swapz, IMM_L)); +} + +#define OPENCV_HAL_IMPL_LASX_ROTATE_CAST(intrin, _Tpvec, cast) \ + template \ + inline _Tpvec intrin(const _Tpvec& a, const _Tpvec& b) \ + { \ + enum {IMMxW = imm * sizeof(typename _Tpvec::lane_type)}; \ + v_uint8x32 ret = intrin(v_reinterpret_as_u8(a), \ + v_reinterpret_as_u8(b)); \ + return _Tpvec(cast(ret.val)); \ + } \ + template \ + inline _Tpvec intrin(const _Tpvec& a) \ + { \ + enum {IMMxW = imm * sizeof(typename _Tpvec::lane_type)}; \ + v_uint8x32 ret = intrin(v_reinterpret_as_u8(a)); \ + return _Tpvec(cast(ret.val)); \ + } + +#define OPENCV_HAL_IMPL_LASX_ROTATE(_Tpvec) \ + OPENCV_HAL_IMPL_LASX_ROTATE_CAST(v_rotate_left, _Tpvec, OPENCV_HAL_NOP) \ + OPENCV_HAL_IMPL_LASX_ROTATE_CAST(v_rotate_right, _Tpvec, OPENCV_HAL_NOP) + +OPENCV_HAL_IMPL_LASX_ROTATE(v_int8x32) +OPENCV_HAL_IMPL_LASX_ROTATE(v_uint16x16) +OPENCV_HAL_IMPL_LASX_ROTATE(v_int16x16) +OPENCV_HAL_IMPL_LASX_ROTATE(v_uint32x8) +OPENCV_HAL_IMPL_LASX_ROTATE(v_int32x8) +OPENCV_HAL_IMPL_LASX_ROTATE(v_uint64x4) +OPENCV_HAL_IMPL_LASX_ROTATE(v_int64x4) + +OPENCV_HAL_IMPL_LASX_ROTATE_CAST(v_rotate_left, v_float32x8, _lasx_256_castsi256_ps) +OPENCV_HAL_IMPL_LASX_ROTATE_CAST(v_rotate_right, v_float32x8, _lasx_256_castsi256_ps) +OPENCV_HAL_IMPL_LASX_ROTATE_CAST(v_rotate_left, v_float64x4, _lasx_256_castsi256_pd) +OPENCV_HAL_IMPL_LASX_ROTATE_CAST(v_rotate_right, v_float64x4, _lasx_256_castsi256_pd) + +/** Reverse **/ +inline v_uint8x32 v_reverse(const v_uint8x32 &a) +{ + static const __m256i perm = _v256_setr_b( + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + __m256i vec = __lasx_xvshuf_b(a.val, a.val, perm); + return v_uint8x32(__lasx_xvpermi_q(vec, vec, 1)); +} + +inline v_int8x32 v_reverse(const v_int8x32 &a) +{ return v_reinterpret_as_s8(v_reverse(v_reinterpret_as_u8(a))); } + +inline v_uint16x16 v_reverse(const v_uint16x16 &a) +{ + static const __m256i perm = _v256_setr_b( + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1, + 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); + __m256i vec = __lasx_xvshuf_b(a.val, a.val, perm); + return v_uint16x16(__lasx_xvpermi_q(vec, vec, 1)); +} + +inline v_int16x16 v_reverse(const v_int16x16 &a) +{ return v_reinterpret_as_s16(v_reverse(v_reinterpret_as_u16(a))); } + +inline v_uint32x8 v_reverse(const v_uint32x8 &a) +{ + static const __m256i perm = _v256_setr_w(7, 6, 5, 4, 3, 2, 1, 0); + return v_uint32x8(__lasx_xvperm_w(a.val, perm)); +} + +inline v_int32x8 v_reverse(const v_int32x8 &a) +{ return v_reinterpret_as_s32(v_reverse(v_reinterpret_as_u32(a))); } + +inline v_float32x8 v_reverse(const v_float32x8 &a) +{ return v_reinterpret_as_f32(v_reverse(v_reinterpret_as_u32(a))); } + +inline v_uint64x4 v_reverse(const v_uint64x4 &a) +{ + return v_uint64x4(__lasx_xvpermi_d(a.val, 0x1b)); +} + +inline v_int64x4 v_reverse(const v_int64x4 &a) +{ return v_reinterpret_as_s64(v_reverse(v_reinterpret_as_u64(a))); } + +inline v_float64x4 v_reverse(const v_float64x4 &a) +{ return v_reinterpret_as_f64(v_reverse(v_reinterpret_as_u64(a))); } + +////////// Reduce and mask ///////// + +/** Reduce **/ +// this function is return a[0]+a[1]+...+a[31] +inline unsigned v_reduce_sum(const v_uint8x32& a) +{ + __m256i t1 = __lasx_xvhaddw_hu_bu(a.val, a.val); + __m256i t2 = __lasx_xvhaddw_wu_hu(t1, t1); + __m256i t3 = __lasx_xvhaddw_du_wu(t2, t2); + return (unsigned)(((v4u64)t3)[0]+((v4u64)t3)[1]+((v4u64)t3)[2]+((v4u64)t3)[3]); +} +inline int v_reduce_sum(const v_int8x32& a) +{ + __m256i t1 = __lasx_xvhaddw_h_b(a.val, a.val); + __m256i t2 = __lasx_xvhaddw_w_h(t1, t1); + __m256i t3 = __lasx_xvhaddw_d_w(t2, t2); + return (int)(((v4i64)t3)[0]+((v4i64)t3)[1]+((v4i64)t3)[2]+((v4i64)t3)[3]); +} + + +#define OPENCV_HAL_IMPL_LASX_REDUCE_32(_Tpvec, sctype, func, intrin) \ + inline sctype v_reduce_##func(const _Tpvec& a) \ + { \ + __m128i val = intrin(_v256_extract_low(a.val), _v256_extract_high(a.val)); \ + val = intrin(val, __lsx_vbsrl_v(val,8)); \ + val = intrin(val, __lsx_vbsrl_v(val,4)); \ + val = intrin(val, __lsx_vbsrl_v(val,2)); \ + val = intrin(val, __lsx_vbsrl_v(val,1)); \ + return (sctype)__lsx_vpickve2gr_w(val, 0); \ + } + +OPENCV_HAL_IMPL_LASX_REDUCE_32(v_uint8x32, uchar, min, __lsx_vmin_bu) +OPENCV_HAL_IMPL_LASX_REDUCE_32(v_int8x32, schar, min, __lsx_vmin_b) +OPENCV_HAL_IMPL_LASX_REDUCE_32(v_uint8x32, uchar, max, __lsx_vmax_bu) +OPENCV_HAL_IMPL_LASX_REDUCE_32(v_int8x32, schar, max, __lsx_vmax_b) + +#define OPENCV_HAL_IMPL_LASX_REDUCE_16(_Tpvec, sctype, func, intrin) \ + inline sctype v_reduce_##func(const _Tpvec& a) \ + { \ + __m128i v0 = _v256_extract_low(a.val); \ + __m128i v1 = _v256_extract_high(a.val); \ + v0 = intrin(v0, v1); \ + v0 = intrin(v0, __lsx_vbsrl_v(v0, 8)); \ + v0 = intrin(v0, __lsx_vbsrl_v(v0, 4)); \ + v0 = intrin(v0, __lsx_vbsrl_v(v0, 2)); \ + return (sctype) __lsx_vpickve2gr_w(v0, 0); \ + } + +OPENCV_HAL_IMPL_LASX_REDUCE_16(v_uint16x16, ushort, min, __lsx_vmin_hu) +OPENCV_HAL_IMPL_LASX_REDUCE_16(v_int16x16, short, min, __lsx_vmin_h) +OPENCV_HAL_IMPL_LASX_REDUCE_16(v_uint16x16, ushort, max, __lsx_vmax_hu) +OPENCV_HAL_IMPL_LASX_REDUCE_16(v_int16x16, short, max, __lsx_vmax_h) + +#define OPENCV_HAL_IMPL_LASX_REDUCE_8(_Tpvec, sctype, func, intrin) \ + inline sctype v_reduce_##func(const _Tpvec& a) \ + { \ + __m128i v0 = _v256_extract_low(a.val); \ + __m128i v1 = _v256_extract_high(a.val); \ + v0 = intrin(v0, v1); \ + v0 = intrin(v0, __lsx_vbsrl_v(v0, 8)); \ + v0 = intrin(v0, __lsx_vbsrl_v(v0, 4)); \ + return (sctype) __lsx_vpickve2gr_w(v0, 0); \ + } + +OPENCV_HAL_IMPL_LASX_REDUCE_8(v_uint32x8, unsigned, min, __lsx_vmin_wu) +OPENCV_HAL_IMPL_LASX_REDUCE_8(v_int32x8, int, min, __lsx_vmin_w) +OPENCV_HAL_IMPL_LASX_REDUCE_8(v_uint32x8, unsigned, max, __lsx_vmax_wu) +OPENCV_HAL_IMPL_LASX_REDUCE_8(v_int32x8, int, max, __lsx_vmax_w) + +#define OPENCV_HAL_IMPL_LASX_REDUCE_FLT(func, intrin) \ + inline float v_reduce_##func(const v_float32x8& a) \ + { \ + __m128 v0 = _v256_extract_low(a.val); \ + __m128 v1 = _v256_extract_high(a.val); \ + v0 = intrin(v0, v1); \ + v0 = intrin(v0, __m128(__lsx_vpermi_w(*((__m128i*)&v0), *((__m128i*)&v0), 0x0e))); \ + v0 = intrin(v0, __m128(__lsx_vpermi_w(*((__m128i*)&v0), *((__m128i*)&v0), 0x01))); \ + float *fvalue = (float*)&v0; \ + return fvalue[0]; \ + } + +OPENCV_HAL_IMPL_LASX_REDUCE_FLT(min, __lsx_vfmin_s) +OPENCV_HAL_IMPL_LASX_REDUCE_FLT(max, __lsx_vfmax_s) + +inline int v_reduce_sum(const v_int32x8& a) +{ + __m256i t1 = __lasx_xvhaddw_d_w(a.val, a.val); + return (int)(((v4i64)t1)[0]+((v4i64)t1)[1]+((v4i64)t1)[2]+((v4i64)t1)[3]); +} + +inline unsigned v_reduce_sum(const v_uint32x8& a) +{ return v_reduce_sum(v_reinterpret_as_s32(a)); } + +inline int v_reduce_sum(const v_int16x16& a) +{ return v_reduce_sum(v_expand_low(a) + v_expand_high(a)); } +inline unsigned v_reduce_sum(const v_uint16x16& a) +{ return v_reduce_sum(v_expand_low(a) + v_expand_high(a)); } + +inline float v_reduce_sum(const v_float32x8& a) +{ + float result = 0; + float *pa = (float*)&a; + for (int i = 0; i < 2; ++i) { + result += pa[i*4] + pa[i*4+1] + pa[i*4+2] + pa[i*4+3]; + } + return result; +} + +inline uint64 v_reduce_sum(const v_uint64x4& a) +{ + uint64 *pa = (uint64*)&a; + return pa[0] + pa[1] + pa[2] + pa[3]; +} +inline int64 v_reduce_sum(const v_int64x4& a) +{ + int64 *pa = (int64*)&a; + return pa[0] + pa[1] + pa[2] + pa[3]; +} +inline double v_reduce_sum(const v_float64x4& a) +{ + double *pa = (double*)&a; + return pa[0] + pa[1] + pa[2] + pa[3]; +} + +inline v_float32x8 v_reduce_sum4(const v_float32x8& a, const v_float32x8& b, + const v_float32x8& c, const v_float32x8& d) +{ + float *pa = (float*)&a; + float *pb = (float*)&b; + float *pc = (float*)&c; + float *pd = (float*)&d; + + float v0 = pa[0] + pa[1] + pa[2] + pa[3]; + float v1 = pb[0] + pb[1] + pb[2] + pb[3]; + float v2 = pc[0] + pc[1] + pc[2] + pc[3]; + float v3 = pd[0] + pd[1] + pd[2] + pd[3]; + float v4 = pa[4] + pa[5] + pa[6] + pa[7]; + float v5 = pb[4] + pb[5] + pb[6] + pb[7]; + float v6 = pc[4] + pc[5] + pc[6] + pc[7]; + float v7 = pd[4] + pd[5] + pd[6] + pd[7]; + return v_float32x8(v0, v1, v2, v3, v4, v5, v6, v7); +} + +inline unsigned v_reduce_sad(const v_uint8x32& a, const v_uint8x32& b) +{ + __m256i t0 = __lasx_xvabsd_bu(a.val, b.val); + __m256i t1 = __lasx_xvhaddw_hu_bu(t0, t0); + __m256i t2 = __lasx_xvhaddw_wu_hu(t1, t1); + __m256i t3 = __lasx_xvhaddw_du_wu(t2, t2); + return (unsigned)(((v4u64)t3)[0]+((v4u64)t3)[1]+((v4u64)t3)[2]+((v4u64)t3)[3]); +} +inline unsigned v_reduce_sad(const v_int8x32& a, const v_int8x32& b) +{ + __m256i t0 = __lasx_xvabsd_b(a.val, b.val); + __m256i t1 = __lasx_xvhaddw_hu_bu(t0, t0); + __m256i t2 = __lasx_xvhaddw_wu_hu(t1, t1); + __m256i t3 = __lasx_xvhaddw_du_wu(t2, t2); + return (unsigned)(((v4u64)t3)[0]+((v4u64)t3)[1]+((v4u64)t3)[2]+((v4u64)t3)[3]); +} +inline unsigned v_reduce_sad(const v_uint16x16& a, const v_uint16x16& b) +{ + v_uint32x8 l, h; + v_expand(v_add_wrap(a - b, b - a), l, h); + return v_reduce_sum(l + h); +} +inline unsigned v_reduce_sad(const v_int16x16& a, const v_int16x16& b) +{ + v_uint32x8 l, h; + v_expand(v_reinterpret_as_u16(v_sub_wrap(v_max(a, b), v_min(a, b))), l, h); + return v_reduce_sum(l + h); +} +inline unsigned v_reduce_sad(const v_uint32x8& a, const v_uint32x8& b) +{ + return v_reduce_sum(v_max(a, b) - v_min(a, b)); +} +inline unsigned v_reduce_sad(const v_int32x8& a, const v_int32x8& b) +{ + v_int32x8 m = a < b; + return v_reduce_sum(v_reinterpret_as_u32(((a - b) ^ m) - m)); +} +inline float v_reduce_sad(const v_float32x8& a, const v_float32x8& b) +{ + v_float32x8 a_b = a - b; + return v_reduce_sum(v_float32x8(*((__m256i*)&a_b.val) & __lasx_xvreplgr2vr_w(0x7fffffff))); +} + +/** Popcount **/ +inline v_uint8x32 v_popcount(const v_uint8x32& a) +{ + __m256i _popcnt_table = _v256_setr_b(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); + __m256i _popcnt_mask = __lasx_xvreplgr2vr_b(0x0F); + return v_uint8x32(__lasx_xvadd_b(__lasx_xvshuf_b(_popcnt_table, _popcnt_table, __lasx_xvand_v(a.val, _popcnt_mask)), + __lasx_xvshuf_b(_popcnt_table, _popcnt_table, __lasx_xvand_v(__lasx_xvsrli_h(a.val, 4), _popcnt_mask)))); +} +inline v_uint16x16 v_popcount(const v_uint16x16& a) +{ + v_uint8x32 p = v_popcount(v_reinterpret_as_u8(a)); + p += v_rotate_right<1>(p); + return v_reinterpret_as_u16(p) & v_uint16x16(__lasx_xvreplgr2vr_h(0x00ff)); +} +inline v_uint32x8 v_popcount(const v_uint32x8& a) +{ + v_uint8x32 p = v_popcount(v_reinterpret_as_u8(a)); + p += v_rotate_right<1>(p); + p += v_rotate_right<2>(p); + return v_reinterpret_as_u32(p) & v_uint32x8(__lasx_xvreplgr2vr_w(0x000000ff)); +} +inline v_uint64x4 v_popcount(const v_uint64x4& a) +{ + v_uint8x32 atemp = v_popcount(v_reinterpret_as_u8(a)); + uint8_t *pa = (uint8_t*)&atemp; + uint64 v[4]; + for (int i = 0; i < 4; ++i) { + v[i] = pa[i*8] + pa[i*8+1] + pa[i*8+2] + pa[i*8+3] + pa[i*8+4] + pa[i*8+5] + pa[i*8+6] + pa[i*8+7]; + } + return v_uint64x4(v[0], v[1], v[2], v[3]); +} +inline v_uint8x32 v_popcount(const v_int8x32& a) +{ return v_popcount(v_reinterpret_as_u8(a)); } +inline v_uint16x16 v_popcount(const v_int16x16& a) +{ return v_popcount(v_reinterpret_as_u16(a)); } +inline v_uint32x8 v_popcount(const v_int32x8& a) +{ return v_popcount(v_reinterpret_as_u32(a)); } +inline v_uint64x4 v_popcount(const v_int64x4& a) +{ return v_popcount(v_reinterpret_as_u64(a)); } + +/** Mask **/ +#define OPENCV_HAL_IMPL_REINTERPRET_INT(ft, tt) \ +inline tt reinterpret_int(ft x) { union { ft l; tt i; } v; v.l = x; return v.i; } +OPENCV_HAL_IMPL_REINTERPRET_INT(uchar, schar) +OPENCV_HAL_IMPL_REINTERPRET_INT(schar, schar) +OPENCV_HAL_IMPL_REINTERPRET_INT(ushort, short) +OPENCV_HAL_IMPL_REINTERPRET_INT(short, short) +OPENCV_HAL_IMPL_REINTERPRET_INT(unsigned, int) +OPENCV_HAL_IMPL_REINTERPRET_INT(int, int) +OPENCV_HAL_IMPL_REINTERPRET_INT(float, int) +OPENCV_HAL_IMPL_REINTERPRET_INT(uint64, int64) +OPENCV_HAL_IMPL_REINTERPRET_INT(int64, int64) +OPENCV_HAL_IMPL_REINTERPRET_INT(double, int64) + +inline int v_signmask(const v_int8x32& a) +{ + int mask = 0; + int8_t *pa = (int8_t*)&a; + for( int i = 0; i < 32; i++ ) + mask |= (reinterpret_int(pa[i]) < 0) << i; + return mask; +} +inline int v_signmask(const v_uint8x32& a) +{ return v_signmask(v_reinterpret_as_s8(a)); } + +inline int v_signmask(const v_int16x16& a) +{ return v_signmask(v_pack(a, a)) & 0xFFFF; } +inline int v_signmask(const v_uint16x16& a) +{ return v_signmask(v_reinterpret_as_s16(a)); } + +inline int v_signmask(const v_int32x8& a) +{ + int mask = 0; + int *pa = (int*)&a; + for( int i = 0; i < 8; i++ ) + mask |= (pa[i] < 0) << i; + return mask; +} +inline int v_signmask(const v_uint32x8& a) +{ return v_signmask(*(v_int32x8*)(&a)); } + +inline int v_signmask(const v_int64x4& a) +{ + int mask = 0; + int64 *pa = (int64*)&a; + for( int i = 0; i < 4; i++ ) + mask |= (pa[i] < 0) << i; + return mask; +} +inline int v_signmask(const v_uint64x4& a) +{ return v_signmask(v_reinterpret_as_s64(a)); } + +inline int v_signmask(const v_float32x8& a) +{ return v_signmask(*(v_int32x8*)(&a)); } + +inline int v_signmask(const v_float64x4& a) +{ return v_signmask(*(v_int64x4*)(&a)); } + +inline int v_scan_forward(const v_int8x32& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))); } +inline int v_scan_forward(const v_uint8x32& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))); } +inline int v_scan_forward(const v_int16x16& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 2; } +inline int v_scan_forward(const v_uint16x16& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 2; } +inline int v_scan_forward(const v_int32x8& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 4; } +inline int v_scan_forward(const v_uint32x8& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 4; } +inline int v_scan_forward(const v_float32x8& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 4; } +inline int v_scan_forward(const v_int64x4& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 8; } +inline int v_scan_forward(const v_uint64x4& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 8; } +inline int v_scan_forward(const v_float64x4& a) { return trailingZeros32(v_signmask(v_reinterpret_as_s8(a))) / 8; } + +/** Checks **/ +#define OPENCV_HAL_IMPL_LASX_CHECK(_Tpvec, allmask) \ + inline bool v_check_all(const _Tpvec& a) { return v_signmask(a) == allmask; } \ + inline bool v_check_any(const _Tpvec& a) { return v_signmask(a) != 0; } +OPENCV_HAL_IMPL_LASX_CHECK(v_uint8x32, -1) +OPENCV_HAL_IMPL_LASX_CHECK(v_int8x32, -1) +OPENCV_HAL_IMPL_LASX_CHECK(v_uint32x8, 255) +OPENCV_HAL_IMPL_LASX_CHECK(v_int32x8, 255) +OPENCV_HAL_IMPL_LASX_CHECK(v_uint64x4, 15) +OPENCV_HAL_IMPL_LASX_CHECK(v_int64x4, 15) +OPENCV_HAL_IMPL_LASX_CHECK(v_float32x8, 255) +OPENCV_HAL_IMPL_LASX_CHECK(v_float64x4, 15) + +#define OPENCV_HAL_IMPL_LASX_CHECK_SHORT(_Tpvec) \ + inline bool v_check_all(const _Tpvec& a) { return (v_signmask(v_reinterpret_as_s8(a)) & 0xaaaaaaaa) == 0xaaaaaaaa; } \ + inline bool v_check_any(const _Tpvec& a) { return (v_signmask(v_reinterpret_as_s8(a)) & 0xaaaaaaaa) != 0; } +OPENCV_HAL_IMPL_LASX_CHECK_SHORT(v_uint16x16) +OPENCV_HAL_IMPL_LASX_CHECK_SHORT(v_int16x16) + +////////// Other math ///////// + +/** Some frequent operations **/ +#define OPENCV_HAL_IMPL_LASX_MULADD(_Tpvec, suffix) \ + inline _Tpvec v_fma(const _Tpvec& a, const _Tpvec& b, const _Tpvec& c) \ + { return _Tpvec(__lasx_xvfmadd_##suffix(a.val, b.val, c.val)); } \ + inline _Tpvec v_muladd(const _Tpvec& a, const _Tpvec& b, const _Tpvec& c) \ + { return _Tpvec(__lasx_xvfmadd_##suffix(a.val, b.val, c.val)); } \ + inline _Tpvec v_sqrt(const _Tpvec& x) \ + { return _Tpvec(__lasx_xvfsqrt_##suffix(x.val)); } \ + inline _Tpvec v_sqr_magnitude(const _Tpvec& a, const _Tpvec& b) \ + { return v_fma(a, a, b * b); } \ + inline _Tpvec v_magnitude(const _Tpvec& a, const _Tpvec& b) \ + { return v_sqrt(v_fma(a, a, b*b)); } + +OPENCV_HAL_IMPL_LASX_MULADD(v_float32x8, s) +OPENCV_HAL_IMPL_LASX_MULADD(v_float64x4, d) + +inline v_int32x8 v_fma(const v_int32x8& a, const v_int32x8& b, const v_int32x8& c) +{ + return a * b + c; +} + +inline v_int32x8 v_muladd(const v_int32x8& a, const v_int32x8& b, const v_int32x8& c) +{ + return v_fma(a, b, c); +} + +inline v_float32x8 v_invsqrt(const v_float32x8& x) +{ + v_float32x8 half = x * v_float32x8(0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5); + v_float32x8 t = v_float32x8(__lasx_xvfrsqrt_s(x.val)); + t *= v_float32x8(1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5) - ((t * t) * half); + return t; +} + +inline v_float64x4 v_invsqrt(const v_float64x4& x) +{ + return v_float64x4(1., 1., 1., 1.) / v_sqrt(x); +} + +/** Absolute values **/ +#define OPENCV_HAL_IMPL_LASX_ABS(_Tpvec, suffix) \ + inline v_u##_Tpvec v_abs(const v_##_Tpvec& x) \ + { return v_u##_Tpvec(__lasx_xvabsd_##suffix(x.val, __lasx_xvreplgr2vr_w(0))); } + +OPENCV_HAL_IMPL_LASX_ABS(int8x32, b) +OPENCV_HAL_IMPL_LASX_ABS(int16x16, h) +OPENCV_HAL_IMPL_LASX_ABS(int32x8, w) + +inline v_float32x8 v_abs(const v_float32x8& x) +{ return v_float32x8(*((__m256i*)&x) & __lasx_xvreplgr2vr_w(0x7fffffff)); } +inline v_float64x4 v_abs(const v_float64x4& x) +{ return v_float64x4(*((__m256i*)&x) & __lasx_xvreplgr2vr_d(0x7fffffffffffffff)); } + +/** Absolute difference **/ +inline v_uint8x32 v_absdiff(const v_uint8x32& a, const v_uint8x32& b) +{ return v_add_wrap(a - b, b - a); } +inline v_uint16x16 v_absdiff(const v_uint16x16& a, const v_uint16x16& b) +{ return v_add_wrap(a - b, b - a); } +inline v_uint32x8 v_absdiff(const v_uint32x8& a, const v_uint32x8& b) +{ return v_max(a, b) - v_min(a, b); } + +inline v_uint8x32 v_absdiff(const v_int8x32& a, const v_int8x32& b) +{ + v_int8x32 d = v_sub_wrap(a, b); + v_int8x32 m = a < b; + return v_reinterpret_as_u8(v_sub_wrap(d ^ m, m)); +} + +inline v_uint16x16 v_absdiff(const v_int16x16& a, const v_int16x16& b) +{ return v_reinterpret_as_u16(v_sub_wrap(v_max(a, b), v_min(a, b))); } + +inline v_uint32x8 v_absdiff(const v_int32x8& a, const v_int32x8& b) +{ + v_int32x8 d = a - b; + v_int32x8 m = a < b; + return v_reinterpret_as_u32((d ^ m) - m); +} + +inline v_float32x8 v_absdiff(const v_float32x8& a, const v_float32x8& b) +{ return v_abs(a - b); } + +inline v_float64x4 v_absdiff(const v_float64x4& a, const v_float64x4& b) +{ return v_abs(a - b); } + +/** Saturating absolute difference **/ +inline v_int8x32 v_absdiffs(const v_int8x32& a, const v_int8x32& b) +{ + v_int8x32 d = a - b; + v_int8x32 m = a < b; + return (d ^ m) - m; +} +inline v_int16x16 v_absdiffs(const v_int16x16& a, const v_int16x16& b) +{ return v_max(a, b) - v_min(a, b); } + +////////// Conversions ///////// + +/** Rounding **/ +inline v_int32x8 v_round(const v_float32x8& a) +{ return v_int32x8(__lasx_xvftint_w_s(a.val)); } + +inline v_int32x8 v_round(const v_float64x4& a) +{ __m256i t = __lasx_xvftint_w_d(a.val, a.val); + return v_int32x8(__lasx_xvpermi_d(t, 0x88)); } + +inline v_int32x8 v_round(const v_float64x4& a, const v_float64x4& b) +{ + __m256i abi = __lasx_xvftint_w_d(b.val, a.val); + return v_int32x8(__lasx_xvpermi_d(abi, 0b11011000)); //3120 +} + +inline v_int32x8 v_trunc(const v_float32x8& a) +{ return v_int32x8(__lasx_xvftintrz_w_s(a.val)); } + +inline v_int32x8 v_trunc(const v_float64x4& a) +{ __m256i t = __lasx_xvftintrz_w_d(a.val, a.val); + return v_int32x8(__lasx_xvpermi_d(t, 0x88)); } + +inline v_int32x8 v_floor(const v_float32x8& a) +{ return v_int32x8(__lasx_xvftintrz_w_s(__m256(__lasx_xvfrintrm_s(a.val)))); } + +inline v_int32x8 v_floor(const v_float64x4& a) +{ return v_trunc(v_float64x4(__lasx_xvfrintrm_d(a.val))); } + +inline v_int32x8 v_ceil(const v_float32x8& a) +{ return v_int32x8(__lasx_xvftintrz_w_s(__m256(__lasx_xvfrintrp_s(a.val)))); } + +inline v_int32x8 v_ceil(const v_float64x4& a) +{ return v_trunc(v_float64x4(__lasx_xvfrintrp_d(a.val))); } + +/** To float **/ +inline v_float32x8 v_cvt_f32(const v_int32x8& a) +{ return v_float32x8(__lasx_xvffint_s_w(a.val)); } + +inline v_float32x8 v_cvt_f32(const v_float64x4& a) +{ return v_float32x8(__lasx_xvpermi_d(__lasx_xvfcvt_s_d(a.val, a.val), 0x88)); } + +inline v_float32x8 v_cvt_f32(const v_float64x4& a, const v_float64x4& b) +{ + __m256 abf = __lasx_xvfcvt_s_d(a.val, b.val); //warnning: order of a,b is diff from instruction xvfcvt.s.d + return v_float32x8(__lasx_xvpermi_d(abf, 0x8D)); +} + +inline v_float64x4 v_cvt_f64(const v_int32x8& a) +{ + __m256i alow = __lasx_xvpermi_d(a.val, 0x10); + return v_float64x4(__lasx_xvffintl_d_w(alow)); +} + +inline v_float64x4 v_cvt_f64_high(const v_int32x8& a) +{ + __m256i ahigh = __lasx_xvpermi_d(a.val, 0x32); + return v_float64x4(__lasx_xvffintl_d_w(ahigh)); +} + +inline v_float64x4 v_cvt_f64(const v_float32x8& a) +{ + __m256i alow = __lasx_xvpermi_d(a.val, 0x10); + return v_float64x4(__lasx_xvfcvtl_d_s((__m256)alow)); +} + +inline v_float64x4 v_cvt_f64_high(const v_float32x8& a) +{ + __m256i ahigh = __lasx_xvpermi_d(a.val, 0x32); + return v_float64x4(__lasx_xvfcvtl_d_s((__m256)ahigh)); +} + +// from (Mysticial and wim) https://stackoverflow.com/q/41144668 +inline v_float64x4 v_cvt_f64(const v_int64x4& v) +{ + // constants encoded as floating-point + __m256i magic_i_lo = __lasx_xvreplgr2vr_d(0x4330000000000000); + __m256i magic_i_hi32 = __lasx_xvreplgr2vr_d(0x4530000080000000); + __m256i magic_i_all = __lasx_xvreplgr2vr_d(0x4530000080100000); + __m256d magic_d_all = _lasx_256_castsi256_pd(magic_i_all); + + // Blend the 32 lowest significant bits of v with magic_int_lo + __m256i mask = _v256_set_w(0, -1, 0, -1, 0, -1, 0, -1); + __m256i v_lo = __lasx_xvbitsel_v(magic_i_lo, v.val, mask); + // Extract the 32 most significant bits of v + __m256i v_hi = __lasx_xvsrli_d(v.val, 32); + // Flip the msb of v_hi and blend with 0x45300000 + v_hi = __lasx_xvxor_v(v_hi, magic_i_hi32); + // Compute in double precision + __m256d v_hi_dbl = __lasx_xvfsub_d(_lasx_256_castsi256_pd(v_hi), magic_d_all); + // (v_hi - magic_d_all) + v_lo Do not assume associativity of floating point addition + __m256d result = __lasx_xvfadd_d(v_hi_dbl, _lasx_256_castsi256_pd(v_lo)); + return v_float64x4(result); +} + +////////////// Lookup table access //////////////////// + +inline v_int8x32 v256_lut(const schar* tab, const int* idx) +{ + return v_int8x32(_v256_setr_b(tab[idx[ 0]], tab[idx[ 1]], tab[idx[ 2]], tab[idx[ 3]], tab[idx[ 4]], tab[idx[ 5]], + tab[idx[ 6]], tab[idx[ 7]], tab[idx[ 8]], tab[idx[ 9]], tab[idx[10]], tab[idx[11]], + tab[idx[12]], tab[idx[13]], tab[idx[14]], tab[idx[15]], tab[idx[16]], tab[idx[17]], + tab[idx[18]], tab[idx[19]], tab[idx[20]], tab[idx[21]], tab[idx[22]], tab[idx[23]], + tab[idx[24]], tab[idx[25]], tab[idx[26]], tab[idx[27]], tab[idx[28]], tab[idx[29]], + tab[idx[30]], tab[idx[31]])); +} +inline v_int8x32 v256_lut_pairs(const schar* tab, const int* idx) +{ + return v_int8x32(_v256_setr_h(*(const short*)(tab + idx[ 0]), *(const short*)(tab + idx[ 1]), *(const short*)(tab + idx[ 2]), + *(const short*)(tab + idx[ 3]), *(const short*)(tab + idx[ 4]), *(const short*)(tab + idx[ 5]), + *(const short*)(tab + idx[ 6]), *(const short*)(tab + idx[ 7]), *(const short*)(tab + idx[ 8]), + *(const short*)(tab + idx[ 9]), *(const short*)(tab + idx[10]), *(const short*)(tab + idx[11]), + *(const short*)(tab + idx[12]), *(const short*)(tab + idx[13]), *(const short*)(tab + idx[14]), + *(const short*)(tab + idx[15]))); +} +inline v_int8x32 v256_lut_quads(const schar* tab, const int* idx) +{ + return v_int8x32(_v256_setr_w(*(const int*)(tab + idx[0]), *(const int*)(tab + idx[1]), + *(const int*)(tab + idx[2]), *(const int*)(tab + idx[3]), + *(const int*)(tab + idx[4]), *(const int*)(tab + idx[5]), + *(const int*)(tab + idx[6]), *(const int*)(tab + idx[7]))); +} +inline v_uint8x32 v256_lut(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v256_lut((const schar *)tab, idx)); } +inline v_uint8x32 v256_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v256_lut_pairs((const schar *)tab, idx)); } +inline v_uint8x32 v256_lut_quads(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v256_lut_quads((const schar *)tab, idx)); } + +inline v_int16x16 v256_lut(const short* tab, const int* idx) +{ + return v_int16x16(_v256_setr_h(tab[idx[ 0]], tab[idx[ 1]], tab[idx[ 2]], tab[idx[ 3]], tab[idx[ 4]], + tab[idx[ 5]], tab[idx[ 6]], tab[idx[ 7]], tab[idx[ 8]], tab[idx[ 9]], + tab[idx[10]], tab[idx[11]], tab[idx[12]], tab[idx[13]], tab[idx[14]], + tab[idx[15]])); +} +inline v_int16x16 v256_lut_pairs(const short* tab, const int* idx) +{ + return v_int16x16(_v256_setr_w(*(const int*)(tab + idx[0]), *(const int*)(tab + idx[1]), + *(const int*)(tab + idx[2]), *(const int*)(tab + idx[3]), + *(const int*)(tab + idx[4]), *(const int*)(tab + idx[5]), + *(const int*)(tab + idx[6]), *(const int*)(tab + idx[7]) )); +} +inline v_int16x16 v256_lut_quads(const short* tab, const int* idx) +{ + return v_int16x16(_v256_setr_d(*(const long long int*)(tab + idx[0]), *(const long long int*)(tab + idx[1]), + *(const long long int*)(tab + idx[2]), *(const long long int*)(tab + idx[3]) )); + +} +inline v_uint16x16 v256_lut(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v256_lut((const short *)tab, idx)); } +inline v_uint16x16 v256_lut_pairs(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v256_lut_pairs((const short *)tab, idx)); } +inline v_uint16x16 v256_lut_quads(const ushort* tab, const int* idx) { return v_reinterpret_as_u16(v256_lut_quads((const short *)tab, idx)); } + +inline v_int32x8 v256_lut(const int* tab, const int* idx) +{ + return v_int32x8(_v256_setr_w(*(const int*)(tab + idx[0]), *(const int*)(tab + idx[1]), + *(const int*)(tab + idx[2]), *(const int*)(tab + idx[3]), + *(const int*)(tab + idx[4]), *(const int*)(tab + idx[5]), + *(const int*)(tab + idx[6]), *(const int*)(tab + idx[7]) )); +} +inline v_int32x8 v256_lut_pairs(const int* tab, const int* idx) +{ + return v_int32x8(_v256_setr_d(*(const long long int*)(tab + idx[0]), *(const long long int*)(tab + idx[1]), + *(const long long int*)(tab + idx[2]), *(const long long int*)(tab + idx[3]) )); +} +inline v_int32x8 v256_lut_quads(const int* tab, const int* idx) +{ + return v_int32x8(_v256_combine(__lsx_vld(tab + idx[0], 0), __lsx_vld(tab + idx[1], 0))); +} +inline v_uint32x8 v256_lut(const unsigned* tab, const int* idx) { return v_reinterpret_as_u32(v256_lut((const int *)tab, idx)); } +inline v_uint32x8 v256_lut_pairs(const unsigned* tab, const int* idx) { return v_reinterpret_as_u32(v256_lut_pairs((const int *)tab, idx)); } +inline v_uint32x8 v256_lut_quads(const unsigned* tab, const int* idx) { return v_reinterpret_as_u32(v256_lut_quads((const int *)tab, idx)); } + +inline v_int64x4 v256_lut(const int64* tab, const int* idx) +{ + return v_int64x4(_v256_setr_d(*(const long long int*)(tab + idx[0]), *(const long long int*)(tab + idx[1]), + *(const long long int*)(tab + idx[2]), *(const long long int*)(tab + idx[3]) )); +} +inline v_int64x4 v256_lut_pairs(const int64* tab, const int* idx) +{ + return v_int64x4(_v256_combine(__lsx_vld(tab + idx[0], 0), __lsx_vld(tab + idx[1], 0))); +} +inline v_uint64x4 v256_lut(const uint64* tab, const int* idx) { return v_reinterpret_as_u64(v256_lut((const int64 *)tab, idx)); } +inline v_uint64x4 v256_lut_pairs(const uint64* tab, const int* idx) { return v_reinterpret_as_u64(v256_lut_pairs((const int64 *)tab, idx)); } + +inline v_float32x8 v256_lut(const float* tab, const int* idx) +{ + return v_float32x8(_v256_setr_ps(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]], + tab[idx[4]], tab[idx[5]], tab[idx[6]], tab[idx[7]])); +} +inline v_float32x8 v256_lut_pairs(const float* tab, const int* idx) { return v_reinterpret_as_f32(v256_lut_pairs((const int *)tab, idx)); } +inline v_float32x8 v256_lut_quads(const float* tab, const int* idx) { return v_reinterpret_as_f32(v256_lut_quads((const int *)tab, idx)); } + +inline v_float64x4 v256_lut(const double* tab, const int* idx) +{ + return v_float64x4(_v256_setr_pd(tab[idx[0]], tab[idx[1]], tab[idx[2]], tab[idx[3]])); +} +inline v_float64x4 v256_lut_pairs(const double* tab, const int* idx) +{ return v_float64x4(_v256_combine(__lsx_vld(tab + idx[0], 0), __lsx_vld(tab + idx[1], 0))); } + +inline v_int32x8 v_lut(const int* tab, const v_int32x8& idxvec) +{ + int *idx = (int*)&idxvec.val; + return v256_lut(tab, idx); +} + +inline v_uint32x8 v_lut(const unsigned* tab, const v_int32x8& idxvec) +{ + return v_reinterpret_as_u32(v_lut((const int *)tab, idxvec)); +} + +inline v_float32x8 v_lut(const float* tab, const v_int32x8& idxvec) +{ + const int *idx = (const int*)&idxvec.val; + return v256_lut(tab, idx); +} + +inline v_float64x4 v_lut(const double* tab, const v_int32x8& idxvec) +{ + const int *idx = (const int*)&idxvec.val; + return v256_lut(tab, idx); +} + +inline void v_lut_deinterleave(const float* tab, const v_int32x8& idxvec, v_float32x8& x, v_float32x8& y) +{ + const int *idx = (const int*)&idxvec.val; + __m128i xy01, xy45, xy23, xy67; + xy01 = __lsx_vld(tab + idx[0], 0); + xy01 = __lsx_vextrins_d(xy01, __lsx_vld(tab + idx[1], 0), 0x10); + xy45 = __lsx_vld(tab + idx[4], 0); + xy45 = __lsx_vextrins_d(xy45, __lsx_vld(tab + idx[5], 0), 0x10); + __m256i xy0145 = _v256_combine(xy01, xy45); + xy23 = __lsx_vld(tab + idx[2], 0); + xy23 = __lsx_vextrins_d(xy23, __lsx_vld(tab + idx[3], 0), 0x10); + xy67 = __lsx_vld(tab + idx[6], 0); + xy67 = __lsx_vextrins_d(xy67, __lsx_vld(tab + idx[7], 0), 0x10); + __m256i xy2367 = _v256_combine(xy23, xy67); + + __m256i xxyy0145 = __lasx_xvilvl_w(xy2367, xy0145); + __m256i xxyy2367 = __lasx_xvilvh_w(xy2367, xy0145); + + x = v_float32x8(__lasx_xvilvl_w(xxyy2367, xxyy0145)); + y = v_float32x8(__lasx_xvilvh_w(xxyy2367, xxyy0145)); +} + +inline void v_lut_deinterleave(const double* tab, const v_int32x8& idxvec, v_float64x4& x, v_float64x4& y) +{ + //int CV_DECL_ALIGNED(32) idx[4]; + const int *idx = (const int*)&idxvec.val; + __m128i xy0 = __lsx_vld(tab + idx[0], 0); + __m128i xy2 = __lsx_vld(tab + idx[2], 0); + __m128i xy1 = __lsx_vld(tab + idx[1], 0); + __m128i xy3 = __lsx_vld(tab + idx[3], 0); + __m256i xy02 = _v256_combine(xy0, xy2); + __m256i xy13 = _v256_combine(xy1, xy3); + + x = v_float64x4(__lasx_xvilvl_d(xy13, xy02)); + y = v_float64x4(__lasx_xvilvh_d(xy13, xy02)); +} + +inline v_int8x32 v_interleave_pairs(const v_int8x32& vec) +{ + return v_int8x32(__lasx_xvshuf_b(vec.val, vec.val, + _v256_set_d(0x0f0d0e0c0b090a08, 0x0705060403010200, 0x0f0d0e0c0b090a08, 0x0705060403010200))); +} +inline v_uint8x32 v_interleave_pairs(const v_uint8x32& vec) +{ return v_reinterpret_as_u8(v_interleave_pairs(v_reinterpret_as_s8(vec))); } +inline v_int8x32 v_interleave_quads(const v_int8x32& vec) +{ + return v_int8x32(__lasx_xvshuf_b(vec.val, vec.val, + _v256_set_d(0x0f0b0e0a0d090c08, 0x0703060205010400, 0x0f0b0e0a0d090c08, 0x0703060205010400))); +} +inline v_uint8x32 v_interleave_quads(const v_uint8x32& vec) +{ return v_reinterpret_as_u8(v_interleave_quads(v_reinterpret_as_s8(vec))); } + +inline v_int16x16 v_interleave_pairs(const v_int16x16& vec) +{ + return v_int16x16(__lasx_xvshuf_b(vec.val, vec.val, + _v256_set_d(0x0f0e0b0a0d0c0908, 0x0706030205040100, 0x0f0e0b0a0d0c0908, 0x0706030205040100))); +} +inline v_uint16x16 v_interleave_pairs(const v_uint16x16& vec) +{ return v_reinterpret_as_u16(v_interleave_pairs(v_reinterpret_as_s16(vec))); } +inline v_int16x16 v_interleave_quads(const v_int16x16& vec) +{ + return v_int16x16(__lasx_xvshuf_b(vec.val, vec.val, + _v256_set_d(0x0f0e07060d0c0504, 0x0b0a030209080100, 0x0f0e07060d0c0504, 0x0b0a030209080100))); +} +inline v_uint16x16 v_interleave_quads(const v_uint16x16& vec) +{ return v_reinterpret_as_u16(v_interleave_quads(v_reinterpret_as_s16(vec))); } + +inline v_int32x8 v_interleave_pairs(const v_int32x8& vec) +{ + return v_int32x8(__lasx_xvshuf4i_w(vec.val, 0xd8)); +} +inline v_uint32x8 v_interleave_pairs(const v_uint32x8& vec) +{ return v_reinterpret_as_u32(v_interleave_pairs(v_reinterpret_as_s32(vec))); } +inline v_float32x8 v_interleave_pairs(const v_float32x8& vec) +{ return v_reinterpret_as_f32(v_interleave_pairs(v_reinterpret_as_s32(vec))); } + +inline v_int8x32 v_pack_triplets(const v_int8x32& vec) +{ + __m256i vzero = __lasx_xvreplgr2vr_w(0); + __m256i t1 = __lasx_xvshuf_b(vec.val, vec.val, + _v256_set_d(0xffffff0f0e0d0c0a, 0x0908060504020100, 0xffffff0f0e0d0c0a, 0x0908060504020100)); + __m256i t2 = __lasx_xvshuf_b(vzero, t1, + _v256_set_d(0x1211100c0b0a0908, 0x0706050403020100, 0x1211100c0b0a0908, 0x0706050403020100)); + return v_int8x32(__lasx_xvperm_w(t2, + _v256_set_d(0x0000000700000007, 0x0000000600000005, 0x0000000400000002, 0x0000000100000000))); +} +inline v_uint8x32 v_pack_triplets(const v_uint8x32& vec) +{ return v_reinterpret_as_u8(v_pack_triplets(v_reinterpret_as_s8(vec))); } + +inline v_int16x16 v_pack_triplets(const v_int16x16& vec) +{ + __m256i vzero = __lasx_xvreplgr2vr_w(0); + __m256i t1 = __lasx_xvshuf_b(vec.val, vec.val, + _v256_set_d(0xffff0f0e0d0c0b0a, 0x0908050403020100, 0xffff0f0e0d0c0b0a, 0x0908050403020100)); + __m256i t2 = __lasx_xvshuf_b(vzero, t1, + _v256_set_d(0x11100d0c0b0a0908, 0x0706050403020100, 0x11100d0c0b0a0908, 0x0706050403020100)); + return v_int16x16(__lasx_xvperm_w(t2, + _v256_set_d(0x0000000700000007, 0x0000000600000005, 0x0000000400000002, 0x0000000100000000))); +} +inline v_uint16x16 v_pack_triplets(const v_uint16x16& vec) +{ return v_reinterpret_as_u16(v_pack_triplets(v_reinterpret_as_s16(vec))); } + +inline v_int32x8 v_pack_triplets(const v_int32x8& vec) +{ + return v_int32x8(__lasx_xvperm_w(vec.val, + _v256_set_d(0x0000000700000007, 0x0000000600000005, 0x0000000400000002, 0x0000000100000000))); +} +inline v_uint32x8 v_pack_triplets(const v_uint32x8& vec) +{ return v_reinterpret_as_u32(v_pack_triplets(v_reinterpret_as_s32(vec))); } +inline v_float32x8 v_pack_triplets(const v_float32x8& vec) +{ + return v_float32x8(__lasx_xvperm_w(*(__m256i*)(&vec.val), + _v256_set_d(0x0000000700000007, 0x0000000600000005, 0x0000000400000002, 0x0000000100000000))); +} + +////////// Matrix operations ///////// + +//////// Dot Product //////// + +// 16 >> 32 +inline v_int32x8 v_dotprod(const v_int16x16& a, const v_int16x16& b) +{ return v_int32x8(__lasx_xvadd_w(__lasx_xvmulwev_w_h(a.val, b.val), __lasx_xvmulwod_w_h(a.val, b.val))); } + +inline v_int32x8 v_dotprod(const v_int16x16& a, const v_int16x16& b, const v_int32x8& c) +{ return v_dotprod(a, b) + c; } + +// 32 >> 64 +inline v_int64x4 v_dotprod(const v_int32x8& a, const v_int32x8& b) +{ + __m256i even = __lasx_xvmulwev_d_w(a.val, b.val); + __m256i odd = __lasx_xvmulwod_d_w(a.val, b.val); + return v_int64x4(__lasx_xvadd_d(even, odd)); +} +inline v_int64x4 v_dotprod(const v_int32x8& a, const v_int32x8& b, const v_int64x4& c) +{ return v_dotprod(a, b) + c; } + +// 8 >> 32 +inline v_uint32x8 v_dotprod_expand(const v_uint8x32& a, const v_uint8x32& b) +{ + __m256i even_m = __lasx_xvreplgr2vr_w(0xFF00FF00); + __m256i even_a = __lasx_xvbitsel_v(a.val, __lasx_xvreplgr2vr_d(0), even_m); + __m256i odd_a = __lasx_xvsrli_h(a.val, 8); + + __m256i even_b = __lasx_xvbitsel_v(b.val, __lasx_xvreplgr2vr_d(0), even_m); + __m256i odd_b = __lasx_xvsrli_h(b.val, 8); + + __m256i prod0 = __lasx_xvadd_w(__lasx_xvmulwev_w_h(even_a, even_b), __lasx_xvmulwod_w_h(even_a, even_b)); + __m256i prod1 = __lasx_xvadd_w(__lasx_xvmulwev_w_h(odd_a, odd_b),__lasx_xvmulwod_w_h(odd_a, odd_b)); + return v_uint32x8(__lasx_xvadd_w(prod0, prod1)); +} +inline v_uint32x8 v_dotprod_expand(const v_uint8x32& a, const v_uint8x32& b, const v_uint32x8& c) +{ return v_dotprod_expand(a, b) + c; } + +inline v_int32x8 v_dotprod_expand(const v_int8x32& a, const v_int8x32& b) +{ + __m256i even_a = __lasx_xvsrai_h(__lasx_xvbsll_v(a.val, 1), 8); + __m256i odd_a = __lasx_xvsrai_h(a.val, 8); + + __m256i even_b = __lasx_xvsrai_h(__lasx_xvbsll_v(b.val, 1), 8); + __m256i odd_b = __lasx_xvsrai_h(b.val, 8); + + __m256i prod0 = __lasx_xvadd_w(__lasx_xvmulwev_w_h(even_a, even_b), __lasx_xvmulwod_w_h(even_a, even_b)); + __m256i prod1 = __lasx_xvadd_w(__lasx_xvmulwev_w_h(odd_a, odd_b),__lasx_xvmulwod_w_h(odd_a, odd_b)); + return v_int32x8(__lasx_xvadd_w(prod0, prod1)); +} +inline v_int32x8 v_dotprod_expand(const v_int8x32& a, const v_int8x32& b, const v_int32x8& c) +{ return v_dotprod_expand(a, b) + c; } + +// 16 >> 64 +inline v_uint64x4 v_dotprod_expand(const v_uint16x16& a, const v_uint16x16& b) +{ + __m256i mullo = __lasx_xvmul_h(a.val, b.val); + __m256i mulhi = __lasx_xvmuh_hu(a.val, b.val); + __m256i mul0 = __lasx_xvilvl_h(mulhi, mullo); + __m256i mul1 = __lasx_xvilvh_h(mulhi, mullo); + + __m256i p02 = __lasx_xvbitsel_v(mul0, __lasx_xvreplgr2vr_d(0), _v256_set_w(-1, 0, -1, 0, -1, 0, -1, 0)); + __m256i p13 = __lasx_xvsrli_d(mul0, 32); + __m256i p46 = __lasx_xvbitsel_v(mul1, __lasx_xvreplgr2vr_d(0), _v256_set_w(-1, 0, -1, 0, -1, 0, -1, 0)); + __m256i p57 = __lasx_xvsrli_d(mul1, 32); + + __m256i p15_ = __lasx_xvadd_d(p02, p13); + __m256i p9d_ = __lasx_xvadd_d(p46, p57); + + return v_uint64x4(__lasx_xvadd_d( + __lasx_xvilvl_d(p9d_, p15_), + __lasx_xvilvh_d(p9d_, p15_))); +} +inline v_uint64x4 v_dotprod_expand(const v_uint16x16& a, const v_uint16x16& b, const v_uint64x4& c) +{ return v_dotprod_expand(a, b) + c; } + +inline v_int64x4 v_dotprod_expand(const v_int16x16& a, const v_int16x16& b) +{ + __m256i prod = __lasx_xvadd_w(__lasx_xvmulwev_w_h(a.val, b.val), __lasx_xvmulwod_w_h(a.val, b.val)); + __m256i sign = __lasx_xvsrai_w(prod, 31); + + __m256i lo = __lasx_xvilvl_w(sign, prod); + __m256i hi = __lasx_xvilvh_w(sign, prod); + + return v_int64x4(__lasx_xvadd_d(__lasx_xvilvl_d(hi, lo), __lasx_xvilvh_d(hi, lo))); +} +inline v_int64x4 v_dotprod_expand(const v_int16x16& a, const v_int16x16& b, const v_int64x4& c) +{ return v_dotprod_expand(a, b) + c; } + +// 32 >> 64f +inline v_float64x4 v_dotprod_expand(const v_int32x8& a, const v_int32x8& b) +{ return v_cvt_f64(v_dotprod(a, b)); } +inline v_float64x4 v_dotprod_expand(const v_int32x8& a, const v_int32x8& b, const v_float64x4& c) +{ return v_dotprod_expand(a, b) + c; } + +//////// Fast Dot Product //////// + +// 16 >> 32 +inline v_int32x8 v_dotprod_fast(const v_int16x16& a, const v_int16x16& b) +{ return v_dotprod(a, b); } +inline v_int32x8 v_dotprod_fast(const v_int16x16& a, const v_int16x16& b, const v_int32x8& c) +{ return v_dotprod(a, b, c); } + +// 32 >> 64 +inline v_int64x4 v_dotprod_fast(const v_int32x8& a, const v_int32x8& b) +{ return v_dotprod(a, b); } +inline v_int64x4 v_dotprod_fast(const v_int32x8& a, const v_int32x8& b, const v_int64x4& c) +{ return v_dotprod(a, b, c); } + +// 8 >> 32 +inline v_uint32x8 v_dotprod_expand_fast(const v_uint8x32& a, const v_uint8x32& b) +{ return v_dotprod_expand(a, b); } +inline v_uint32x8 v_dotprod_expand_fast(const v_uint8x32& a, const v_uint8x32& b, const v_uint32x8& c) +{ return v_dotprod_expand(a, b, c); } + +inline v_int32x8 v_dotprod_expand_fast(const v_int8x32& a, const v_int8x32& b) +{ return v_dotprod_expand(a, b); } +inline v_int32x8 v_dotprod_expand_fast(const v_int8x32& a, const v_int8x32& b, const v_int32x8& c) +{ return v_dotprod_expand(a, b, c); } + +// 16 >> 64 +inline v_uint64x4 v_dotprod_expand_fast(const v_uint16x16& a, const v_uint16x16& b) +{ + __m256i mullo = __lasx_xvmul_h(a.val, b.val); + __m256i mulhi = __lasx_xvmuh_hu(a.val, b.val); + __m256i mul0 = __lasx_xvilvl_h(mulhi, mullo); + __m256i mul1 = __lasx_xvilvh_h(mulhi, mullo); + + __m256i p02 = __lasx_xvbitsel_v(mul0, __lasx_xvreplgr2vr_d(0), _v256_set_w(-1, 0, -1, 0, -1, 0, -1, 0)); + __m256i p13 = __lasx_xvsrli_d(mul0, 32); + __m256i p46 = __lasx_xvbitsel_v(mul1, __lasx_xvreplgr2vr_d(0), _v256_set_w(-1, 0, -1, 0, -1, 0, -1, 0)); + __m256i p57 = __lasx_xvsrli_d(mul1, 32); + + __m256i p15_ = __lasx_xvadd_d(p02, p13); + __m256i p9d_ = __lasx_xvadd_d(p46, p57); + + return v_uint64x4(__lasx_xvadd_d(p15_, p9d_)); +} +inline v_uint64x4 v_dotprod_expand_fast(const v_uint16x16& a, const v_uint16x16& b, const v_uint64x4& c) +{ return v_dotprod_expand_fast(a, b) + c; } + +inline v_int64x4 v_dotprod_expand_fast(const v_int16x16& a, const v_int16x16& b) +{ + __m256i prod = __lasx_xvadd_w(__lasx_xvmulwev_w_h(a.val, b.val), __lasx_xvmulwod_w_h(a.val, b.val)); + __m256i sign = __lasx_xvsrai_w(prod, 31); + __m256i lo = __lasx_xvilvl_w(sign, prod); + __m256i hi = __lasx_xvilvh_w(sign, prod); + return v_int64x4(__lasx_xvadd_d(lo, hi)); +} +inline v_int64x4 v_dotprod_expand_fast(const v_int16x16& a, const v_int16x16& b, const v_int64x4& c) +{ return v_dotprod_expand_fast(a, b) + c; } + +// 32 >> 64f +inline v_float64x4 v_dotprod_expand_fast(const v_int32x8& a, const v_int32x8& b) +{ return v_dotprod_expand(a, b); } +inline v_float64x4 v_dotprod_expand_fast(const v_int32x8& a, const v_int32x8& b, const v_float64x4& c) +{ return v_dotprod_expand(a, b, c); } + + +#define OPENCV_HAL_LASX_SPLAT2_PS(a, im) \ + v_float32x8(__lasx_xvpermi_w(a.val, a.val, im)) + +inline v_float32x8 v_matmul(const v_float32x8& v, const v_float32x8& m0, + const v_float32x8& m1, const v_float32x8& m2, + const v_float32x8& m3) +{ + v_float32x8 v04 = OPENCV_HAL_LASX_SPLAT2_PS(v, 0); + v_float32x8 v15 = OPENCV_HAL_LASX_SPLAT2_PS(v, 0x55); + v_float32x8 v26 = OPENCV_HAL_LASX_SPLAT2_PS(v, 0xAA); + v_float32x8 v37 = OPENCV_HAL_LASX_SPLAT2_PS(v, 0xFF); + return v_fma(v04, m0, v_fma(v15, m1, v_fma(v26, m2, v37 * m3))); +} + +inline v_float32x8 v_matmuladd(const v_float32x8& v, const v_float32x8& m0, + const v_float32x8& m1, const v_float32x8& m2, + const v_float32x8& a) +{ + v_float32x8 v04 = OPENCV_HAL_LASX_SPLAT2_PS(v, 0); + v_float32x8 v15 = OPENCV_HAL_LASX_SPLAT2_PS(v, 0x55); + v_float32x8 v26 = OPENCV_HAL_LASX_SPLAT2_PS(v, 0xAA); + return v_fma(v04, m0, v_fma(v15, m1, v_fma(v26, m2, a))); +} + + +#define OPENCV_HAL_IMPL_LASX_TRANSPOSE4x4(_Tpvec, cast_from, cast_to) \ + inline void v_transpose4x4(const _Tpvec& a0, const _Tpvec& a1, \ + const _Tpvec& a2, const _Tpvec& a3, \ + _Tpvec& b0, _Tpvec& b1, _Tpvec& b2, _Tpvec& b3) \ + { \ + __m256i t0 = cast_from(__lasx_xvilvl_w(a1.val, a0.val)); \ + __m256i t1 = cast_from(__lasx_xvilvl_w(a3.val, a2.val)); \ + __m256i t2 = cast_from(__lasx_xvilvh_w(a1.val, a0.val)); \ + __m256i t3 = cast_from(__lasx_xvilvh_w(a3.val, a2.val)); \ + b0.val = cast_to(__lasx_xvilvl_d(t1, t0)); \ + b1.val = cast_to(__lasx_xvilvh_d(t1, t0)); \ + b2.val = cast_to(__lasx_xvilvl_d(t3, t2)); \ + b3.val = cast_to(__lasx_xvilvh_d(t3, t2)); \ + } + +OPENCV_HAL_IMPL_LASX_TRANSPOSE4x4(v_uint32x8, OPENCV_HAL_NOP, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_LASX_TRANSPOSE4x4(v_int32x8, OPENCV_HAL_NOP, OPENCV_HAL_NOP) + +inline void v_transpose4x4(const v_float32x8 &a0, const v_float32x8 &a1, + const v_float32x8 &a2, const v_float32x8 &a3, + v_float32x8 &b0, v_float32x8 &b1, v_float32x8 &b2, v_float32x8 &b3) +{ + __m256i t0 = __lasx_xvilvl_w(__m256i(a1.val), __m256i(a0.val)); + __m256i t1 = __lasx_xvilvl_w(__m256i(a3.val), __m256i(a2.val)); + __m256i t2 = __lasx_xvilvh_w(__m256i(a1.val), __m256i(a0.val)); + __m256i t3 = __lasx_xvilvh_w(__m256i(a3.val), __m256i(a2.val)); + b0.val = __m256(__lasx_xvilvl_d(t1, t0)); + b1.val = __m256(__lasx_xvilvh_d(t1, t0)); + b2.val = __m256(__lasx_xvilvl_d(t3, t2)); + b3.val = __m256(__lasx_xvilvh_d(t3, t2)); +} + +//////////////// Value reordering /////////////// + +/* Expand */ +#define OPENCV_HAL_IMPL_LASX_EXPAND(_Tpvec, _Tpwvec, _Tp, intrin) \ + inline void v_expand(const _Tpvec& a, _Tpwvec& b0, _Tpwvec& b1) \ + { \ + b0.val = intrin(a.val); \ + b1.val = intrin(__lasx_xvpermi_q(a.val, a.val, 0x11)); \ + } \ + inline _Tpwvec v_expand_low(const _Tpvec& a) \ + { return _Tpwvec(intrin(a.val)); } \ + inline _Tpwvec v_expand_high(const _Tpvec& a) \ + { return _Tpwvec(intrin(__lasx_xvpermi_q(a.val, a.val, 0x11))); } \ + inline _Tpwvec v256_load_expand(const _Tp* ptr) \ + { \ + __m128i a = __lsx_vld(ptr, 0); \ + return _Tpwvec(intrin(*((__m256i*)&a))); \ + } + +OPENCV_HAL_IMPL_LASX_EXPAND(v_uint8x32, v_uint16x16, uchar, __lasx_vext2xv_hu_bu) +OPENCV_HAL_IMPL_LASX_EXPAND(v_int8x32, v_int16x16, schar, __lasx_vext2xv_h_b) +OPENCV_HAL_IMPL_LASX_EXPAND(v_uint16x16, v_uint32x8, ushort, __lasx_vext2xv_wu_hu) +OPENCV_HAL_IMPL_LASX_EXPAND(v_int16x16, v_int32x8, short, __lasx_vext2xv_w_h) +OPENCV_HAL_IMPL_LASX_EXPAND(v_uint32x8, v_uint64x4, unsigned, __lasx_vext2xv_du_wu) +OPENCV_HAL_IMPL_LASX_EXPAND(v_int32x8, v_int64x4, int, __lasx_vext2xv_d_w) + +#define OPENCV_HAL_IMPL_LASX_EXPAND_Q(_Tpvec, _Tp, intrin) \ + inline _Tpvec v256_load_expand_q(const _Tp* ptr) \ + { \ + __m128i a = __lsx_vld(ptr, 0); \ + return _Tpvec(intrin(*((__m256i*)&a))); \ + } + +OPENCV_HAL_IMPL_LASX_EXPAND_Q(v_uint32x8, uchar, __lasx_vext2xv_wu_bu) +OPENCV_HAL_IMPL_LASX_EXPAND_Q(v_int32x8, schar, __lasx_vext2xv_w_b) + +/* pack */ +// 16 +inline v_int8x32 v_pack(const v_int16x16& a, const v_int16x16& b) +{ return v_int8x32(_v256_shuffle_odd_64(_lasx_packs_h(a.val, b.val))); } + +inline v_uint8x32 v_pack(const v_uint16x16& a, const v_uint16x16& b) +{ + __m256i t = __lasx_xvreplgr2vr_h(255); + __m256i a1 = __lasx_xvmin_hu(a.val, t); + __m256i b1 = __lasx_xvmin_hu(b.val, t); + return v_uint8x32(_v256_shuffle_odd_64(_lasx_packus_h(a1, b1))); +} + +inline v_uint8x32 v_pack_u(const v_int16x16& a, const v_int16x16& b) +{ + return v_uint8x32(_v256_shuffle_odd_64(_lasx_packus_h(a.val, b.val))); +} + +inline void v_pack_store(schar* ptr, const v_int16x16& a) +{ v_store_low(ptr, v_pack(a, a)); } + +inline void v_pack_store(uchar* ptr, const v_uint16x16& a) +{ + const __m256i m = __lasx_xvreplgr2vr_h(255); + __m256i am = __lasx_xvmin_hu(a.val, m); + am = _v256_shuffle_odd_64(_lasx_packus_h(am, am)); + v_store_low(ptr, v_uint8x32(am)); +} + +inline void v_pack_u_store(uchar* ptr, const v_int16x16& a) +{ v_store_low(ptr, v_pack_u(a, a)); } + +template inline +v_uint8x32 v_rshr_pack(const v_uint16x16& a, const v_uint16x16& b) +{ + // we assume that n > 0, and so the shifted 16-bit values can be treated as signed numbers. + v_uint16x16 delta = v256_setall_u16((short)(1 << (n-1))); + return v_pack_u(v_reinterpret_as_s16((a + delta) >> n), + v_reinterpret_as_s16((b + delta) >> n)); +} + +template inline +void v_rshr_pack_store(uchar* ptr, const v_uint16x16& a) +{ + v_uint16x16 delta = v256_setall_u16((short)(1 << (n-1))); + v_pack_u_store(ptr, v_reinterpret_as_s16((a + delta) >> n)); +} + +template inline +v_uint8x32 v_rshr_pack_u(const v_int16x16& a, const v_int16x16& b) +{ + v_int16x16 delta = v256_setall_s16((short)(1 << (n-1))); + return v_pack_u((a + delta) >> n, (b + delta) >> n); +} + +template inline +void v_rshr_pack_u_store(uchar* ptr, const v_int16x16& a) +{ + v_int16x16 delta = v256_setall_s16((short)(1 << (n-1))); + v_pack_u_store(ptr, (a + delta) >> n); +} + +template inline +v_int8x32 v_rshr_pack(const v_int16x16& a, const v_int16x16& b) +{ + v_int16x16 delta = v256_setall_s16((short)(1 << (n-1))); + return v_pack((a + delta) >> n, (b + delta) >> n); +} + +template inline +void v_rshr_pack_store(schar* ptr, const v_int16x16& a) +{ + v_int16x16 delta = v256_setall_s16((short)(1 << (n-1))); + v_pack_store(ptr, (a + delta) >> n); +} + +// 32 +inline v_int16x16 v_pack(const v_int32x8& a, const v_int32x8& b) +{ return v_int16x16(_v256_shuffle_odd_64(_lasx_packs_w(a.val, b.val))); } + +inline v_uint16x16 v_pack(const v_uint32x8& a, const v_uint32x8& b) +{ return v_uint16x16(_v256_shuffle_odd_64(_v256_packs_epu32(a.val, b.val))); } + +inline v_uint16x16 v_pack_u(const v_int32x8& a, const v_int32x8& b) +{ return v_uint16x16(_v256_shuffle_odd_64(_lasx_packus_w(a.val, b.val))); } + +inline void v_pack_store(short* ptr, const v_int32x8& a) +{ v_store_low(ptr, v_pack(a, a)); } + +inline void v_pack_store(ushort* ptr, const v_uint32x8& a) +{ + const __m256i m = __lasx_xvreplgr2vr_w(65535); + __m256i am = __lasx_xvmin_wu(a.val, m); + am = _v256_shuffle_odd_64(_lasx_packus_w(am, am)); + v_store_low(ptr, v_uint16x16(am)); +} + +inline void v_pack_u_store(ushort* ptr, const v_int32x8& a) +{ v_store_low(ptr, v_pack_u(a, a)); } + + +template inline +v_uint16x16 v_rshr_pack(const v_uint32x8& a, const v_uint32x8& b) +{ + // we assume that n > 0, and so the shifted 32-bit values can be treated as signed numbers. + v_uint32x8 delta = v256_setall_u32(1 << (n-1)); + return v_pack_u(v_reinterpret_as_s32((a + delta) >> n), + v_reinterpret_as_s32((b + delta) >> n)); +} + +template inline +void v_rshr_pack_store(ushort* ptr, const v_uint32x8& a) +{ + v_uint32x8 delta = v256_setall_u32(1 << (n-1)); + v_pack_u_store(ptr, v_reinterpret_as_s32((a + delta) >> n)); +} + +template inline +v_uint16x16 v_rshr_pack_u(const v_int32x8& a, const v_int32x8& b) +{ + v_int32x8 delta = v256_setall_s32(1 << (n-1)); + return v_pack_u((a + delta) >> n, (b + delta) >> n); +} + +template inline +void v_rshr_pack_u_store(ushort* ptr, const v_int32x8& a) +{ + v_int32x8 delta = v256_setall_s32(1 << (n-1)); + v_pack_u_store(ptr, (a + delta) >> n); +} + +template inline +v_int16x16 v_rshr_pack(const v_int32x8& a, const v_int32x8& b) +{ + v_int32x8 delta = v256_setall_s32(1 << (n-1)); + return v_pack((a + delta) >> n, (b + delta) >> n); +} + +template inline +void v_rshr_pack_store(short* ptr, const v_int32x8& a) +{ + v_int32x8 delta = v256_setall_s32(1 << (n-1)); + v_pack_store(ptr, (a + delta) >> n); +} + +// 64 +// Non-saturating pack +inline v_uint32x8 v_pack(const v_uint64x4& a, const v_uint64x4& b) +{ + __m256i a0 = __lasx_xvshuf4i_w(a.val, 0x08); + __m256i b0 = __lasx_xvshuf4i_w(b.val, 0x08); + __m256i ab = __lasx_xvilvl_d(b0, a0); + return v_uint32x8(_v256_shuffle_odd_64(ab)); +} + +inline v_int32x8 v_pack(const v_int64x4& a, const v_int64x4& b) +{ return v_reinterpret_as_s32(v_pack(v_reinterpret_as_u64(a), v_reinterpret_as_u64(b))); } + +inline void v_pack_store(unsigned* ptr, const v_uint64x4& a) +{ + __m256i a0 = __lasx_xvshuf4i_w(a.val, 0x08); + v_store_low(ptr, v_uint32x8(_v256_shuffle_odd_64(a0))); +} + +inline void v_pack_store(int* ptr, const v_int64x4& b) +{ v_pack_store((unsigned*)ptr, v_reinterpret_as_u64(b)); } + +template inline +v_uint32x8 v_rshr_pack(const v_uint64x4& a, const v_uint64x4& b) +{ + v_uint64x4 delta = v256_setall_u64((uint64)1 << (n-1)); + return v_pack((a + delta) >> n, (b + delta) >> n); +} + +template inline +void v_rshr_pack_store(unsigned* ptr, const v_uint64x4& a) +{ + v_uint64x4 delta = v256_setall_u64((uint64)1 << (n-1)); + v_pack_store(ptr, (a + delta) >> n); +} + +template inline +v_int32x8 v_rshr_pack(const v_int64x4& a, const v_int64x4& b) +{ + v_int64x4 delta = v256_setall_s64((int64)1 << (n-1)); + return v_pack((a + delta) >> n, (b + delta) >> n); +} + +template inline +void v_rshr_pack_store(int* ptr, const v_int64x4& a) +{ + v_int64x4 delta = v256_setall_s64((int64)1 << (n-1)); + v_pack_store(ptr, (a + delta) >> n); +} + +// pack boolean +inline v_uint8x32 v_pack_b(const v_uint16x16& a, const v_uint16x16& b) +{ + __m256i ab = _lasx_packs_h(a.val, b.val); + return v_uint8x32(_v256_shuffle_odd_64(ab)); +} + +inline v_uint8x32 v_pack_b(const v_uint32x8& a, const v_uint32x8& b, + const v_uint32x8& c, const v_uint32x8& d) +{ + __m256i ab = _lasx_packs_w(a.val, b.val); + __m256i cd = _lasx_packs_w(c.val, d.val); + + __m256i abcd = _v256_shuffle_odd_64(_lasx_packs_h(ab, cd)); + return v_uint8x32(__lasx_xvshuf4i_w(abcd, 0xd8)); +} + +inline v_uint8x32 v_pack_b(const v_uint64x4& a, const v_uint64x4& b, const v_uint64x4& c, + const v_uint64x4& d, const v_uint64x4& e, const v_uint64x4& f, + const v_uint64x4& g, const v_uint64x4& h) +{ + __m256i ab = _lasx_packs_w(a.val, b.val); + __m256i cd = _lasx_packs_w(c.val, d.val); + __m256i ef = _lasx_packs_w(e.val, f.val); + __m256i gh = _lasx_packs_w(g.val, h.val); + + __m256i abcd = _lasx_packs_w(ab, cd); + __m256i efgh = _lasx_packs_w(ef, gh); + __m256i pkall = _v256_shuffle_odd_64(_lasx_packs_h(abcd, efgh)); + + __m256i rev = _v256_alignr_b(pkall, pkall, 8); + return v_uint8x32(__lasx_xvilvl_h(rev, pkall)); +} + +/* Recombine */ +// its up there with load and store operations + +/* Extract */ +#define OPENCV_HAL_IMPL_LASX_EXTRACT(_Tpvec) \ + template \ + inline _Tpvec v_extract(const _Tpvec& a, const _Tpvec& b) \ + { return v_rotate_right(a, b); } + +OPENCV_HAL_IMPL_LASX_EXTRACT(v_uint8x32) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_int8x32) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_uint16x16) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_int16x16) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_uint32x8) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_int32x8) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_uint64x4) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_int64x4) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_float32x8) +OPENCV_HAL_IMPL_LASX_EXTRACT(v_float64x4) + +template +inline uchar v_extract_n(v_uint8x32 a) +{ + return (uchar)_v256_extract_b(a.val); +} + +template +inline schar v_extract_n(v_int8x32 a) +{ + return (schar)v_extract_n(v_reinterpret_as_u8(a)); +} + +template +inline ushort v_extract_n(v_uint16x16 a) +{ + return (ushort)_v256_extract_h(a.val); +} + +template +inline short v_extract_n(v_int16x16 a) +{ + return (short)v_extract_n(v_reinterpret_as_u16(a)); +} + +template +inline uint v_extract_n(v_uint32x8 a) +{ + return (uint)_v256_extract_w(a.val); +} + +template +inline int v_extract_n(v_int32x8 a) +{ + return (int)v_extract_n(v_reinterpret_as_u32(a)); +} + +template +inline uint64 v_extract_n(v_uint64x4 a) +{ + return (uint64)_v256_extract_d(a.val); +} + +template +inline int64 v_extract_n(v_int64x4 v) +{ + return (int64)v_extract_n(v_reinterpret_as_u64(v)); +} + +template +inline float v_extract_n(v_float32x8 v) +{ + union { uint iv; float fv; } d; + d.iv = v_extract_n(v_reinterpret_as_u32(v)); + return d.fv; +} + +template +inline double v_extract_n(v_float64x4 v) +{ + union { uint64 iv; double dv; } d; + d.iv = v_extract_n(v_reinterpret_as_u64(v)); + return d.dv; +} + +template +inline v_uint32x8 v_broadcast_element(v_uint32x8 a) +{ + static const __m256i perm = __lasx_xvreplgr2vr_w((char)i); + return v_uint32x8(__lasx_xvperm_w(a.val, perm)); +} + +template +inline v_int32x8 v_broadcast_element(const v_int32x8 &a) +{ return v_reinterpret_as_s32(v_broadcast_element(v_reinterpret_as_u32(a))); } + +template +inline v_float32x8 v_broadcast_element(const v_float32x8 &a) +{ return v_reinterpret_as_f32(v_broadcast_element(v_reinterpret_as_u32(a))); } + + +///////////////////// load deinterleave ///////////////////////////// + +inline void v_load_deinterleave( const uchar* ptr, v_uint8x32& a, v_uint8x32& b ) +{ + __m256i ab0 = __lasx_xvld(ptr, 0); + __m256i ab1 = __lasx_xvld(ptr + 32, 0); + + const __m256i sh = _v256_setr_b(0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15, + 0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15); + __m256i p0 = __lasx_xvshuf_b(ab0, ab0, sh); + __m256i p1 = __lasx_xvshuf_b(ab1, ab1, sh); + __m256i pl = __lasx_xvpermi_q(p0, p1, 0x02); + __m256i ph = __lasx_xvpermi_q(p0, p1, 0x13); + __m256i a0 = __lasx_xvilvl_d(ph, pl); + __m256i b0 = __lasx_xvilvh_d(ph, pl); + a = v_uint8x32(a0); + b = v_uint8x32(b0); +} + +inline void v_load_deinterleave( const ushort* ptr, v_uint16x16& a, v_uint16x16& b ) +{ + __m256i ab0 = __lasx_xvld(ptr, 0); + __m256i ab1 = __lasx_xvld(ptr + 16, 0); + + const __m256i sh = _v256_setr_b(0, 1, 4, 5, 8, 9, 12, 13, 2, 3, 6, 7, 10, 11, 14, 15, + 0, 1, 4, 5, 8, 9, 12, 13, 2, 3, 6, 7, 10, 11, 14, 15); + __m256i p0 = __lasx_xvshuf_b(ab0, ab0, sh); + __m256i p1 = __lasx_xvshuf_b(ab1, ab1, sh); + __m256i pl = __lasx_xvpermi_q(p0, p1, 0x02); + __m256i ph = __lasx_xvpermi_q(p0, p1, 0x13); + __m256i a0 = __lasx_xvilvl_d(ph, pl); + __m256i b0 = __lasx_xvilvh_d(ph, pl); + a = v_uint16x16(a0); + b = v_uint16x16(b0); +} + +inline void v_load_deinterleave( const unsigned* ptr, v_uint32x8& a, v_uint32x8& b ) +{ + __m256i ab0 = __lasx_xvld(ptr, 0); + __m256i ab1 = __lasx_xvld(ptr + 8, 0); + + //const int sh = 0+2*4+1*16+3*64; + __m256i p0 = __lasx_xvshuf4i_w(ab0, 0xD8); + __m256i p1 = __lasx_xvshuf4i_w(ab1, 0xD8); + __m256i pl = __lasx_xvpermi_q(p0, p1, 0x02); + __m256i ph = __lasx_xvpermi_q(p0, p1, 0x13); + __m256i a0 = __lasx_xvilvl_d(ph, pl); + __m256i b0 = __lasx_xvilvh_d(ph, pl); + a = v_uint32x8(a0); + b = v_uint32x8(b0); +} + +inline void v_load_deinterleave( const uint64* ptr, v_uint64x4& a, v_uint64x4& b ) +{ + __m256i ab0 = __lasx_xvld(ptr, 0); + __m256i ab1 = __lasx_xvld(ptr + 4, 0); + + __m256i pl = __lasx_xvpermi_q(ab0, ab1, 0x02); + __m256i ph = __lasx_xvpermi_q(ab0, ab1, 0x13); + __m256i a0 = __lasx_xvilvl_d(ph, pl); + __m256i b0 = __lasx_xvilvh_d(ph, pl); + a = v_uint64x4(a0); + b = v_uint64x4(b0); +} + +inline void v_load_deinterleave( const uchar* ptr, v_uint8x32& a, v_uint8x32& b, v_uint8x32& c ) +{ + __m256i bgr0 = __lasx_xvld(ptr, 0); + __m256i bgr1 = __lasx_xvld(ptr + 32, 0); + __m256i bgr2 = __lasx_xvld(ptr + 64, 0); + + __m256i s02_low = __lasx_xvpermi_q(bgr0, bgr2, 0x02); + __m256i s02_high = __lasx_xvpermi_q(bgr0, bgr2, 0x13); + + const __m256i m0 = _v256_setr_b(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, + 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0); + const __m256i m1 = _v256_setr_b(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, + -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1); + + __m256i b0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(s02_low, s02_high, m0), bgr1, m1); + __m256i g0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(s02_high, s02_low, m1), bgr1, m0); + __m256i r0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(bgr1, s02_low, m0), s02_high, m1); + + const __m256i + sh_b = _v256_setr_b(0, 3, 6, 9, 12, 15, 2, 5, 8, 11, 14, 1, 4, 7, 10, 13, + 0, 3, 6, 9, 12, 15, 2, 5, 8, 11, 14, 1, 4, 7, 10, 13), + sh_g = _v256_setr_b(1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, 5, 8, 11, 14, + 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, 5, 8, 11, 14), + sh_r = _v256_setr_b(2, 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, + 2, 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15); + b0 = __lasx_xvshuf_b(b0, b0, sh_b); + g0 = __lasx_xvshuf_b(g0, g0, sh_g); + r0 = __lasx_xvshuf_b(r0, r0, sh_r); + + a = v_uint8x32(b0); + b = v_uint8x32(g0); + c = v_uint8x32(r0); +} + +inline void v_load_deinterleave( const ushort* ptr, v_uint16x16& a, v_uint16x16& b, v_uint16x16& c ) +{ + __m256i bgr0 = __lasx_xvld(ptr, 0); + __m256i bgr1 = __lasx_xvld(ptr + 16, 0); + __m256i bgr2 = __lasx_xvld(ptr + 32, 0); + + __m256i s02_low = __lasx_xvpermi_q(bgr0, bgr2, 0x02); + __m256i s02_high = __lasx_xvpermi_q(bgr0, bgr2, 0x13); + + const __m256i m0 = _v256_setr_b(0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, + 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0); + const __m256i m1 = _v256_setr_b(0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, + -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0); + __m256i b0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(s02_low, s02_high, m0), bgr1, m1); + __m256i g0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(bgr1, s02_low, m0), s02_high, m1); + __m256i r0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(s02_high, s02_low, m1), bgr1, m0); + const __m256i sh_b = _v256_setr_b(0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, 4, 5, 10, 11, + 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, 4, 5, 10, 11); + const __m256i sh_g = _v256_setr_b(2, 3, 8, 9, 14, 15, 4, 5, 10, 11, 0, 1, 6, 7, 12, 13, + 2, 3, 8, 9, 14, 15, 4, 5, 10, 11, 0, 1, 6, 7, 12, 13); + const __m256i sh_r = _v256_setr_b(4, 5, 10, 11, 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, + 4, 5, 10, 11, 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15); + b0 = __lasx_xvshuf_b(b0, b0, sh_b); + g0 = __lasx_xvshuf_b(g0, g0, sh_g); + r0 = __lasx_xvshuf_b(r0, r0, sh_r); + + a = v_uint16x16(b0); + b = v_uint16x16(g0); + c = v_uint16x16(r0); +} + +inline void v_load_deinterleave( const unsigned* ptr, v_uint32x8& a, v_uint32x8& b, v_uint32x8& c ) +{ + __m256i bgr0 = __lasx_xvld(ptr, 0); + __m256i bgr1 = __lasx_xvld(ptr + 8, 0); + __m256i bgr2 = __lasx_xvld(ptr + 16, 0); + + __m256i s02_low = __lasx_xvpermi_q(bgr0, bgr2, 0x02); + __m256i s02_high = __lasx_xvpermi_q(bgr0, bgr2, 0x13); + + __m256i m24 = _v256_set_w(0, 0, -1, 0, 0, -1, 0, 0); + __m256i m92 = _v256_set_w(-1, 0, 0, -1, 0, 0, -1, 0); + __m256i b0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(s02_low, s02_high, m24), bgr1, m92); + __m256i g0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(s02_high, s02_low, m92), bgr1, m24); + __m256i r0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(bgr1, s02_low, m24), s02_high, m92); + + b0 = __lasx_xvshuf4i_w(b0, 0x6c); + g0 = __lasx_xvshuf4i_w(g0, 0xb1); + r0 = __lasx_xvshuf4i_w(r0, 0xc6); + + a = v_uint32x8(b0); + b = v_uint32x8(g0); + c = v_uint32x8(r0); +} + +inline void v_load_deinterleave( const uint64* ptr, v_uint64x4& a, v_uint64x4& b, v_uint64x4& c ) +{ + __m256i bgr0 = __lasx_xvld(ptr, 0); + __m256i bgr1 = __lasx_xvld(ptr + 4, 0); + __m256i bgr2 = __lasx_xvld(ptr + 8, 0); + + __m256i s01 = __lasx_xvpermi_q(bgr0, bgr1, 0x12); // get bgr0 low 128 and bgr1 high 128 + __m256i s12 = __lasx_xvpermi_q(bgr1, bgr2, 0x12); + __m256i s20r = __lasx_xvpermi_d(__lasx_xvpermi_q(bgr2, bgr0, 0x12), 0x1b); + __m256i b0 = __lasx_xvilvl_d(s20r, s01); + __m256i g0 = _v256_alignr_b(s12, s01, 8); + __m256i r0 = __lasx_xvilvh_d(s12, s20r); + + a = v_uint64x4(b0); + b = v_uint64x4(g0); + c = v_uint64x4(r0); +} + +inline void v_load_deinterleave( const uchar* ptr, v_uint8x32& a, v_uint8x32& b, v_uint8x32& c, v_uint8x32& d ) +{ + __m256i bgr0 = __lasx_xvld(ptr, 0); + __m256i bgr1 = __lasx_xvld(ptr + 32, 0); + __m256i bgr2 = __lasx_xvld(ptr + 64, 0); + __m256i bgr3 = __lasx_xvld(ptr + 96, 0); + const __m256i sh = _v256_setr_b(0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, + 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15); + + __m256i p0 = __lasx_xvshuf_b(bgr0, bgr0, sh); + __m256i p1 = __lasx_xvshuf_b(bgr1, bgr1, sh); + __m256i p2 = __lasx_xvshuf_b(bgr2, bgr2, sh); + __m256i p3 = __lasx_xvshuf_b(bgr3, bgr3, sh); + + __m256i p01l = __lasx_xvilvl_w(p1, p0); + __m256i p01h = __lasx_xvilvh_w(p1, p0); + __m256i p23l = __lasx_xvilvl_w(p3, p2); + __m256i p23h = __lasx_xvilvh_w(p3, p2); + + __m256i pll = __lasx_xvpermi_q(p01l, p23l, 0x02); + __m256i plh = __lasx_xvpermi_q(p01l, p23l, 0x13); + __m256i phl = __lasx_xvpermi_q(p01h, p23h, 0x02); + __m256i phh = __lasx_xvpermi_q(p01h, p23h, 0x13); + + __m256i b0 = __lasx_xvilvl_w(plh, pll); + __m256i g0 = __lasx_xvilvh_w(plh, pll); + __m256i r0 = __lasx_xvilvl_w(phh, phl); + __m256i a0 = __lasx_xvilvh_w(phh, phl); + + a = v_uint8x32(b0); + b = v_uint8x32(g0); + c = v_uint8x32(r0); + d = v_uint8x32(a0); +} + +inline void v_load_deinterleave( const ushort* ptr, v_uint16x16& a, v_uint16x16& b, v_uint16x16& c, v_uint16x16& d ) +{ + __m256i bgr0 = __lasx_xvld(ptr, 0); + __m256i bgr1 = __lasx_xvld(ptr + 16, 0); + __m256i bgr2 = __lasx_xvld(ptr + 32, 0); + __m256i bgr3 = __lasx_xvld(ptr + 48, 0); + const __m256i sh = _v256_setr_b(0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15, + 0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15); + __m256i p0 = __lasx_xvshuf_b(bgr0, bgr0, sh); + __m256i p1 = __lasx_xvshuf_b(bgr1, bgr1, sh); + __m256i p2 = __lasx_xvshuf_b(bgr2, bgr2, sh); + __m256i p3 = __lasx_xvshuf_b(bgr3, bgr3, sh); + + __m256i p01l = __lasx_xvilvl_w(p1, p0); + __m256i p01h = __lasx_xvilvh_w(p1, p0); + __m256i p23l = __lasx_xvilvl_w(p3, p2); + __m256i p23h = __lasx_xvilvh_w(p3, p2); + + __m256i pll = __lasx_xvpermi_q(p01l, p23l, 0x02); + __m256i plh = __lasx_xvpermi_q(p01l, p23l, 0x13); + __m256i phl = __lasx_xvpermi_q(p01h, p23h, 0x02); + __m256i phh = __lasx_xvpermi_q(p01h, p23h, 0x13); + + __m256i b0 = __lasx_xvilvl_w(plh, pll); + __m256i g0 = __lasx_xvilvh_w(plh, pll); + __m256i r0 = __lasx_xvilvl_w(phh, phl); + __m256i a0 = __lasx_xvilvh_w(phh, phl); + + a = v_uint16x16(b0); + b = v_uint16x16(g0); + c = v_uint16x16(r0); + d = v_uint16x16(a0); +} + +inline void v_load_deinterleave( const unsigned* ptr, v_uint32x8& a, v_uint32x8& b, v_uint32x8& c, v_uint32x8& d ) +{ + __m256i p0 = __lasx_xvld(ptr, 0); + __m256i p1 = __lasx_xvld(ptr + 8, 0); + __m256i p2 = __lasx_xvld(ptr + 16, 0); + __m256i p3 = __lasx_xvld(ptr + 24, 0); + + __m256i p01l = __lasx_xvilvl_w(p1, p0); + __m256i p01h = __lasx_xvilvh_w(p1, p0); + __m256i p23l = __lasx_xvilvl_w(p3, p2); + __m256i p23h = __lasx_xvilvh_w(p3, p2); + + __m256i pll = __lasx_xvpermi_q(p01l, p23l, 0x02); + __m256i plh = __lasx_xvpermi_q(p01l, p23l, 0x13); + __m256i phl = __lasx_xvpermi_q(p01h, p23h, 0x02); + __m256i phh = __lasx_xvpermi_q(p01h, p23h, 0x13); + + __m256i b0 = __lasx_xvilvl_w(plh, pll); + __m256i g0 = __lasx_xvilvh_w(plh, pll); + __m256i r0 = __lasx_xvilvl_w(phh, phl); + __m256i a0 = __lasx_xvilvh_w(phh, phl); + + a = v_uint32x8(b0); + b = v_uint32x8(g0); + c = v_uint32x8(r0); + d = v_uint32x8(a0); +} + +inline void v_load_deinterleave( const uint64* ptr, v_uint64x4& a, v_uint64x4& b, v_uint64x4& c, v_uint64x4& d ) +{ + __m256i bgra0 = __lasx_xvld(ptr, 0); + __m256i bgra1 = __lasx_xvld(ptr + 4, 0); + __m256i bgra2 = __lasx_xvld(ptr + 8, 0); + __m256i bgra3 = __lasx_xvld(ptr + 12, 0); + + __m256i l02 = __lasx_xvpermi_q(bgra0, bgra2, 0x02); + __m256i h02 = __lasx_xvpermi_q(bgra0, bgra2, 0x13); + __m256i l13 = __lasx_xvpermi_q(bgra1, bgra3, 0x02); + __m256i h13 = __lasx_xvpermi_q(bgra1, bgra3, 0x13); + + __m256i b0 = __lasx_xvilvl_d(l13, l02); + __m256i g0 = __lasx_xvilvh_d(l13, l02); + __m256i r0 = __lasx_xvilvl_d(h13, h02); + __m256i a0 = __lasx_xvilvh_d(h13, h02); + + a = v_uint64x4(b0); + b = v_uint64x4(g0); + c = v_uint64x4(r0); + d = v_uint64x4(a0); +} + +///////////////////////////// store interleave ///////////////////////////////////// + +inline void v_store_interleave( uchar* ptr, const v_uint8x32& x, const v_uint8x32& y, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i xy_l = __lasx_xvilvl_b(y.val, x.val); + __m256i xy_h = __lasx_xvilvh_b(y.val, x.val); + + __m256i xy0 = __lasx_xvpermi_q(xy_h, xy_l, 0 + 2*16); + __m256i xy1 = __lasx_xvpermi_q(xy_h, xy_l, 1 + 3*16); + + __lasx_xvst(xy0, (__m256i*)ptr, 0); + __lasx_xvst(xy1, (__m256i*)ptr, 32*1); +} + +inline void v_store_interleave( ushort* ptr, const v_uint16x16& x, const v_uint16x16& y, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i xy_l = __lasx_xvilvl_h(y.val, x.val); + __m256i xy_h = __lasx_xvilvh_h(y.val, x.val); + + __m256i xy0 = __lasx_xvpermi_q(xy_h, xy_l, 0 + 2*16); + __m256i xy1 = __lasx_xvpermi_q(xy_h, xy_l, 1 + 3*16); + + __lasx_xvst(xy0, (__m256i*)ptr, 0); + __lasx_xvst(xy1, (__m256i*)ptr, 16*2); +} + +inline void v_store_interleave( unsigned* ptr, const v_uint32x8& x, const v_uint32x8& y, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i xy_l = __lasx_xvilvl_w(y.val, x.val); + __m256i xy_h = __lasx_xvilvh_w(y.val, x.val); + + __m256i xy0 = __lasx_xvpermi_q(xy_h, xy_l, 0 + 2*16); + __m256i xy1 = __lasx_xvpermi_q(xy_h, xy_l, 1 + 3*16); + + __lasx_xvst(xy0, (__m256i*)ptr, 0); + __lasx_xvst(xy1, (__m256i*)ptr, 8*4); +} + +inline void v_store_interleave( uint64* ptr, const v_uint64x4& x, const v_uint64x4& y, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i xy_l = __lasx_xvilvl_d(y.val, x.val); + __m256i xy_h = __lasx_xvilvh_d(y.val, x.val); + + __m256i xy0 = __lasx_xvpermi_q(xy_h, xy_l, 0 + 2*16); + __m256i xy1 = __lasx_xvpermi_q(xy_h, xy_l, 1 + 3*16); + + __lasx_xvst(xy0, (__m256i*)ptr, 0); + __lasx_xvst(xy1, (__m256i*)ptr, 4*8); +} + +inline void v_store_interleave( uchar* ptr, const v_uint8x32& a, const v_uint8x32& b, const v_uint8x32& c, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + const __m256i sh_b = _v256_setr_b( + 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5, + 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5); + const __m256i sh_g = _v256_setr_b( + 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, + 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10); + const __m256i sh_r = _v256_setr_b( + 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, + 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15); + + __m256i b0 = __lasx_xvshuf_b(a.val, a.val, sh_b); + __m256i g0 = __lasx_xvshuf_b(b.val, b.val, sh_g); + __m256i r0 = __lasx_xvshuf_b(c.val, c.val, sh_r); + + const __m256i m0 = _v256_setr_b(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, + 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0); + const __m256i m1 = _v256_setr_b(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, + 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0); + + __m256i p0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(b0, g0, m0), r0, m1); + __m256i p1 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(g0, r0, m0), b0, m1); + __m256i p2 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(r0, b0, m0), g0, m1); + + __m256i bgr0 = __lasx_xvpermi_q(p1, p0, 0 + 2*16); + __m256i bgr1 = __lasx_xvpermi_q(p0, p2, 0 + 3*16); + __m256i bgr2 = __lasx_xvpermi_q(p2, p1, 1 + 3*16); + + __lasx_xvst(bgr0, (__m256i*)ptr, 0); + __lasx_xvst(bgr1, (__m256i*)ptr, 32); + __lasx_xvst(bgr2, (__m256i*)ptr, 64); +} + +inline void v_store_interleave( ushort* ptr, const v_uint16x16& a, const v_uint16x16& b, const v_uint16x16& c, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + const __m256i sh_b = _v256_setr_b( + 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, 4, 5, 10, 11, + 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, 4, 5, 10, 11); + const __m256i sh_g = _v256_setr_b( + 10, 11, 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, 4, 5, + 10, 11, 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, 4, 5); + const __m256i sh_r = _v256_setr_b( + 4, 5, 10, 11, 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15, + 4, 5, 10, 11, 0, 1, 6, 7, 12, 13, 2, 3, 8, 9, 14, 15); + + __m256i b0 = __lasx_xvshuf_b(a.val, a.val, sh_b); + __m256i g0 = __lasx_xvshuf_b(b.val, b.val, sh_g); + __m256i r0 = __lasx_xvshuf_b(c.val, c.val, sh_r); + + const __m256i m0 = _v256_setr_b(0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, + 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0); + const __m256i m1 = _v256_setr_b(0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, + -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0); + + __m256i p0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(b0, g0, m0), r0, m1); + __m256i p1 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(g0, r0, m0), b0, m1); + __m256i p2 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(r0, b0, m0), g0, m1); + + __m256i bgr0 = __lasx_xvpermi_q(p2, p0, 0 + 2*16); + __m256i bgr2 = __lasx_xvpermi_q(p2, p0, 1 + 3*16); + + __lasx_xvst(bgr0, (__m256i*)ptr, 0); + __lasx_xvst(p1, (__m256i*)ptr, 16*2); + __lasx_xvst(bgr2, (__m256i*)ptr, 32*2); +} + +inline void v_store_interleave( unsigned* ptr, const v_uint32x8& a, const v_uint32x8& b, const v_uint32x8& c, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i b0 = __lasx_xvshuf4i_w(a.val, 0x6c); + __m256i g0 = __lasx_xvshuf4i_w(b.val, 0xb1); + __m256i r0 = __lasx_xvshuf4i_w(c.val, 0xc6); + + __m256i bitmask_1 = _v256_set_w(-1, 0, 0, -1, 0, 0, -1, 0); + __m256i bitmask_2 = _v256_set_w(0, 0, -1, 0, 0, -1, 0, 0); + + __m256i p0 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(b0, g0, bitmask_1), r0, bitmask_2); + __m256i p1 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(g0, r0, bitmask_1), b0, bitmask_2); + __m256i p2 = __lasx_xvbitsel_v(__lasx_xvbitsel_v(r0, b0, bitmask_1), g0, bitmask_2); + + __m256i bgr0 = __lasx_xvpermi_q(p1, p0, 0 + 2*16); + __m256i bgr2 = __lasx_xvpermi_q(p1, p0, 1 + 3*16); + + __lasx_xvst(bgr0, (__m256i*)ptr, 0); + __lasx_xvst(p2, (__m256i*)ptr, 8*4); + __lasx_xvst(bgr2, (__m256i*)ptr, 16*4); +} + +inline void v_store_interleave( uint64* ptr, const v_uint64x4& a, const v_uint64x4& b, const v_uint64x4& c, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i s01 = __lasx_xvilvl_d(b.val, a.val); + __m256i s12 = __lasx_xvilvh_d(c.val, b.val); + __m256i s20 = __lasx_xvpermi_w(a.val, c.val, 0xe4); + + __m256i bgr0 = __lasx_xvpermi_q(s20, s01, 0 + 2*16); + __m256i bgr1 = __lasx_xvpermi_q(s01, s12, 0x30); + __m256i bgr2 = __lasx_xvpermi_q(s12, s20, 1 + 3*16); + + __lasx_xvst(bgr0, (__m256i*)ptr, 0); + __lasx_xvst(bgr1, (__m256i*)ptr, 4*8); + __lasx_xvst(bgr2, (__m256i*)ptr, 8*8); +} + +inline void v_store_interleave( uchar* ptr, const v_uint8x32& a, const v_uint8x32& b, + const v_uint8x32& c, const v_uint8x32& d, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i bg0 = __lasx_xvilvl_b(b.val, a.val); + __m256i bg1 = __lasx_xvilvh_b(b.val, a.val); + __m256i ra0 = __lasx_xvilvl_b(d.val, c.val); + __m256i ra1 = __lasx_xvilvh_b(d.val, c.val); + + __m256i bgra0_ = __lasx_xvilvl_h(ra0, bg0); + __m256i bgra1_ = __lasx_xvilvh_h(ra0, bg0); + __m256i bgra2_ = __lasx_xvilvl_h(ra1, bg1); + __m256i bgra3_ = __lasx_xvilvh_h(ra1, bg1); + + __m256i bgra0 = __lasx_xvpermi_q(bgra1_, bgra0_, 0 + 2*16); + __m256i bgra2 = __lasx_xvpermi_q(bgra1_, bgra0_, 1 + 3*16); + __m256i bgra1 = __lasx_xvpermi_q(bgra3_, bgra2_, 0 + 2*16); + __m256i bgra3 = __lasx_xvpermi_q(bgra3_, bgra2_, 1 + 3*16); + + __lasx_xvst(bgra0, (__m256i*)ptr, 0); + __lasx_xvst(bgra1, (__m256i*)ptr, 32); + __lasx_xvst(bgra2, (__m256i*)ptr, 64); + __lasx_xvst(bgra3, (__m256i*)ptr, 96); +} + +inline void v_store_interleave( ushort* ptr, const v_uint16x16& a, const v_uint16x16& b, + const v_uint16x16& c, const v_uint16x16& d, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i bg0 = __lasx_xvilvl_h(b.val, a.val); + __m256i bg1 = __lasx_xvilvh_h(b.val, a.val); + __m256i ra0 = __lasx_xvilvl_h(d.val, c.val); + __m256i ra1 = __lasx_xvilvh_h(d.val, c.val); + + __m256i bgra0_ = __lasx_xvilvl_w(ra0, bg0); + __m256i bgra1_ = __lasx_xvilvh_w(ra0, bg0); + __m256i bgra2_ = __lasx_xvilvl_w(ra1, bg1); + __m256i bgra3_ = __lasx_xvilvh_w(ra1, bg1); + + __m256i bgra0 = __lasx_xvpermi_q(bgra1_, bgra0_, 0 + 2*16); + __m256i bgra2 = __lasx_xvpermi_q(bgra1_, bgra0_, 1 + 3*16); + __m256i bgra1 = __lasx_xvpermi_q(bgra3_, bgra2_, 0 + 2*16); + __m256i bgra3 = __lasx_xvpermi_q(bgra3_, bgra2_, 1 + 3*16); + + __lasx_xvst(bgra0, (__m256i*)ptr, 0); + __lasx_xvst(bgra1, (__m256i*)ptr, 16*2); + __lasx_xvst(bgra2, (__m256i*)ptr, 32*2); + __lasx_xvst(bgra3, (__m256i*)ptr, 48*2); +} + +inline void v_store_interleave( unsigned* ptr, const v_uint32x8& a, const v_uint32x8& b, + const v_uint32x8& c, const v_uint32x8& d, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i bg0 = __lasx_xvilvl_w(b.val, a.val); + __m256i bg1 = __lasx_xvilvh_w(b.val, a.val); + __m256i ra0 = __lasx_xvilvl_w(d.val, c.val); + __m256i ra1 = __lasx_xvilvh_w(d.val, c.val); + + __m256i bgra0_ = __lasx_xvilvl_d(ra0, bg0); + __m256i bgra1_ = __lasx_xvilvh_d(ra0, bg0); + __m256i bgra2_ = __lasx_xvilvl_d(ra1, bg1); + __m256i bgra3_ = __lasx_xvilvh_d(ra1, bg1); + + __m256i bgra0 = __lasx_xvpermi_q(bgra1_, bgra0_, 0 + 2*16); + __m256i bgra2 = __lasx_xvpermi_q(bgra1_, bgra0_, 1 + 3*16); + __m256i bgra1 = __lasx_xvpermi_q(bgra3_, bgra2_, 0 + 2*16); + __m256i bgra3 = __lasx_xvpermi_q(bgra3_, bgra2_, 1 + 3*16); + + __lasx_xvst(bgra0, (__m256i*)ptr, 0); + __lasx_xvst(bgra1, (__m256i*)ptr, 8*4); + __lasx_xvst(bgra2, (__m256i*)ptr, 16*4); + __lasx_xvst(bgra3, (__m256i*)ptr, 24*4); +} + +inline void v_store_interleave( uint64* ptr, const v_uint64x4& a, const v_uint64x4& b, + const v_uint64x4& c, const v_uint64x4& d, + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) +{ + __m256i bg0 = __lasx_xvilvl_d(b.val, a.val); + __m256i bg1 = __lasx_xvilvh_d(b.val, a.val); + __m256i ra0 = __lasx_xvilvl_d(d.val, c.val); + __m256i ra1 = __lasx_xvilvh_d(d.val, c.val); + + __m256i bgra0 = __lasx_xvpermi_q(ra0, bg0, 0 + 2*16); + __m256i bgra1 = __lasx_xvpermi_q(ra1, bg1, 0 + 2*16); + __m256i bgra2 = __lasx_xvpermi_q(ra0, bg0, 1 + 3*16); + __m256i bgra3 = __lasx_xvpermi_q(ra1, bg1, 1 + 3*16); + + __lasx_xvst(bgra0, (__m256i*)ptr, 0); + __lasx_xvst(bgra1, (__m256i*)(ptr), 4*8); + __lasx_xvst(bgra2, (__m256i*)(ptr), 8*8); + __lasx_xvst(bgra3, (__m256i*)(ptr), 12*8); +} + + +#define OPENCV_HAL_IMPL_LASX_LOADSTORE_INTERLEAVE(_Tpvec0, _Tp0, suffix0, _Tpvec1, _Tp1, suffix1) \ +inline void v_load_deinterleave( const _Tp0* ptr, _Tpvec0& a0, _Tpvec0& b0 ) \ +{ \ + _Tpvec1 a1, b1; \ + v_load_deinterleave((const _Tp1*)ptr, a1, b1); \ + a0 = v_reinterpret_as_##suffix0(a1); \ + b0 = v_reinterpret_as_##suffix0(b1); \ +} \ +inline void v_load_deinterleave( const _Tp0* ptr, _Tpvec0& a0, _Tpvec0& b0, _Tpvec0& c0 ) \ +{ \ + _Tpvec1 a1, b1, c1; \ + v_load_deinterleave((const _Tp1*)ptr, a1, b1, c1); \ + a0 = v_reinterpret_as_##suffix0(a1); \ + b0 = v_reinterpret_as_##suffix0(b1); \ + c0 = v_reinterpret_as_##suffix0(c1); \ +} \ +inline void v_load_deinterleave( const _Tp0* ptr, _Tpvec0& a0, _Tpvec0& b0, _Tpvec0& c0, _Tpvec0& d0 ) \ +{ \ + _Tpvec1 a1, b1, c1, d1; \ + v_load_deinterleave((const _Tp1*)ptr, a1, b1, c1, d1); \ + a0 = v_reinterpret_as_##suffix0(a1); \ + b0 = v_reinterpret_as_##suffix0(b1); \ + c0 = v_reinterpret_as_##suffix0(c1); \ + d0 = v_reinterpret_as_##suffix0(d1); \ +} \ +inline void v_store_interleave( _Tp0* ptr, const _Tpvec0& a0, const _Tpvec0& b0, \ + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) \ +{ \ + _Tpvec1 a1 = v_reinterpret_as_##suffix1(a0); \ + _Tpvec1 b1 = v_reinterpret_as_##suffix1(b0); \ + v_store_interleave((_Tp1*)ptr, a1, b1/*, mode*/); \ +} \ +inline void v_store_interleave( _Tp0* ptr, const _Tpvec0& a0, const _Tpvec0& b0, const _Tpvec0& c0, \ + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) \ +{ \ + _Tpvec1 a1 = v_reinterpret_as_##suffix1(a0); \ + _Tpvec1 b1 = v_reinterpret_as_##suffix1(b0); \ + _Tpvec1 c1 = v_reinterpret_as_##suffix1(c0); \ + v_store_interleave((_Tp1*)ptr, a1, b1, c1/*, mode*/); \ +} \ +inline void v_store_interleave( _Tp0* ptr, const _Tpvec0& a0, const _Tpvec0& b0, \ + const _Tpvec0& c0, const _Tpvec0& d0, \ + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) \ +{ \ + _Tpvec1 a1 = v_reinterpret_as_##suffix1(a0); \ + _Tpvec1 b1 = v_reinterpret_as_##suffix1(b0); \ + _Tpvec1 c1 = v_reinterpret_as_##suffix1(c0); \ + _Tpvec1 d1 = v_reinterpret_as_##suffix1(d0); \ + v_store_interleave((_Tp1*)ptr, a1, b1, c1, d1/*, mode*/); \ +} + +OPENCV_HAL_IMPL_LASX_LOADSTORE_INTERLEAVE(v_int8x32, schar, s8, v_uint8x32, uchar, u8) +OPENCV_HAL_IMPL_LASX_LOADSTORE_INTERLEAVE(v_int16x16, short, s16, v_uint16x16, ushort, u16) +OPENCV_HAL_IMPL_LASX_LOADSTORE_INTERLEAVE(v_int32x8, int, s32, v_uint32x8, unsigned, u32) +OPENCV_HAL_IMPL_LASX_LOADSTORE_INTERLEAVE(v_float32x8, float, f32, v_uint32x8, unsigned, u32) +OPENCV_HAL_IMPL_LASX_LOADSTORE_INTERLEAVE(v_int64x4, int64, s64, v_uint64x4, uint64, u64) +OPENCV_HAL_IMPL_LASX_LOADSTORE_INTERLEAVE(v_float64x4, double, f64, v_uint64x4, uint64, u64) + +// +// FP16 +// + +inline v_float32x8 v256_load_expand(const float16_t* ptr) +{ +#if CV_FP16 + //1-load128, 2-permi, 3-cvt + return v_float32x8(__lasx_xvfcvtl_s_h(__lasx_xvpermi_d(__lsx_vld((const __m128i*)ptr, 0), 0x10))); +#else + float CV_DECL_ALIGNED(32) buf[8]; + for (int i = 0; i < 8; i++) + buf[i] = (float)ptr[i]; + return v256_load_aligned(buf); +#endif +} + +inline void v_pack_store(float16_t* ptr, const v_float32x8& a) +{ +#if CV_FP16 + __m256i ah = __lasx_xvfcvt_h_s(a.val, a.val); + __lsx_vst((_m128i)ah, ptr, 0); +#else + float CV_DECL_ALIGNED(32) buf[8]; + v_store_aligned(buf, a); + for (int i = 0; i < 8; i++) + ptr[i] = float16_t(buf[i]); +#endif +} + +// +// end of FP16 +// + +inline void v256_cleanup() {} + +CV_CPU_OPTIMIZATION_HAL_NAMESPACE_END + +//! @endcond + +} // cv:: + +#endif // OPENCV_HAL_INTRIN_LASX_HPP diff --git a/modules/core/include/opencv2/core/hal/intrin_rvv.hpp b/modules/core/include/opencv2/core/hal/intrin_rvv.hpp index a592976827..dca54a27d1 100644 --- a/modules/core/include/opencv2/core/hal/intrin_rvv.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_rvv.hpp @@ -2646,14 +2646,14 @@ inline v_##_Tpvec v_interleave_quads(const v_##_Tpvec& vec) \ v_store(ptrvec, vec); \ for (int i = 0; i < v_##_Tpvec::nlanes/8; i++) \ { \ - ptr[8*i ] = ptrvec[4*i ]; \ - ptr[8*i+1] = ptrvec[4*i+4]; \ - ptr[8*i+2] = ptrvec[4*i+1]; \ - ptr[8*i+3] = ptrvec[4*i+5]; \ - ptr[8*i+4] = ptrvec[4*i+2]; \ - ptr[8*i+5] = ptrvec[4*i+6]; \ - ptr[8*i+6] = ptrvec[4*i+3]; \ - ptr[8*i+7] = ptrvec[4*i+7]; \ + ptr[8*i ] = ptrvec[8*i ]; \ + ptr[8*i+1] = ptrvec[8*i+4]; \ + ptr[8*i+2] = ptrvec[8*i+1]; \ + ptr[8*i+3] = ptrvec[8*i+5]; \ + ptr[8*i+4] = ptrvec[8*i+2]; \ + ptr[8*i+5] = ptrvec[8*i+6]; \ + ptr[8*i+6] = ptrvec[8*i+3]; \ + ptr[8*i+7] = ptrvec[8*i+7]; \ } \ return v_load(ptr); \ } @@ -2753,7 +2753,7 @@ inline int v_signmask(const _Tpvec& a) \ { \ uint8_t ans[16] = {0};\ vsm(ans, vmslt(a, 0, vl), vl);\ - return reinterpret_cast(ans)[0];\ + return reinterpret_cast(ans)[0] & ((1 << (vl)) - 1);\ } OPENCV_HAL_IMPL_RVV_SIGNMASK_OP(v_int8x16, 8, 16) @@ -2810,7 +2810,7 @@ OPENCV_HAL_IMPL_RVV_SCAN_FORWOARD_OP(v_float64x2, double, f64) #ifndef __clang__ inline v_int8x16 v_pack_triplets(const v_int8x16& vec) { - uint64 ptr[2] = {0x0908060504020100, 0xFFFFFFFF0E0D0C0A}; + uint64 ptr[2] = {0x0908060504020100, 0xFFFFFF0F0E0D0C0A}; return v_int8x16((vint8m1_t)vrgather_vv_u8m1((vuint8m1_t)vint8m1_t(vec), (vuint8m1_t)vle64_v_u64m1(ptr, 2), 16)); } inline v_uint8x16 v_pack_triplets(const v_uint8x16& vec) @@ -2820,7 +2820,7 @@ inline v_uint8x16 v_pack_triplets(const v_uint8x16& vec) inline v_int16x8 v_pack_triplets(const v_int16x8& vec) { - uint64 ptr[2] = {0x0908060504020100, 0xFFFFFFFF0E0D0C0A}; + uint64 ptr[2] = {0x0908050403020100, 0xFFFF0F0E0D0C0B0A}; return v_int16x8((vint16m1_t)vrgather_vv_u8m1((vuint8m1_t)vint16m1_t(vec), (vuint8m1_t)vle64_v_u64m1(ptr, 2), 16)); } inline v_uint16x8 v_pack_triplets(const v_uint16x8& vec) @@ -2836,7 +2836,7 @@ inline v_float32x4 v_pack_triplets(const v_float32x4& vec) { return vec; } inline v_int8x16 v_pack_triplets(const v_int8x16& vec) { - uint64 ptr[2] = {0x0908060504020100, 0xFFFFFFFF0E0D0C0A}; + uint64 ptr[2] = {0x0908060504020100, 0xFFFFFF0F0E0D0C0A}; return v_int8x16(vreinterpret_i8m1(vrgather_vv_u8m1(v_reinterpret_as_u8(vec), vreinterpret_u8m1(vle64_v_u64m1(ptr, 2)), 16))); } inline v_uint8x16 v_pack_triplets(const v_uint8x16& vec) @@ -2846,7 +2846,7 @@ inline v_uint8x16 v_pack_triplets(const v_uint8x16& vec) inline v_int16x8 v_pack_triplets(const v_int16x8& vec) { - uint64 ptr[2] = {0x0908060504020100, 0xFFFFFFFF0E0D0C0A}; + uint64 ptr[2] = {0x0908050403020100, 0xFFFF0F0E0D0C0B0A}; return v_int16x8(v_reinterpret_as_s16(v_uint8x16(vrgather_vv_u8m1(v_reinterpret_as_u8(vec), vreinterpret_u8m1(vle64_v_u64m1(ptr, 2)), 16)))); } inline v_uint16x8 v_pack_triplets(const v_uint16x8& vec) diff --git a/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp b/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp index 30c7524699..96069b92b5 100644 --- a/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_rvv_scalable.hpp @@ -1,3 +1,9 @@ +// 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. + +// The original implementation is contributed by HAN Liutong. +// Copyright (C) 2022, Institute of Software, Chinese Academy of Sciences. #ifndef OPENCV_HAL_INTRIN_RVV_SCALABLE_HPP #define OPENCV_HAL_INTRIN_RVV_SCALABLE_HPP @@ -5,6 +11,14 @@ #include #include #include +#include + +#if defined(__GNUC__) && !defined(__clang__) +// FIXIT: eliminate massive warnigs from templates +// GCC from 'rvv-next': riscv64-unknown-linux-gnu-g++ (g42df3464463) 12.0.1 20220505 (prerelease) +// doesn't work: #pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-attributes" +#endif #ifndef CV_RVV_MAX_VLEN #define CV_RVV_MAX_VLEN 1024 @@ -284,6 +298,64 @@ inline v_float64 v_reinterpret_as_f64(const v_float32& v) \ } #endif +//////////// Extract ////////////// + +#define OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(_Tpvec, _Tp, suffix, vl) \ +template \ +inline _Tpvec v_extract(const _Tpvec& a, const _Tpvec& b, int i = s) \ +{ \ + return vslideup(vslidedown(v_setzero_##suffix(), a, i, vl), b, VTraits<_Tpvec>::vlanes() - i, vl); \ +} \ +template inline _Tp v_extract_n(_Tpvec v, int i = s) \ +{ \ + return vmv_x(vslidedown(v_setzero_##suffix(), v, i, vl)); \ +} + + +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_uint8, uchar, u8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_int8, schar, s8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_uint16, ushort, u16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_int16, short, s16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_uint32, unsigned int, u32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_int32, int, s32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_uint64, uint64, u64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT_INTEGER(v_int64, int64, s64, VTraits::vlanes()) + +#define OPENCV_HAL_IMPL_RVV_EXTRACT_FP(_Tpvec, _Tp, suffix, vl) \ +template \ +inline _Tpvec v_extract(const _Tpvec& a, const _Tpvec& b, int i = s) \ +{ \ + return vslideup(vslidedown(v_setzero_##suffix(), a, i, vl), b, VTraits<_Tpvec>::vlanes() - i, vl); \ +} \ +template inline _Tp v_extract_n(_Tpvec v, int i = s) \ +{ \ + return vfmv_f(vslidedown(v_setzero_##suffix(), v, i, vl)); \ +} + +OPENCV_HAL_IMPL_RVV_EXTRACT_FP(v_float32, float, f32, VTraits::vlanes()) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_EXTRACT_FP(v_float64, double, f64, VTraits::vlanes()) +#endif + +#define OPENCV_HAL_IMPL_RVV_EXTRACT(_Tpvec, _Tp, vl) \ +inline _Tp v_extract_highest(_Tpvec v) \ +{ \ + return v_extract_n(v, vl-1); \ +} + +OPENCV_HAL_IMPL_RVV_EXTRACT(v_uint8, uchar, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_int8, schar, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_uint16, ushort, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_int16, short, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_uint32, unsigned int, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_int32, int, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_uint64, uint64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_int64, int64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_EXTRACT(v_float32, float, VTraits::vlanes()) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_EXTRACT(v_float64, double, VTraits::vlanes()) +#endif + ////////////// Load/Store ////////////// #define OPENCV_HAL_IMPL_RVV_LOADSTORE_OP(_Tpvec, _nTpvec, _Tp, hvl, vl, width, suffix, vmv) \ @@ -387,6 +459,9 @@ OPENCV_HAL_IMPL_RVV_LUT(v_int16, short, m2) OPENCV_HAL_IMPL_RVV_LUT(v_int32, int, m1) OPENCV_HAL_IMPL_RVV_LUT(v_int64, int64_t, mf2) OPENCV_HAL_IMPL_RVV_LUT(v_float32, float, m1) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_LUT(v_float64, double, mf2) +#endif inline v_uint8 v_lut(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut((schar*)tab, idx)); } inline v_uint8 v_lut_pairs(const uchar* tab, const int* idx) { return v_reinterpret_as_u8(v_lut_pairs((schar*)tab, idx)); } @@ -401,6 +476,296 @@ inline v_uint64 v_lut(const uint64* tab, const int* idx) { return v_reinterpret_ inline v_uint64 v_lut_pairs(const uint64* tab, const int* idx) { return v_reinterpret_as_u64(v_lut_pairs((const int64_t *)tab, idx)); } inline v_uint64 v_lut_quads(const uint64* tab, const int* idx) { return v_reinterpret_as_u64(v_lut_quads((const int64_t*)tab, idx)); } +////////////// Pack boolean //////////////////// +inline v_uint8 v_pack_b(const v_uint16& a, const v_uint16& b) +{ + return vnsrl(vset(vlmul_ext_u16m2(a),1,b), 0, VTraits::vlanes()); +} + +inline v_uint8 v_pack_b(const v_uint32& a, const v_uint32& b, + const v_uint32& c, const v_uint32& d) +{ + + return vnsrl(vnsrl(vset(vset(vset(vlmul_ext_u32m4(a),1,b),2,c),3,d), 0, VTraits::vlanes()), 0, VTraits::vlanes()); +} + +inline v_uint8 v_pack_b(const v_uint64& a, const v_uint64& b, const v_uint64& c, + const v_uint64& d, const v_uint64& e, const v_uint64& f, + const v_uint64& g, const v_uint64& h) +{ + return vnsrl(vnsrl(vnsrl( + vset(vset(vset(vset(vset(vset(vset(vlmul_ext_u64m8(a), + 1,b),2,c),3,d),4,e),5,f),6,g),7,h), + 0, VTraits::vlanes()), 0, VTraits::vlanes()), 0, VTraits::vlanes()); +} + +////////////// Arithmetics ////////////// +#define OPENCV_HAL_IMPL_RVV_BIN_OP(_Tpvec, ocv_intrin, rvv_intrin) \ +inline _Tpvec v_##ocv_intrin(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return rvv_intrin(a, b, VTraits<_Tpvec>::vlanes()); \ +} + +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint8, add, vsaddu) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint8, sub, vssubu) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int8, add, vsadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int8, sub, vssub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint16, add, vsaddu) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint16, sub, vssubu) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int16, add, vsadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int16, sub, vssub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint32, add, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint32, sub, vsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint32, mul, vmul) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int32, add, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int32, sub, vsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int32, mul, vmul) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float32, add, vfadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float32, sub, vfsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float32, mul, vfmul) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float32, div, vfdiv) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint64, add, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint64, sub, vsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int64, add, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int64, sub, vsub) + +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float64, add, vfadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float64, sub, vfsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float64, mul, vfmul) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_float64, div, vfdiv) +#endif + +#define OPENCV_HAL_IMPL_RVV_BIN_MADD(_Tpvec, rvv_add) \ +template \ +inline _Tpvec v_add(const _Tpvec& f1, const _Tpvec& f2, const Args&... vf) { \ + return v_add(rvv_add(f1, f2, VTraits<_Tpvec>::vlanes()), vf...); \ +} +#define OPENCV_HAL_IMPL_RVV_BIN_MMUL(_Tpvec, rvv_mul) \ +template \ +inline _Tpvec v_mul(const _Tpvec& f1, const _Tpvec& f2, const Args&... vf) { \ + return v_mul(rvv_mul(f1, f2, VTraits<_Tpvec>::vlanes()), vf...); \ +} +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_uint8, vsaddu) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_int8, vsadd) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_uint16, vsaddu) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_int16, vsadd) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_uint32, vadd) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_int32, vadd) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_float32, vfadd) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_uint64, vadd) +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_int64, vadd) + +OPENCV_HAL_IMPL_RVV_BIN_MMUL(v_uint32, vmul) +OPENCV_HAL_IMPL_RVV_BIN_MMUL(v_int32, vmul) +OPENCV_HAL_IMPL_RVV_BIN_MMUL(v_float32, vfmul) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_BIN_MADD(v_float64, vfadd) +OPENCV_HAL_IMPL_RVV_BIN_MMUL(v_float64, vfmul) +#endif + +#define OPENCV_HAL_IMPL_RVV_MUL_EXPAND(_Tpvec, _Tpwvec, _TpwvecM2, suffix, wmul) \ +inline void v_mul_expand(const _Tpvec& a, const _Tpvec& b, _Tpwvec& c, _Tpwvec& d) \ +{ \ + _TpwvecM2 temp = wmul(a, b, VTraits<_Tpvec>::vlanes()); \ + c = vget_##suffix##m1(temp, 0); \ + d = vget_##suffix##m1(temp, 1); \ +} + +OPENCV_HAL_IMPL_RVV_MUL_EXPAND(v_uint8, v_uint16, vuint16m2_t, u16, vwmulu) +OPENCV_HAL_IMPL_RVV_MUL_EXPAND(v_int8, v_int16, vint16m2_t, i16, vwmul) +OPENCV_HAL_IMPL_RVV_MUL_EXPAND(v_uint16, v_uint32, vuint32m2_t, u32, vwmulu) +OPENCV_HAL_IMPL_RVV_MUL_EXPAND(v_int16, v_int32, vint32m2_t, i32, vwmul) +OPENCV_HAL_IMPL_RVV_MUL_EXPAND(v_uint32, v_uint64, vuint64m2_t, u64, vwmulu) + +inline v_int16 v_mul_hi(const v_int16& a, const v_int16& b) +{ + return vmulh(a, b, VTraits::vlanes()); +} +inline v_uint16 v_mul_hi(const v_uint16& a, const v_uint16& b) +{ + return vmulhu(a, b, VTraits::vlanes()); +} + +////////////// Arithmetics (wrap)////////////// +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint8, add_wrap, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int8, add_wrap, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint16, add_wrap, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int16, add_wrap, vadd) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint8, sub_wrap, vsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int8, sub_wrap, vsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint16, sub_wrap, vsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int16, sub_wrap, vsub) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint8, mul_wrap, vmul) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int8, mul_wrap, vmul) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_uint16, mul_wrap, vmul) +OPENCV_HAL_IMPL_RVV_BIN_OP(v_int16, mul_wrap, vmul) + +//////// Saturating Multiply //////// +#define OPENCV_HAL_IMPL_RVV_MUL_SAT(_Tpvec, _clip, _wmul) \ +inline _Tpvec v_mul(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return _clip(_wmul(a, b, VTraits<_Tpvec>::vlanes()), 0, VTraits<_Tpvec>::vlanes()); \ +} \ +template \ +inline _Tpvec v_mul(const _Tpvec& a1, const _Tpvec& a2, const Args&... va) { \ + return v_mul(_clip(_wmul(a1, a2, VTraits<_Tpvec>::vlanes()), 0, VTraits<_Tpvec>::vlanes()), va...); \ +} + +OPENCV_HAL_IMPL_RVV_MUL_SAT(v_uint8, vnclipu, vwmulu) +OPENCV_HAL_IMPL_RVV_MUL_SAT(v_int8, vnclip, vwmul) +OPENCV_HAL_IMPL_RVV_MUL_SAT(v_uint16, vnclipu, vwmulu) +OPENCV_HAL_IMPL_RVV_MUL_SAT(v_int16, vnclip, vwmul) + +////////////// Bitwise logic ////////////// + +#define OPENCV_HAL_IMPL_RVV_LOGIC_OP(_Tpvec, vl) \ +inline _Tpvec v_and(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vand(a, b, vl); \ +} \ +inline _Tpvec v_or(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vor(a, b, vl); \ +} \ +inline _Tpvec v_xor(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vxor(a, b, vl); \ +} \ +inline _Tpvec v_not (const _Tpvec& a) \ +{ \ + return vnot(a, vl); \ +} + +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_uint8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_int8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_uint16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_int16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_uint32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_int32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_uint64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_LOGIC_OP(v_int64, VTraits::vlanes()) + +#define OPENCV_HAL_IMPL_RVV_FLT_BIT_OP(intrin) \ +inline v_float32 intrin (const v_float32& a, const v_float32& b) \ +{ \ + return vreinterpret_f32m1(intrin(vreinterpret_i32m1(a), vreinterpret_i32m1(b))); \ +} +OPENCV_HAL_IMPL_RVV_FLT_BIT_OP(v_and) +OPENCV_HAL_IMPL_RVV_FLT_BIT_OP(v_or) +OPENCV_HAL_IMPL_RVV_FLT_BIT_OP(v_xor) + +inline v_float32 v_not (const v_float32& a) \ +{ \ + return vreinterpret_f32m1(v_not(vreinterpret_i32m1(a))); \ +} + +#if CV_SIMD_SCALABLE_64F +#define OPENCV_HAL_IMPL_RVV_FLT64_BIT_OP(intrin) \ +inline v_float64 intrin (const v_float64& a, const v_float64& b) \ +{ \ + return vreinterpret_f64m1(intrin(vreinterpret_i64m1(a), vreinterpret_i64m1(b))); \ +} +OPENCV_HAL_IMPL_RVV_FLT64_BIT_OP(v_and) +OPENCV_HAL_IMPL_RVV_FLT64_BIT_OP(v_or) +OPENCV_HAL_IMPL_RVV_FLT64_BIT_OP(v_xor) + +inline v_float64 v_not (const v_float64& a) \ +{ \ + return vreinterpret_f64m1(v_not(vreinterpret_i64m1(a))); \ +} +#endif + + +////////////// Bitwise shifts ////////////// + +#define OPENCV_HAL_IMPL_RVV_UNSIGNED_SHIFT_OP(_Tpvec, vl) \ +template inline _Tpvec v_shl(const _Tpvec& a) \ +{ \ + return _Tpvec(vsll(a, uint8_t(n), vl)); \ +} \ +template inline _Tpvec v_shr(const _Tpvec& a) \ +{ \ + return _Tpvec(vsrl(a, uint8_t(n), vl)); \ +} + +#define OPENCV_HAL_IMPL_RVV_SIGNED_SHIFT_OP(_Tpvec, vl) \ +template inline _Tpvec v_shl(const _Tpvec& a) \ +{ \ + return _Tpvec(vsll(a, uint8_t(n), vl)); \ +} \ +template inline _Tpvec v_shr(const _Tpvec& a) \ +{ \ + return _Tpvec(vsra(a, uint8_t(n), vl)); \ +} + +OPENCV_HAL_IMPL_RVV_UNSIGNED_SHIFT_OP(v_uint16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_UNSIGNED_SHIFT_OP(v_uint32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_UNSIGNED_SHIFT_OP(v_uint64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SIGNED_SHIFT_OP(v_int16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SIGNED_SHIFT_OP(v_int32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SIGNED_SHIFT_OP(v_int64, VTraits::vlanes()) + +////////////// Comparison ////////////// +#define OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, op, intrin, suffix, vl) \ +inline _Tpvec v_##op(const _Tpvec& a, const _Tpvec& b) \ +{ \ + uint64_t ones = -1; \ + return vmerge(intrin(a, b, vl), vmv_v_x_##suffix##m1(0, vl), ones, vl); \ +} + +#define OPENCV_HAL_IMPL_RVV_FLOAT_CMP_OP(_Tpvec, op, intrin, suffix, vl) \ +inline _Tpvec v_##op (const _Tpvec& a, const _Tpvec& b) \ +{ \ + union { uint64 u; double d; } ones; ones.u = -1; \ + return _Tpvec(vfmerge(intrin(a, b, vl), vfmv_v_f_##suffix##m1(0, vl), ones.d, vl)); \ +} //TODO + +#define OPENCV_HAL_IMPL_RVV_UNSIGNED_CMP(_Tpvec, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, eq, vmseq, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, ne, vmsne, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, lt, vmsltu, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, gt, vmsgtu, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, le, vmsleu, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, ge, vmsgeu, suffix, vl) + +#define OPENCV_HAL_IMPL_RVV_SIGNED_CMP(_Tpvec, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, eq, vmseq, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, ne, vmsne, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, lt, vmslt, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, gt, vmsgt, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, le, vmsle, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_INT_CMP_OP(_Tpvec, ge, vmsge, suffix, vl) + +#define OPENCV_HAL_IMPL_RVV_FLOAT_CMP(_Tpvec, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_FLOAT_CMP_OP(_Tpvec, eq, vmfeq, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_FLOAT_CMP_OP(_Tpvec, ne, vmfne, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_FLOAT_CMP_OP(_Tpvec, lt, vmflt, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_FLOAT_CMP_OP(_Tpvec, gt, vmfgt, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_FLOAT_CMP_OP(_Tpvec, le, vmfle, suffix, vl) \ +OPENCV_HAL_IMPL_RVV_FLOAT_CMP_OP(_Tpvec, ge, vmfge, suffix, vl) + + +OPENCV_HAL_IMPL_RVV_UNSIGNED_CMP(v_uint8, u8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_UNSIGNED_CMP(v_uint16, u16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_UNSIGNED_CMP(v_uint32, u32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_UNSIGNED_CMP(v_uint64, u64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SIGNED_CMP(v_int8, i8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SIGNED_CMP(v_int16, i16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SIGNED_CMP(v_int32, i32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SIGNED_CMP(v_int64, i64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_FLOAT_CMP(v_float32, f32, VTraits::vlanes()) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_FLOAT_CMP(v_float64, f64, VTraits::vlanes()) +#endif + +inline v_float32 v_not_nan(const v_float32& a) +{ return v_eq(a, a); } + +#if CV_SIMD_SCALABLE_64F +inline v_float64 v_not_nan(const v_float64& a) +{ return v_eq(a, a); } +#endif ////////////// Min/Max ////////////// @@ -424,15 +789,559 @@ OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_int32, v_min, vmin, VTraits::vlanes()) OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_int32, v_max, vmax, VTraits::vlanes()) OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_float32, v_min, vfmin, VTraits::vlanes()) OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_float32, v_max, vfmax, VTraits::vlanes()) -OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_uint64, v_min, vminu, VTraits::vlanes()) -OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_uint64, v_max, vmaxu, VTraits::vlanes()) -OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_int64, v_min, vmin, VTraits::vlanes()) -OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_int64, v_max, vmax, VTraits::vlanes()) #if CV_SIMD_SCALABLE_64F OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_float64, v_min, vfmin, VTraits::vlanes()) OPENCV_HAL_IMPL_RVV_BIN_FUNC(v_float64, v_max, vfmax, VTraits::vlanes()) #endif +////////////// Transpose4x4 ////////////// +#define OPENCV_HAL_IMPL_RVV_ZIP4(_Tpvec, _wTpvec, suffix, convert2u, convert) \ +inline void v_zip4(const _Tpvec& a0, const _Tpvec& a1, _Tpvec& b0, _Tpvec& b1) { \ + int vl = 4; \ + _wTpvec temp = vreinterpret_##suffix##m2(convert2u( \ + vor(vzext_vf2(convert(a0), vl), \ + vreinterpret_u64m2(vslide1up(vreinterpret_u32m2(vzext_vf2(convert(a1), vl)), 0, vl*2)), \ + vl))); \ + b0 = vget_##suffix##m1(temp, 0); \ + b1 = vget_##suffix##m1(vrgather(temp, vadd(vid_v_u32m2(vl), 4, vl)/*{4,5,6,7} */, vl) ,0); \ +} + +OPENCV_HAL_IMPL_RVV_ZIP4(v_uint32, vuint32m2_t, u32, OPENCV_HAL_NOP, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_RVV_ZIP4(v_int32, vint32m2_t, i32, vreinterpret_u32m2, vreinterpret_u32m1) +OPENCV_HAL_IMPL_RVV_ZIP4(v_float32, vfloat32m2_t, f32, vreinterpret_u32m2, vreinterpret_u32m1) + +#if 0 +// this is v_zip4 and v_tranpose4x4 for scalable VLEN, costs more instruction than current 128-bit only version. +inline void v_zip4(const v_float32& a0, const v_float32& a1, v_float32& b0, v_float32& b1) { + vuint64m1_t vid1 = vid_v_u64m1(VTraits::vlanes()); + vuint16m1_t t1 = vreinterpret_u16m1(vid1); + vuint16m1_t t2 = vslide1up(t1, 0, VTraits::vlanes()); + vuint16m1_t t3 = vslide1up(t2, 0, VTraits::vlanes()); + vuint16m1_t t4 = vslide1up(t3, 0, VTraits::vlanes()); + t1 = vor( + vor(t1, t2, VTraits::vlanes()), + vor(t3, t4, VTraits::vlanes()), + VTraits::vlanes() + ); + vuint32m2_t vidx0 = vwmulu(t1, 4, VTraits::vlanes()); + vidx0 = vadd(vidx0, vid_v_u32m2(VTraits::vlanes()), VTraits::vlanes()); + vuint32m2_t vidx1 = vadd(vidx0, 4, VTraits::vlanes()); + vfloat32m2_t temp = vreinterpret_f32m2(vreinterpret_u32m2( + vor(vzext_vf2(vreinterpret_u32m1(a0), VTraits::vlanes()), + vreinterpret_u64m2(vslide1up(vreinterpret_u32m2(vzext_vf2(vreinterpret_u32m1(a1), VTraits::vlanes())), 0, VTraits::vlanes()*2)), + VTraits::vlanes()))); + b0 = vlmul_trunc_f32m1(vrgather(temp, vidx0, VTraits::vlanes())); + b1 = vlmul_trunc_f32m1(vrgather(temp, vidx1, VTraits::vlanes())); +} + +inline void v_transpose4x4(const v_float32& a0, const v_float32& a1, const v_float32& a2, const v_float32& a3,\ + v_float32& b0, v_float32& b1, v_float32& b2, v_float32& b3) { \ + vuint64m2_t vid1 = vid_v_u64m2(VTraits::vlanes()); + vuint16m2_t t1 = vreinterpret_u16m2(vid1); + vuint16m2_t t2 = vslide1up(t1, 0, VTraits::vlanes()); + vuint16m2_t t3 = vslide1up(t2, 0, VTraits::vlanes()); + vuint16m2_t t4 = vslide1up(t3, 0, VTraits::vlanes()); + t1 = vor( + vor(t1, t2, VTraits::vlanes()), + vor(t3, t4, VTraits::vlanes()), + VTraits::vlanes() + ); + vuint16m2_t vidx0 = vmul(t1, 12, VTraits::vlanes()); + vidx0 = vadd(vidx0, vid_v_u16m2(VTraits::vlanes()), VTraits::vlanes()); + vuint16m2_t vidx1 = vadd(vidx0, 4, VTraits::vlanes()); + vuint16m2_t vidx2 = vadd(vidx0, 8, VTraits::vlanes()); + vuint16m2_t vidx3 = vadd(vidx0, 12, VTraits::vlanes()); + vuint32m2_t tempA = vreinterpret_u32m2( \ + vor(vzext_vf2(vreinterpret_u32m1(a0), VTraits::vlanes()), \ + vreinterpret_u64m2(vslide1up(vreinterpret_u32m2(vzext_vf2(vreinterpret_u32m1(a2), VTraits::vlanes())), 0, VTraits::vlanes())), \ + VTraits::vlanes())); \ + vuint32m2_t tempB = vreinterpret_u32m2( \ + vor(vzext_vf2(vreinterpret_u32m1(a1), VTraits::vlanes()), \ + vreinterpret_u64m2(vslide1up(vreinterpret_u32m2(vzext_vf2(vreinterpret_u32m1(a3), VTraits::vlanes())), 0, VTraits::vlanes())), \ + VTraits::vlanes())); \ + vfloat32m4_t temp = vreinterpret_f32m4(vreinterpret_u32m4( \ + vor(vzext_vf2(tempA, VTraits::vlanes()), \ + vreinterpret_u64m4(vslide1up(vreinterpret_u32m4(vzext_vf2(tempB, VTraits::vlanes())), 0, VTraits::vlanes())), \ + VTraits::vlanes()))); \ + b0 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx0, VTraits::vlanes())); + b1 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx1, VTraits::vlanes())); + b2 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx2, VTraits::vlanes())); + b3 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx3, VTraits::vlanes())); +} +#endif + +#define OPENCV_HAL_IMPL_RVV_TRANSPOSE4x4(_Tpvec, suffix) \ +inline void v_transpose4x4(const _Tpvec& a0, const _Tpvec& a1, const _Tpvec& a2, const _Tpvec& a3, _Tpvec& b0, _Tpvec& b1, _Tpvec& b2, _Tpvec& b3) { \ + _Tpvec t0,t1,t2,t3; \ + v_zip4(a0, a2, t0, t2); \ + v_zip4(a1, a3, t1, t3); \ + v_zip4(t0, t1, b0, b1); \ + v_zip4(t2, t3, b2, b3); \ +} + +OPENCV_HAL_IMPL_RVV_TRANSPOSE4x4(v_uint32, u32) +OPENCV_HAL_IMPL_RVV_TRANSPOSE4x4(v_int32, i32) +OPENCV_HAL_IMPL_RVV_TRANSPOSE4x4(v_float32, f32) + +////////////// Reduce ////////////// + +#define OPENCV_HAL_IMPL_RVV_REDUCE_SUM(_Tpvec, _wTpvec, _nwTpvec, scalartype, wsuffix, vl, red) \ +inline scalartype v_reduce_sum(const _Tpvec& a) \ +{ \ + _nwTpvec zero = vmv_v_x_##wsuffix##m1(0, vl); \ + _nwTpvec res = vmv_v_x_##wsuffix##m1(0, vl); \ + res = v##red(res, a, zero, vl); \ + return (scalartype)v_get0(res); \ +} +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_uint8, v_uint16, vuint16m1_t, unsigned, u16, VTraits::vlanes(), wredsumu) +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_int8, v_int16, vint16m1_t, int, i16, VTraits::vlanes(), wredsum) +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_uint16, v_uint32, vuint32m1_t, unsigned, u32, VTraits::vlanes(), wredsumu) +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_int16, v_int32, vint32m1_t, int, i32, VTraits::vlanes(), wredsum) +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_uint32, v_uint64, vuint64m1_t, unsigned, u64, VTraits::vlanes(), wredsumu) +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_int32, v_int64, vint64m1_t, int, i64, VTraits::vlanes(), wredsum) +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_uint64, v_uint64, vuint64m1_t, uint64, u64, VTraits::vlanes(), redsum) +OPENCV_HAL_IMPL_RVV_REDUCE_SUM(v_int64, v_int64, vint64m1_t, int64, i64, VTraits::vlanes(), redsum) + + +#define OPENCV_HAL_IMPL_RVV_REDUCE_SUM_FP(_Tpvec, _wTpvec, _nwTpvec, scalartype, wsuffix, vl) \ +inline scalartype v_reduce_sum(const _Tpvec& a) \ +{ \ + _nwTpvec zero = vfmv_v_f_##wsuffix##m1(0, vl); \ + _nwTpvec res = vfmv_v_f_##wsuffix##m1(0, vl); \ + res = vfredosum(res, a, zero, vl); \ + return (scalartype)v_get0(res); \ +} +OPENCV_HAL_IMPL_RVV_REDUCE_SUM_FP(v_float32, v_float32, vfloat32m1_t, float, f32, VTraits::vlanes()) + +#define OPENCV_HAL_IMPL_RVV_REDUCE(_Tpvec, func, scalartype, suffix, vl, red) \ +inline scalartype v_reduce_##func(const _Tpvec& a) \ +{ \ + _Tpvec res = _Tpvec(v##red(a, a, a, vl)); \ + return (scalartype)v_get0(res); \ +} + +OPENCV_HAL_IMPL_RVV_REDUCE(v_uint8, min, uchar, u8, VTraits::vlanes(), redminu) +OPENCV_HAL_IMPL_RVV_REDUCE(v_int8, min, schar, i8, VTraits::vlanes(), redmin) +OPENCV_HAL_IMPL_RVV_REDUCE(v_uint16, min, ushort, u16, VTraits::vlanes(), redminu) +OPENCV_HAL_IMPL_RVV_REDUCE(v_int16, min, short, i16, VTraits::vlanes(), redmin) +OPENCV_HAL_IMPL_RVV_REDUCE(v_uint32, min, unsigned, u32, VTraits::vlanes(), redminu) +OPENCV_HAL_IMPL_RVV_REDUCE(v_int32, min, int, i32, VTraits::vlanes(), redmin) +OPENCV_HAL_IMPL_RVV_REDUCE(v_float32, min, float, f32, VTraits::vlanes(), fredmin) +OPENCV_HAL_IMPL_RVV_REDUCE(v_uint8, max, uchar, u8, VTraits::vlanes(), redmaxu) +OPENCV_HAL_IMPL_RVV_REDUCE(v_int8, max, schar, i8, VTraits::vlanes(), redmax) +OPENCV_HAL_IMPL_RVV_REDUCE(v_uint16, max, ushort, u16, VTraits::vlanes(), redmaxu) +OPENCV_HAL_IMPL_RVV_REDUCE(v_int16, max, short, i16, VTraits::vlanes(), redmax) +OPENCV_HAL_IMPL_RVV_REDUCE(v_uint32, max, unsigned, u32, VTraits::vlanes(), redmaxu) +OPENCV_HAL_IMPL_RVV_REDUCE(v_int32, max, int, i32, VTraits::vlanes(), redmax) +OPENCV_HAL_IMPL_RVV_REDUCE(v_float32, max, float, f32, VTraits::vlanes(), fredmax) + +inline v_float32 v_reduce_sum4(const v_float32& a, const v_float32& b, + const v_float32& c, const v_float32& d) +{ + // 0000 1111 2222 3333 .... + vuint64m2_t vid1 = vid_v_u64m2(VTraits::vlanes()); + vuint16m2_t t1 = vreinterpret_u16m2(vid1); + vuint16m2_t t2 = vslide1up(t1, 0, VTraits::vlanes()); + vuint16m2_t t3 = vslide1up(t2, 0, VTraits::vlanes()); + vuint16m2_t t4 = vslide1up(t3, 0, VTraits::vlanes()); + t1 = vor( + vor(t1, t2, VTraits::vlanes()), + vor(t3, t4, VTraits::vlanes()), + VTraits::vlanes() + ); + + // index for transpose4X4 + vuint16m2_t vidx0 = vmul(t1, 12, VTraits::vlanes()); + vidx0 = vadd(vidx0, vid_v_u16m2(VTraits::vlanes()), VTraits::vlanes()); + vuint16m2_t vidx1 = vadd(vidx0, 4, VTraits::vlanes()); + vuint16m2_t vidx2 = vadd(vidx0, 8, VTraits::vlanes()); + vuint16m2_t vidx3 = vadd(vidx0, 12, VTraits::vlanes()); + + // zip + vuint32m2_t tempA = vreinterpret_u32m2( \ + vor(vzext_vf2(vreinterpret_u32m1(a), VTraits::vlanes()), \ + vreinterpret_u64m2(vslide1up(vreinterpret_u32m2(vzext_vf2(vreinterpret_u32m1(c), VTraits::vlanes())), 0, VTraits::vlanes())), \ + VTraits::vlanes())); \ + vuint32m2_t tempB = vreinterpret_u32m2( \ + vor(vzext_vf2(vreinterpret_u32m1(b), VTraits::vlanes()), \ + vreinterpret_u64m2(vslide1up(vreinterpret_u32m2(vzext_vf2(vreinterpret_u32m1(d), VTraits::vlanes())), 0, VTraits::vlanes())), \ + VTraits::vlanes())); \ + vfloat32m4_t temp = vreinterpret_f32m4(vreinterpret_u32m4( \ + vor(vzext_vf2(tempA, VTraits::vlanes()), \ + vreinterpret_u64m4(vslide1up(vreinterpret_u32m4(vzext_vf2(tempB, VTraits::vlanes())), 0, VTraits::vlanes())), \ + VTraits::vlanes()))); + + // transpose + vfloat32m1_t b0 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx0, VTraits::vlanes())); + vfloat32m1_t b1 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx1, VTraits::vlanes())); + vfloat32m1_t b2 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx2, VTraits::vlanes())); + vfloat32m1_t b3 = vlmul_trunc_f32m1(vrgatherei16(temp, vidx3, VTraits::vlanes())); + + // vector add + v_float32 res = vfadd( + vfadd(b0, b1, VTraits::vlanes()), + vfadd(b2, b3, VTraits::vlanes()), + VTraits::vlanes() + ); + return res; +} + +////////////// Square-Root ////////////// + +inline v_float32 v_sqrt(const v_float32& x) +{ + return vfsqrt(x, VTraits::vlanes()); +} + +inline v_float32 v_invsqrt(const v_float32& x) +{ + v_float32 one = v_setall_f32(1.0f); + return v_div(one, v_sqrt(x)); +} + +#if CV_SIMD_SCALABLE_64F +inline v_float64 v_sqrt(const v_float64& x) +{ + return vfsqrt(x, VTraits::vlanes()); +} + +inline v_float64 v_invsqrt(const v_float64& x) +{ + v_float64 one = v_setall_f64(1.0f); + return v_div(one, v_sqrt(x)); +} +#endif + +inline v_float32 v_magnitude(const v_float32& a, const v_float32& b) +{ + v_float32 x = vfmacc(vfmul(a, a, VTraits::vlanes()), b, b, VTraits::vlanes()); + return v_sqrt(x); +} + +inline v_float32 v_sqr_magnitude(const v_float32& a, const v_float32& b) +{ + return v_float32(vfmacc(vfmul(a, a, VTraits::vlanes()), b, b, VTraits::vlanes())); +} + +#if CV_SIMD_SCALABLE_64F +inline v_float64 v_magnitude(const v_float64& a, const v_float64& b) +{ + v_float64 x = vfmacc(vfmul(a, a, VTraits::vlanes()), b, b, VTraits::vlanes()); + return v_sqrt(x); +} + +inline v_float64 v_sqr_magnitude(const v_float64& a, const v_float64& b) +{ + return vfmacc(vfmul(a, a, VTraits::vlanes()), b, b, VTraits::vlanes()); +} +#endif + +////////////// Multiply-Add ////////////// + +inline v_float32 v_fma(const v_float32& a, const v_float32& b, const v_float32& c) +{ + return vfmacc(c, a, b, VTraits::vlanes()); +} +inline v_int32 v_fma(const v_int32& a, const v_int32& b, const v_int32& c) +{ + return vmacc(c, a, b, VTraits::vlanes()); +} + +inline v_float32 v_muladd(const v_float32& a, const v_float32& b, const v_float32& c) +{ + return v_fma(a, b, c); +} + +inline v_int32 v_muladd(const v_int32& a, const v_int32& b, const v_int32& c) +{ + return v_fma(a, b, c); +} + +#if CV_SIMD_SCALABLE_64F +inline v_float64 v_fma(const v_float64& a, const v_float64& b, const v_float64& c) +{ + return vfmacc_vv_f64m1(c, a, b, VTraits::vlanes()); +} + +inline v_float64 v_muladd(const v_float64& a, const v_float64& b, const v_float64& c) +{ + return v_fma(a, b, c); +} +#endif + +////////////// Check all/any ////////////// + +#define OPENCV_HAL_IMPL_RVV_CHECK_ALLANY(_Tpvec, vl) \ +inline bool v_check_all(const _Tpvec& a) \ +{ \ + return vcpop(vmslt(a, 0, vl), vl) == vl; \ +} \ +inline bool v_check_any(const _Tpvec& a) \ +{ \ + return vcpop(vmslt(a, 0, vl), vl) != 0; \ +} + +OPENCV_HAL_IMPL_RVV_CHECK_ALLANY(v_int8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_CHECK_ALLANY(v_int16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_CHECK_ALLANY(v_int32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_CHECK_ALLANY(v_int64, VTraits::vlanes()) + + +inline bool v_check_all(const v_uint8& a) +{ return v_check_all(v_reinterpret_as_s8(a)); } +inline bool v_check_any(const v_uint8& a) +{ return v_check_any(v_reinterpret_as_s8(a)); } + +inline bool v_check_all(const v_uint16& a) +{ return v_check_all(v_reinterpret_as_s16(a)); } +inline bool v_check_any(const v_uint16& a) +{ return v_check_any(v_reinterpret_as_s16(a)); } + +inline bool v_check_all(const v_uint32& a) +{ return v_check_all(v_reinterpret_as_s32(a)); } +inline bool v_check_any(const v_uint32& a) +{ return v_check_any(v_reinterpret_as_s32(a)); } + +inline bool v_check_all(const v_float32& a) +{ return v_check_all(v_reinterpret_as_s32(a)); } +inline bool v_check_any(const v_float32& a) +{ return v_check_any(v_reinterpret_as_s32(a)); } + +inline bool v_check_all(const v_uint64& a) +{ return v_check_all(v_reinterpret_as_s64(a)); } +inline bool v_check_any(const v_uint64& a) +{ return v_check_any(v_reinterpret_as_s64(a)); } + +#if CV_SIMD_SCALABLE_64F +inline bool v_check_all(const v_float64& a) +{ return v_check_all(v_reinterpret_as_s64(a)); } +inline bool v_check_any(const v_float64& a) +{ return v_check_any(v_reinterpret_as_s64(a)); } +#endif + +////////////// abs ////////////// + +#define OPENCV_HAL_IMPL_RVV_ABSDIFF(_Tpvec, abs) \ +inline _Tpvec v_##abs(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return v_sub(v_max(a, b), v_min(a, b)); \ +} + +OPENCV_HAL_IMPL_RVV_ABSDIFF(v_uint8, absdiff) +OPENCV_HAL_IMPL_RVV_ABSDIFF(v_uint16, absdiff) +OPENCV_HAL_IMPL_RVV_ABSDIFF(v_uint32, absdiff) +OPENCV_HAL_IMPL_RVV_ABSDIFF(v_float32, absdiff) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_ABSDIFF(v_float64, absdiff) +#endif +OPENCV_HAL_IMPL_RVV_ABSDIFF(v_int8, absdiffs) +OPENCV_HAL_IMPL_RVV_ABSDIFF(v_int16, absdiffs) + +#define OPENCV_HAL_IMPL_RVV_ABSDIFF_S(_Tpvec, _rTpvec, width) \ +inline _rTpvec v_absdiff(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vnclipu(vreinterpret_u##width##m2(vwsub_vv(v_max(a, b), v_min(a, b), VTraits<_Tpvec>::vlanes())), 0, VTraits<_Tpvec>::vlanes()); \ +} + +OPENCV_HAL_IMPL_RVV_ABSDIFF_S(v_int8, v_uint8, 16) +OPENCV_HAL_IMPL_RVV_ABSDIFF_S(v_int16, v_uint16, 32) +OPENCV_HAL_IMPL_RVV_ABSDIFF_S(v_int32, v_uint32, 64) + +#define OPENCV_HAL_IMPL_RVV_ABS(_Tprvec, _Tpvec, suffix) \ +inline _Tprvec v_abs(const _Tpvec& a) \ +{ \ + return v_absdiff(a, v_setzero_##suffix()); \ +} + +OPENCV_HAL_IMPL_RVV_ABS(v_uint8, v_int8, s8) +OPENCV_HAL_IMPL_RVV_ABS(v_uint16, v_int16, s16) +OPENCV_HAL_IMPL_RVV_ABS(v_uint32, v_int32, s32) +OPENCV_HAL_IMPL_RVV_ABS(v_float32, v_float32, f32) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_ABS(v_float64, v_float64, f64) +#endif + + +#define OPENCV_HAL_IMPL_RVV_REDUCE_SAD(_Tpvec, scalartype) \ +inline scalartype v_reduce_sad(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return v_reduce_sum(v_absdiff(a, b)); \ +} + +OPENCV_HAL_IMPL_RVV_REDUCE_SAD(v_uint8, unsigned) +OPENCV_HAL_IMPL_RVV_REDUCE_SAD(v_int8, unsigned) +OPENCV_HAL_IMPL_RVV_REDUCE_SAD(v_uint16, unsigned) +OPENCV_HAL_IMPL_RVV_REDUCE_SAD(v_int16, unsigned) +OPENCV_HAL_IMPL_RVV_REDUCE_SAD(v_uint32, unsigned) +OPENCV_HAL_IMPL_RVV_REDUCE_SAD(v_int32, unsigned) +OPENCV_HAL_IMPL_RVV_REDUCE_SAD(v_float32, float) + +////////////// Select ////////////// + +#define OPENCV_HAL_IMPL_RVV_SELECT(_Tpvec, vl) \ +inline _Tpvec v_select(const _Tpvec& mask, const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vmerge(vmsne(mask, 0, vl), b, a, vl); \ +} + +OPENCV_HAL_IMPL_RVV_SELECT(v_uint8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SELECT(v_uint16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SELECT(v_uint32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SELECT(v_int8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SELECT(v_int16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_SELECT(v_int32, VTraits::vlanes()) + +inline v_float32 v_select(const v_float32& mask, const v_float32& a, const v_float32& b) \ +{ \ + return vmerge(vmfne(mask, 0, VTraits::vlanes()), b, a, VTraits::vlanes()); \ +} + +#if CV_SIMD_SCALABLE_64F +inline v_float64 v_select(const v_float64& mask, const v_float64& a, const v_float64& b) \ +{ \ + return vmerge(vmfne(mask, 0, VTraits::vlanes()), b, a, VTraits::vlanes()); \ +} +#endif + +////////////// Rotate shift ////////////// + +#define OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(_Tpvec, suffix, vl) \ +template inline _Tpvec v_rotate_right(const _Tpvec& a) \ +{ \ + return vslidedown(vmv_v_x_##suffix##m1(0, vl), a, n, vl); \ +} \ +template inline _Tpvec v_rotate_left(const _Tpvec& a) \ +{ \ + return vslideup(vmv_v_x_##suffix##m1(0, vl), a, n, vl); \ +} \ +template<> inline _Tpvec v_rotate_left<0>(const _Tpvec& a) \ +{ return a; } \ +template inline _Tpvec v_rotate_right(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vslideup(vslidedown(vmv_v_x_##suffix##m1(0, vl), a, n, vl), b, VTraits<_Tpvec>::vlanes() - n, vl); \ +} \ +template inline _Tpvec v_rotate_left(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vslideup(vslidedown(vmv_v_x_##suffix##m1(0, vl), b, VTraits<_Tpvec>::vlanes() - n, vl), a, n, vl); \ +} \ +template<> inline _Tpvec v_rotate_left<0>(const _Tpvec& a, const _Tpvec& b) \ +{ CV_UNUSED(b); return a; } + +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_uint8, u8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_int8, i8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_uint16, u16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_int16, i16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_uint32, u32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_int32, i32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_uint64, u64, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_ROTATE_INTEGER(v_int64, i64, VTraits::vlanes()) + +#define OPENCV_HAL_IMPL_RVV_ROTATE_FP(_Tpvec, suffix, vl) \ +template inline _Tpvec v_rotate_right(const _Tpvec& a) \ +{ \ + return vslidedown(vfmv_v_f_##suffix##m1(0, vl), a, n, vl); \ +} \ +template inline _Tpvec v_rotate_left(const _Tpvec& a) \ +{ \ + return vslideup(vfmv_v_f_##suffix##m1(0, vl), a, n, vl); \ +} \ +template<> inline _Tpvec v_rotate_left<0>(const _Tpvec& a) \ +{ return a; } \ +template inline _Tpvec v_rotate_right(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vslideup(vslidedown(vfmv_v_f_##suffix##m1(0, vl), a, n, vl), b, VTraits<_Tpvec>::vlanes() - n, vl); \ +} \ +template inline _Tpvec v_rotate_left(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vslideup(vslidedown(vfmv_v_f_##suffix##m1(0, vl), b, VTraits<_Tpvec>::vlanes() - n, vl), a, n, vl); \ +} \ +template<> inline _Tpvec v_rotate_left<0>(const _Tpvec& a, const _Tpvec& b) \ +{ CV_UNUSED(b); return a; } + +OPENCV_HAL_IMPL_RVV_ROTATE_FP(v_float32, f32, VTraits::vlanes()) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_ROTATE_FP(v_float64, f64, VTraits::vlanes()) +#endif + +////////////// Convert to float ////////////// +inline v_float32 v_cvt_f32(const v_int32& a) +{ + return vfcvt_f_x_v_f32m1(a, VTraits::vlanes()); +} + +#if CV_SIMD_SCALABLE_64F +inline v_float32 v_cvt_f32(const v_float64& a) +{ + return vfncvt_f(vlmul_ext_f64m2(a), VTraits::vlanes()); +} + +inline v_float32 v_cvt_f32(const v_float64& a, const v_float64& b) +{ + return vfncvt_f(vset(vlmul_ext_f64m2(a),1,b), VTraits::vlanes()); +} + +inline v_float64 v_cvt_f64(const v_int32& a) +{ + return vget_f64m1(vfwcvt_f(a, VTraits::vlanes()), 0); +} + +inline v_float64 v_cvt_f64_high(const v_int32& a) +{ + return vget_f64m1(vfwcvt_f(a, VTraits::vlanes()), 1); +} + +inline v_float64 v_cvt_f64(const v_float32& a) +{ + return vget_f64m1(vfwcvt_f(a, VTraits::vlanes()), 0); +} + +inline v_float64 v_cvt_f64_high(const v_float32& a) +{ + return vget_f64m1(vfwcvt_f(a, VTraits::vlanes()), 1); +} + +inline v_float64 v_cvt_f64(const v_int64& a) +{ + return vfcvt_f(a, VTraits::vlanes()); +} +#endif + +//////////// Broadcast ////////////// + +#define OPENCV_HAL_IMPL_RVV_BROADCAST(_Tpvec, suffix) \ +template inline _Tpvec v_broadcast_element(_Tpvec v, int i = s) \ +{ \ + return v_setall_##suffix(v_extract_n(v, i)); \ +} \ +inline _Tpvec v_broadcast_highest(_Tpvec v) \ +{ \ + return v_setall_##suffix(v_extract_n(v, VTraits<_Tpvec>::vlanes()-1)); \ +} + +OPENCV_HAL_IMPL_RVV_BROADCAST(v_uint32, u32) +OPENCV_HAL_IMPL_RVV_BROADCAST(v_int32, s32) +OPENCV_HAL_IMPL_RVV_BROADCAST(v_float32, f32) + + +////////////// Reverse ////////////// +#define OPENCV_HAL_IMPL_RVV_REVERSE(_Tpvec, width) \ +inline _Tpvec v_reverse(const _Tpvec& a) \ +{ \ + vuint##width##m1_t vidx = vrsub(vid_v_u##width##m1(VTraits<_Tpvec>::vlanes()), VTraits<_Tpvec>::vlanes()-1, VTraits<_Tpvec>::vlanes()); \ + return vrgather(a, vidx, VTraits<_Tpvec>::vlanes()); \ +} +OPENCV_HAL_IMPL_RVV_REVERSE(v_uint8, 8) +OPENCV_HAL_IMPL_RVV_REVERSE(v_int8, 8) +OPENCV_HAL_IMPL_RVV_REVERSE(v_uint16, 16) +OPENCV_HAL_IMPL_RVV_REVERSE(v_int16, 16) +OPENCV_HAL_IMPL_RVV_REVERSE(v_uint32, 32) +OPENCV_HAL_IMPL_RVV_REVERSE(v_int32, 32) +OPENCV_HAL_IMPL_RVV_REVERSE(v_float32, 32) +OPENCV_HAL_IMPL_RVV_REVERSE(v_uint64, 64) +OPENCV_HAL_IMPL_RVV_REVERSE(v_int64, 64) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_REVERSE(v_float64, 64) +#endif //////////// Value reordering //////////// @@ -475,13 +1384,691 @@ inline v_int32 v_load_expand_q(const schar* ptr) return vwcvt_x(vwcvt_x(vle8_v_i8mf4(ptr, VTraits::vlanes()), VTraits::vlanes()), VTraits::vlanes()); } +#define OPENCV_HAL_IMPL_RVV_PACK(_Tpvec, _Tp, _wTpvec, hwidth, hsuffix, suffix, rshr, shr) \ +inline _Tpvec v_pack(const _wTpvec& a, const _wTpvec& b) \ +{ \ + return shr(vset(vlmul_ext_##suffix##m2(a), 1, b), 0, VTraits<_Tpvec>::vlanes()); \ +} \ +inline void v_pack_store(_Tp* ptr, const _wTpvec& a) \ +{ \ + vse##hwidth##_v_##hsuffix##mf2(ptr, shr(a, 0, VTraits<_Tpvec>::vlanes()), VTraits<_wTpvec>::vlanes()); \ +} \ +template inline \ +_Tpvec v_rshr_pack(const _wTpvec& a, const _wTpvec& b, int N = n) \ +{ \ + return rshr(vset(vlmul_ext_##suffix##m2(a), 1, b), N, VTraits<_Tpvec>::vlanes()); \ +} \ +template inline \ +void v_rshr_pack_store(_Tp* ptr, const _wTpvec& a, int N = n) \ +{ \ + vse##hwidth##_v_##hsuffix##mf2(ptr, rshr(a, N, VTraits<_Tpvec>::vlanes()), VTraits<_wTpvec>::vlanes()); \ +} + +OPENCV_HAL_IMPL_RVV_PACK(v_uint8, uchar, v_uint16, 8, u8, u16, vnclipu, vnclipu) +OPENCV_HAL_IMPL_RVV_PACK(v_int8, schar, v_int16, 8, i8, i16, vnclip, vnclip) +OPENCV_HAL_IMPL_RVV_PACK(v_uint16, ushort, v_uint32, 16, u16, u32, vnclipu, vnclipu) +OPENCV_HAL_IMPL_RVV_PACK(v_int16, short, v_int32, 16, i16, i32, vnclip, vnclip) +OPENCV_HAL_IMPL_RVV_PACK(v_uint32, unsigned, v_uint64, 32, u32, u64, vnclipu, vnsrl) +OPENCV_HAL_IMPL_RVV_PACK(v_int32, int, v_int64, 32, i32, i64, vnclip, vnsra) + +#define OPENCV_HAL_IMPL_RVV_PACK_U(_Tpvec, _Tp, _wTpvec, _wTp, hwidth, width, hsuffix, suffix, rshr, cast, hvl, vl) \ +inline _Tpvec v_pack_u(const _wTpvec& a, const _wTpvec& b) \ +{ \ + return vnclipu(cast(vmax(vset(vlmul_ext_##suffix##m2(a), 1, b), 0, vl)), 0, vl); \ +} \ +inline void v_pack_u_store(_Tp* ptr, const _wTpvec& a) \ +{ \ + vse##hwidth##_v_##hsuffix##mf2(ptr, vnclipu(vreinterpret_u##width##m1(vmax(a, 0, vl)), 0, vl), hvl); \ +} \ +template inline \ +_Tpvec v_rshr_pack_u(const _wTpvec& a, const _wTpvec& b, int n = N) \ +{ \ + return vnclipu(cast(vmax(vset(vlmul_ext_##suffix##m2(a), 1, b), 0, vl)), n, vl); \ +} \ +template inline \ +void v_rshr_pack_u_store(_Tp* ptr, const _wTpvec& a, int n = N) \ +{ \ + vse##hwidth##_v_##hsuffix##mf2(ptr, vnclipu(vreinterpret_u##width##m1(vmax(a, 0, vl)), n, vl), hvl); \ +} + +OPENCV_HAL_IMPL_RVV_PACK_U(v_uint8, uchar, v_int16, short, 8, 16, u8, i16, vnclipu_wx_u8m1, vreinterpret_v_i16m2_u16m2, VTraits::vlanes(), VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_PACK_U(v_uint16, ushort, v_int32, int, 16, 32, u16, i32, vnclipu_wx_u16m1, vreinterpret_v_i32m2_u32m2, VTraits::vlanes(), VTraits::vlanes()) + + +/* void v_zip(const _Tpvec& a0, const _Tpvec& a1, _Tpvec& b0, _Tpvec& b1) + a0 = {A1 A2 A3 A4} + a1 = {B1 B2 B3 B4} +--------------- + {A1 B1 A2 B2} and {A3 B3 A4 B4} +*/ + +#define OPENCV_HAL_IMPL_RVV_ZIP(_Tpvec, _wTpvec, suffix, width, width2, convert2um2, convert2um1) \ +inline void v_zip(const _Tpvec& a0, const _Tpvec& a1, _Tpvec& b0, _Tpvec& b1) { \ + _wTpvec temp = vreinterpret_##suffix##m2(convert2um2( \ + vor(vzext_vf2(convert2um1(a0), VTraits<_Tpvec>::vlanes()*2), \ + vreinterpret_u##width2##m2(vslide1up(vreinterpret_u##width##m2(vzext_vf2(convert2um1(a1), VTraits<_Tpvec>::vlanes()*2)), 0, VTraits<_Tpvec>::vlanes()*2)), \ + VTraits<_Tpvec>::vlanes()))); \ + b0 = vget_##suffix##m1(temp, 0); \ + b1 = vget_##suffix##m1(temp, 1); \ +} +OPENCV_HAL_IMPL_RVV_ZIP(v_uint8, vuint8m2_t, u8, 8, 16, OPENCV_HAL_NOP, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_RVV_ZIP(v_int8, vint8m2_t, i8, 8, 16, vreinterpret_u8m2, vreinterpret_u8m1) +OPENCV_HAL_IMPL_RVV_ZIP(v_uint16, vuint16m2_t, u16, 16, 32, OPENCV_HAL_NOP, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_RVV_ZIP(v_int16, vint16m2_t, i16, 16, 32, vreinterpret_u16m2, vreinterpret_u16m1) +OPENCV_HAL_IMPL_RVV_ZIP(v_uint32, vuint32m2_t, u32, 32, 64, OPENCV_HAL_NOP, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_RVV_ZIP(v_int32, vint32m2_t, i32, 32, 64, vreinterpret_u32m2, vreinterpret_u32m1) +OPENCV_HAL_IMPL_RVV_ZIP(v_float32, vfloat32m2_t, f32, 32, 64, vreinterpret_u32m2, vreinterpret_u32m1) + +#define OPENCV_HAL_IMPL_RVV_UNPACKS(_Tpvec, width) \ +inline _Tpvec v_combine_low(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vslideup(a, b, VTraits<_Tpvec>::vlanes()/2, VTraits<_Tpvec>::vlanes());\ +} \ +inline _Tpvec v_combine_high(const _Tpvec& a, const _Tpvec& b) \ +{ \ + return vslideup( \ + vslidedown(a, a, VTraits<_Tpvec>::vlanes()/2, VTraits<_Tpvec>::vlanes()), \ + vslidedown(b, b, VTraits<_Tpvec>::vlanes()/2, VTraits<_Tpvec>::vlanes()), \ + VTraits<_Tpvec>::vlanes()/2, \ + VTraits<_Tpvec>::vlanes()); \ +} \ +inline void v_recombine(const _Tpvec& a, const _Tpvec& b, _Tpvec& c, _Tpvec& d) \ +{ \ + c = v_combine_low(a, b); \ + d = v_combine_high(a, b); \ +} + +OPENCV_HAL_IMPL_RVV_UNPACKS(v_uint8, 8) +OPENCV_HAL_IMPL_RVV_UNPACKS(v_int8, 8) +OPENCV_HAL_IMPL_RVV_UNPACKS(v_uint16, 16) +OPENCV_HAL_IMPL_RVV_UNPACKS(v_int16, 16) +OPENCV_HAL_IMPL_RVV_UNPACKS(v_uint32, 32) +OPENCV_HAL_IMPL_RVV_UNPACKS(v_int32, 32) +OPENCV_HAL_IMPL_RVV_UNPACKS(v_float32, 32) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_UNPACKS(v_float64, 64) +#endif + +#define OPENCV_HAL_IMPL_RVV_INTERLEAVED(_Tpvec, _Tp, suffix, width, hwidth, vl) \ +inline void v_load_deinterleave(const _Tp* ptr, v_##_Tpvec& a, v_##_Tpvec& b) \ +{ \ + a = vlse##width##_v_##suffix##m1(ptr , sizeof(_Tp)*2, VTraits::vlanes()); \ + b = vlse##width##_v_##suffix##m1(ptr+1, sizeof(_Tp)*2, VTraits::vlanes()); \ +}\ +inline void v_load_deinterleave(const _Tp* ptr, v_##_Tpvec& a, v_##_Tpvec& b, v_##_Tpvec& c) \ +{ \ + a = vlse##width##_v_##suffix##m1(ptr , sizeof(_Tp)*3, VTraits::vlanes()); \ + b = vlse##width##_v_##suffix##m1(ptr+1, sizeof(_Tp)*3, VTraits::vlanes()); \ + c = vlse##width##_v_##suffix##m1(ptr+2, sizeof(_Tp)*3, VTraits::vlanes()); \ +} \ +inline void v_load_deinterleave(const _Tp* ptr, v_##_Tpvec& a, v_##_Tpvec& b, \ + v_##_Tpvec& c, v_##_Tpvec& d) \ +{ \ + \ + a = vlse##width##_v_##suffix##m1(ptr , sizeof(_Tp)*4, VTraits::vlanes()); \ + b = vlse##width##_v_##suffix##m1(ptr+1, sizeof(_Tp)*4, VTraits::vlanes()); \ + c = vlse##width##_v_##suffix##m1(ptr+2, sizeof(_Tp)*4, VTraits::vlanes()); \ + d = vlse##width##_v_##suffix##m1(ptr+3, sizeof(_Tp)*4, VTraits::vlanes()); \ +} \ +inline void v_store_interleave( _Tp* ptr, const v_##_Tpvec& a, const v_##_Tpvec& b, \ + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED) \ +{ \ + vsse##width(ptr, sizeof(_Tp)*2, a, VTraits::vlanes()); \ + vsse##width(ptr+1, sizeof(_Tp)*2, b, VTraits::vlanes()); \ +} \ +inline void v_store_interleave( _Tp* ptr, const v_##_Tpvec& a, const v_##_Tpvec& b, \ + const v_##_Tpvec& c, hal::StoreMode /*mode*/=hal::STORE_UNALIGNED) \ +{ \ + vsse##width(ptr, sizeof(_Tp)*3, a, VTraits::vlanes()); \ + vsse##width(ptr+1, sizeof(_Tp)*3, b, VTraits::vlanes()); \ + vsse##width(ptr+2, sizeof(_Tp)*3, c, VTraits::vlanes()); \ +} \ +inline void v_store_interleave( _Tp* ptr, const v_##_Tpvec& a, const v_##_Tpvec& b, \ + const v_##_Tpvec& c, const v_##_Tpvec& d, \ + hal::StoreMode /*mode*/=hal::STORE_UNALIGNED ) \ +{ \ + vsse##width(ptr, sizeof(_Tp)*4, a, VTraits::vlanes()); \ + vsse##width(ptr+1, sizeof(_Tp)*4, b, VTraits::vlanes()); \ + vsse##width(ptr+2, sizeof(_Tp)*4, c, VTraits::vlanes()); \ + vsse##width(ptr+3, sizeof(_Tp)*4, d, VTraits::vlanes()); \ +} + +OPENCV_HAL_IMPL_RVV_INTERLEAVED(uint8, uchar, u8, 8, 4, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(int8, schar, i8, 8, 4, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(uint16, ushort, u16, 16, 8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(int16, short, i16, 16, 8, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(uint32, unsigned, u32, 32, 16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(int32, int, i32, 32, 16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(float32, float, f32, 32, 16, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(uint64, uint64, u64, 64, 32, VTraits::vlanes()) +OPENCV_HAL_IMPL_RVV_INTERLEAVED(int64, int64, i64, 64, 32, VTraits::vlanes()) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_INTERLEAVED(float64, double, f64, 64, 32, VTraits::vlanes()) +#endif + +static uint64_t idx_interleave_pairs[] = { \ + 0x0705060403010200, 0x0f0d0e0c0b090a08, 0x1715161413111210, 0x1f1d1e1c1b191a18, \ + 0x2725262423212220, 0x2f2d2e2c2b292a28, 0x3735363433313230, 0x3f3d3e3c3b393a38, \ + 0x4745464443414240, 0x4f4d4e4c4b494a48, 0x5755565453515250, 0x5f5d5e5c5b595a58, \ + 0x6765666463616260, 0x6f6d6e6c6b696a68, 0x7775767473717270, 0x7f7d7e7c7b797a78}; + +static uint64_t idx_interleave_quads[] = { \ + 0x0703060205010400, 0x0f0b0e0a0d090c08, 0x1713161215111410, 0x1f1b1e1a1d191c18, \ + 0x2723262225212420, 0x2f2b2e2a2d292c28, 0x3733363235313430, 0x3f3b3e3a3d393c38, \ + 0x4743464245414440, 0x4f4b4e4a4d494c48, 0x5753565255515450, 0x5f5b5e5a5d595c58, \ + 0x6763666265616460, 0x6f6b6e6a6d696c68, 0x7773767275717470, 0x7f7b7e7a7d797c78}; + +#define OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ_NOEXPEND(_Tpvec, func) \ +inline _Tpvec v_interleave_##func(const _Tpvec& vec) { \ + CV_CheckLE(VTraits<_Tpvec>::vlanes(), VTraits<_Tpvec>::max_nlanes, "RVV implementation only supports VLEN in the range [128, 1024]"); \ + vuint8m1_t vidx = vundefined_u8m1();\ + vidx = vreinterpret_u8m1(vle64_v_u64m1(idx_interleave_##func, 16)); \ + return vrgather(vec, vidx, VTraits::vlanes()); \ +} +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ_NOEXPEND(v_uint8, pairs) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ_NOEXPEND(v_int8, pairs) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ_NOEXPEND(v_uint8, quads) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ_NOEXPEND(v_int8, quads) + +#define OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(_Tpvec, width, vzext_vfx, func) \ +inline _Tpvec v_interleave_##func(const _Tpvec& vec) { \ + CV_CheckLE(VTraits<_Tpvec>::vlanes(), VTraits<_Tpvec>::max_nlanes, "RVV implementation only supports VLEN in the range [128, 1024]"); \ + vuint##width##m1_t vidx = vundefined_u##width##m1();\ + vidx = vget_u##width##m1(vzext_vfx(vreinterpret_u8m1(vle64_v_u64m1(idx_interleave_##func, 16)), VTraits::vlanes()), 0); \ + return vrgather(vec, vidx, VTraits<_Tpvec>::vlanes()); \ +} + +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_uint16, 16, vzext_vf2, pairs) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_int16, 16, vzext_vf2, pairs) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_uint32, 32, vzext_vf4, pairs) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_int32, 32, vzext_vf4, pairs) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_float32, 32, vzext_vf4, pairs) + +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_uint16, 16, vzext_vf2, quads) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_int16, 16, vzext_vf2, quads) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_uint32, 32, vzext_vf4, quads) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_int32, 32, vzext_vf4, quads) +OPENCV_HAL_IMPL_RVV_INTERLEAVED_PQ(v_float32, 32, vzext_vf4, quads) + +//////////// PopCount ////////// +static const unsigned char popCountTable[256] = +{ + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, +}; +#define OPENCV_HAL_IMPL_RVV_HADD(_Tpvec, _Tpvec2, _Tm2, width, width2, suffix, add) \ +static inline _Tpvec2 v_hadd(_Tpvec a) { \ + vuint##width2##m1_t oneX2 = vmv_v_x_u##width2##m1(1, VTraits::vlanes()); \ + vuint##width##m1_t one = vreinterpret_u##width##m1(oneX2); \ + _Tm2 res = add(a, vslide1down(a, 0, VTraits::vlanes()), VTraits::vlanes()); \ + return vget_##suffix##m1(vcompress(vmseq(one, 1, VTraits::vlanes()), res, res, VTraits::vlanes()), 0); \ +} +OPENCV_HAL_IMPL_RVV_HADD(v_uint8, v_uint16, vuint16m2_t, 8, 16, u16, vwaddu_vv) +OPENCV_HAL_IMPL_RVV_HADD(v_uint16, v_uint32, vuint32m2_t, 16, 32, u32, vwaddu_vv) +OPENCV_HAL_IMPL_RVV_HADD(v_uint32, v_uint64, vuint64m2_t, 32, 64, u64, vwaddu_vv) +OPENCV_HAL_IMPL_RVV_HADD(v_int8, v_int16, vint16m2_t, 8, 16, i16, vwadd_vv) +OPENCV_HAL_IMPL_RVV_HADD(v_int16, v_int32, vint32m2_t, 16, 32, i32, vwadd_vv) +OPENCV_HAL_IMPL_RVV_HADD(v_int32, v_int64, vint64m2_t, 32, 64, i64, vwadd_vv) + +OPENCV_HAL_IMPL_RVV_HADD(vint32m2_t, v_int32, vint32m2_t, 16, 32, i32, vadd) +OPENCV_HAL_IMPL_RVV_HADD(vint64m2_t, v_int64, vint64m2_t, 32, 64, i64, vadd) + +inline v_uint8 v_popcount(const v_uint8& a) +{ + return vloxei8(popCountTable, a, VTraits::vlanes()); +} +inline v_uint16 v_popcount(const v_uint16& a) +{ + return v_hadd(v_popcount(vreinterpret_u8m1(a))); +} +inline v_uint32 v_popcount(const v_uint32& a) +{ + return v_hadd(v_hadd(v_popcount(vreinterpret_u8m1(a)))); +} + +inline v_uint8 v_popcount(const v_int8& a) +{ + return v_popcount(v_abs(a));\ +} +inline v_uint16 v_popcount(const v_int16& a) +{ + return v_popcount(v_abs(a));\ +} +inline v_uint32 v_popcount(const v_int32& a) +{ + return v_popcount(v_abs(a));\ +} + + +//////////// SignMask //////////// +#define OPENCV_HAL_IMPL_RVV_SIGNMASK_OP(_Tpvec) \ +inline int v_signmask(const _Tpvec& a) \ +{ \ + uint8_t ans[4] = {0}; \ + vsm(ans, vmslt(a, 0, VTraits<_Tpvec>::vlanes()), VTraits<_Tpvec>::vlanes()); \ + return *(reinterpret_cast(ans)) & (((__int128_t)1 << VTraits<_Tpvec>::vlanes()) - 1); \ +} \ +inline int v_scan_forward(const _Tpvec& a) \ +{ \ + return (int)vfirst(vmslt(a, 0, VTraits<_Tpvec>::vlanes()), VTraits<_Tpvec>::vlanes()); \ +} + +OPENCV_HAL_IMPL_RVV_SIGNMASK_OP(v_int8) +OPENCV_HAL_IMPL_RVV_SIGNMASK_OP(v_int16) +OPENCV_HAL_IMPL_RVV_SIGNMASK_OP(v_int32) +OPENCV_HAL_IMPL_RVV_SIGNMASK_OP(v_int64) + +inline int64 v_signmask(const v_uint8& a) +{ return v_signmask(v_reinterpret_as_s8(a)); } +inline int64 v_signmask(const v_uint16& a) +{ return v_signmask(v_reinterpret_as_s16(a)); } +inline int v_signmask(const v_uint32& a) +{ return v_signmask(v_reinterpret_as_s32(a)); } +inline int v_signmask(const v_float32& a) +{ return v_signmask(v_reinterpret_as_s32(a)); } +inline int v_signmask(const v_uint64& a) +{ return v_signmask(v_reinterpret_as_s64(a)); } +#if CV_SIMD_SCALABLE_64F +inline int v_signmask(const v_float64& a) +{ return v_signmask(v_reinterpret_as_s64(a)); } +#endif + +//////////// Scan forward //////////// +inline int v_scan_forward(const v_uint8& a) +{ return v_scan_forward(v_reinterpret_as_s8(a)); } +inline int v_scan_forward(const v_uint16& a) +{ return v_scan_forward(v_reinterpret_as_s16(a)); } +inline int v_scan_forward(const v_uint32& a) +{ return v_scan_forward(v_reinterpret_as_s32(a)); } +inline int v_scan_forward(const v_float32& a) +{ return v_scan_forward(v_reinterpret_as_s32(a)); } +inline int v_scan_forward(const v_uint64& a) +{ return v_scan_forward(v_reinterpret_as_s64(a)); } +#if CV_SIMD_SCALABLE_64F +inline int v_scan_forward(const v_float64& a) +{ return v_scan_forward(v_reinterpret_as_s64(a)); } +#endif + +//////////// Pack triplets //////////// +// {A0, A1, A2, A3, B0, B1, B2, B3, C0 ...} --> {A0, A1, A2, B0, B1, B2, C0 ...} +// mask: {0,0,0,1, ...} -> {T,T,T,F, ...} +#define OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(_Tpvec, v_trunc) \ +inline _Tpvec v_pack_triplets(const _Tpvec& vec) { \ + size_t vl = vsetvlmax_e8m1(); \ + vuint32m1_t one = vmv_v_x_u32m1(1, vl/4); \ + vuint8m1_t zero = vmv_v_x_u8m1(0, vl); \ + vuint8m1_t mask = vreinterpret_u8m1(one); \ + return vcompress(vmseq(v_trunc(vslideup(zero, mask, 3, vl)), 0, vl), vec, vec, VTraits<_Tpvec>::vlanes()); \ +} + +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_uint8, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_int8, OPENCV_HAL_NOP) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_uint16, vlmul_trunc_u8mf2) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_int16, vlmul_trunc_u8mf2) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_uint32, vlmul_trunc_u8mf4) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_int32, vlmul_trunc_u8mf4) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_float32, vlmul_trunc_u8mf4) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_uint64, vlmul_trunc_u8mf8) +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_int64, vlmul_trunc_u8mf8) +#if CV_SIMD_SCALABLE_64F +OPENCV_HAL_IMPL_RVV_PACK_TRIPLETS(v_float64, vlmul_trunc_u8mf8) +#endif + ////// FP16 support /////// +#if defined(__riscv_zfh) && __riscv_zfh inline v_float32 v_load_expand(const float16_t* ptr) { - // TODO - return vundefined_f32m1(); + return vfwcvt_f(vle16_v_f16mf2((_Float16*)ptr, VTraits::vlanes()) ,VTraits::vlanes());; +} + +inline void v_pack_store(float16_t* ptr, const v_float32& v) +{ + vse16_v_f16mf2((_Float16*)ptr, vfncvt_f_f_w_f16mf2(v, VTraits::vlanes()), VTraits::vlanes()); +} +#else +inline v_float32 v_load_expand(const float16_t* ptr) +{ + float buf[32]; + for( int i = 0; i < VTraits::vlanes(); i++ ) buf[i] = (float)ptr[i]; + return v_load(buf); +} + +inline void v_pack_store(float16_t* ptr, const v_float32& v) +{ + float buf[32]; + v_store(buf, v); + for( int i = 0; i < VTraits::vlanes(); i++ ) ptr[i] = float16_t(buf[i]); +} +#endif +////////////// Rounding ////////////// +inline v_int32 v_round(const v_float32& a) +{ + // return vfcvt_x(vfadd(a, 1e-6, VTraits::vlanes()), VTraits::vlanes()); + return vfcvt_x(a, VTraits::vlanes()); +} + +inline v_int32 v_floor(const v_float32& a) +{ + return vfcvt_x(vfsub(a, 0.5f - 1e-5, VTraits::vlanes()), VTraits::vlanes()); + // return vfcvt_x(a, VTraits::vlanes()); +} + +inline v_int32 v_ceil(const v_float32& a) +{ + return vfcvt_x(vfadd(a, 0.5f - 1e-5, VTraits::vlanes()), VTraits::vlanes()); +} + +inline v_int32 v_trunc(const v_float32& a) +{ + return vfcvt_rtz_x(a, VTraits::vlanes()); +} +#if CV_SIMD_SCALABLE_64F +inline v_int32 v_round(const v_float64& a) +{ + return vfncvt_x(vlmul_ext_f64m2(vfadd(a, 1e-6, VTraits::vlanes())), VTraits::vlanes()); +} + +inline v_int32 v_round(const v_float64& a, const v_float64& b) +{ + return vfncvt_x(vset(vlmul_ext_f64m2(vfadd(a, 1e-6, VTraits::vlanes())), 1, b), VTraits::vlanes()); +} + +inline v_int32 v_floor(const v_float64& a) +{ + return vfncvt_x(vlmul_ext_f64m2(vfsub(a, 0.5f - 1e-6, VTraits::vlanes())), VTraits::vlanes()); +} + +inline v_int32 v_ceil(const v_float64& a) +{ + return vfncvt_x(vlmul_ext_f64m2(vfadd(a, 0.5f - 1e-6, VTraits::vlanes())), VTraits::vlanes()); +} + +inline v_int32 v_trunc(const v_float64& a) +{ + return vfncvt_rtz_x(vlmul_ext_f64m2(a), VTraits::vlanes()); +} +#endif + +//////// Dot Product //////// + +// 16 >> 32 +inline v_int32 v_dotprod(const v_int16& a, const v_int16& b) +{ + vint32m2_t temp1 = vwmul(a, b, VTraits::vlanes()); + return v_hadd(temp1); +} + +inline v_int32 v_dotprod(const v_int16& a, const v_int16& b, const v_int32& c) +{ + vint32m2_t temp1 = vwmul(a, b, VTraits::vlanes()); + return vadd(v_hadd(temp1), c, VTraits::vlanes()); +} + +// 32 >> 64 +inline v_int64 v_dotprod(const v_int32& a, const v_int32& b) +{ + vuint64m1_t one64 = vmv_v_x_u64m1(1, VTraits::vlanes()); \ + vuint32m1_t one32 = vreinterpret_u32m1(one64); \ + vbool32_t mask = vmseq(one32, 1, VTraits::vlanes()); \ + vint64m2_t temp1 = vwmul(a, b, VTraits::vlanes()); \ + vint64m2_t temp2 = vslide1down(temp1, 0, VTraits::vlanes()); + vint64m2_t res = vadd(temp1, temp2, VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vlmul_trunc_i64m1(res); \ +} +inline v_int64 v_dotprod(const v_int32& a, const v_int32& b, const v_int64& c) +{ + vuint64m1_t one64 = vmv_v_x_u64m1(1, VTraits::vlanes()); \ + vuint32m1_t one32 = vreinterpret_u32m1(one64); \ + vbool32_t mask = vmseq(one32, 1, VTraits::vlanes()); \ + vint64m2_t temp1 = vwmul(a, b, VTraits::vlanes()); \ + vint64m2_t temp2 = vslide1down(temp1, 0, VTraits::vlanes()); + vint64m2_t res = vadd(temp1, temp2, VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vadd(vlmul_trunc_i64m1(res), c, VTraits::vlanes()); \ +} + +// 8 >> 32 +inline v_uint32 v_dotprod_expand(const v_uint8& a, const v_uint8& b) +{ + vuint32m1_t one32 = vmv_v_x_u32m1(1, VTraits::vlanes()); \ + vuint8m1_t one8 = vreinterpret_u8m1(one32); \ + vbool8_t mask = vmseq(one8, 1, VTraits::vlanes()); \ + vuint16m2_t t0 = vwmulu(a, b, VTraits::vlanes()); \ + vuint16m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vuint16m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vuint16m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vuint32m4_t res = vadd(vwaddu_vv(t2, t3, VTraits::vlanes()), vwaddu_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vlmul_trunc_u32m1(res); +} + +inline v_uint32 v_dotprod_expand(const v_uint8& a, const v_uint8& b, + const v_uint32& c) +{ + vuint32m1_t one32 = vmv_v_x_u32m1(1, VTraits::vlanes()); \ + vuint8m1_t one8 = vreinterpret_u8m1(one32); \ + vbool8_t mask = vmseq(one8, 1, VTraits::vlanes()); \ + vuint16m2_t t0 = vwmulu(a, b, VTraits::vlanes()); \ + vuint16m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vuint16m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vuint16m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vuint32m4_t res = vadd(vwaddu_vv(t2, t3, VTraits::vlanes()), vwaddu_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vadd(vlmul_trunc_u32m1(res), c, VTraits::vlanes()); +} + +inline v_int32 v_dotprod_expand(const v_int8& a, const v_int8& b) +{ + vuint32m1_t one32 = vmv_v_x_u32m1(1, VTraits::vlanes()); \ + vuint8m1_t one8 = vreinterpret_u8m1(one32); \ + vbool8_t mask = vmseq(one8, 1, VTraits::vlanes()); \ + vint16m2_t t0 = vwmul(a, b, VTraits::vlanes()); \ + vint16m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vint16m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vint16m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vint32m4_t res = vadd(vwadd_vv(t2, t3, VTraits::vlanes()), vwadd_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vlmul_trunc_i32m1(res); +} + +inline v_int32 v_dotprod_expand(const v_int8& a, const v_int8& b, + const v_int32& c) +{ + vuint32m1_t one32 = vmv_v_x_u32m1(1, VTraits::vlanes()); \ + vuint8m1_t one8 = vreinterpret_u8m1(one32); \ + vbool8_t mask = vmseq(one8, 1, VTraits::vlanes()); \ + vint16m2_t t0 = vwmul(a, b, VTraits::vlanes()); \ + vint16m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vint16m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vint16m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vint32m4_t res = vadd(vwadd_vv(t2, t3, VTraits::vlanes()), vwadd_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vadd(vlmul_trunc_i32m1(res), c, VTraits::vlanes()); +} + + +// // 16 >> 64 +inline v_uint64 v_dotprod_expand(const v_uint16& a, const v_uint16& b) +{ + vuint64m1_t one64 = vmv_v_x_u64m1(1, VTraits::vlanes()); \ + vuint16m1_t one16 = vreinterpret_u16m1(one64); \ + vbool16_t mask = vmseq(one16, 1, VTraits::vlanes()); \ + vuint32m2_t t0 = vwmulu(a, b, VTraits::vlanes()); \ + vuint32m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vuint32m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vuint32m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vuint64m4_t res = vadd(vwaddu_vv(t2, t3, VTraits::vlanes()), vwaddu_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vlmul_trunc_u64m1(res); +} +inline v_uint64 v_dotprod_expand(const v_uint16& a, const v_uint16& b, const v_uint64& c) +{ + vuint64m1_t one64 = vmv_v_x_u64m1(1, VTraits::vlanes()); \ + vuint16m1_t one16 = vreinterpret_u16m1(one64); \ + vbool16_t mask = vmseq(one16, 1, VTraits::vlanes()); \ + vuint32m2_t t0 = vwmulu(a, b, VTraits::vlanes()); \ + vuint32m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vuint32m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vuint32m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vuint64m4_t res = vadd(vwaddu_vv(t2, t3, VTraits::vlanes()), vwaddu_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vadd(vlmul_trunc_u64m1(res), c, VTraits::vlanes()); +} + +inline v_int64 v_dotprod_expand(const v_int16& a, const v_int16& b) +{ + vuint64m1_t one64 = vmv_v_x_u64m1(1, VTraits::vlanes()); \ + vuint16m1_t one16 = vreinterpret_u16m1(one64); \ + vbool16_t mask = vmseq(one16, 1, VTraits::vlanes()); \ + vint32m2_t t0 = vwmul(a, b, VTraits::vlanes()); \ + vint32m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vint32m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vint32m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vint64m4_t res = vadd(vwadd_vv(t2, t3, VTraits::vlanes()), vwadd_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vlmul_trunc_i64m1(res); +} +inline v_int64 v_dotprod_expand(const v_int16& a, const v_int16& b, + const v_int64& c) +{ + vuint64m1_t one64 = vmv_v_x_u64m1(1, VTraits::vlanes()); \ + vuint16m1_t one16 = vreinterpret_u16m1(one64); \ + vbool16_t mask = vmseq(one16, 1, VTraits::vlanes()); \ + vint32m2_t t0 = vwmul(a, b, VTraits::vlanes()); \ + vint32m2_t t1= vslide1down(t0, 0, VTraits::vlanes()); + vint32m2_t t2= vslide1down(t1, 0, VTraits::vlanes()); + vint32m2_t t3= vslide1down(t2, 0, VTraits::vlanes()); + vint64m4_t res = vadd(vwadd_vv(t2, t3, VTraits::vlanes()), vwadd_vv(t0, t1, VTraits::vlanes()), VTraits::vlanes()); + res = vcompress(mask, res, res, VTraits::vlanes()); \ + return vadd(vlmul_trunc_i64m1(res), c, VTraits::vlanes()); +} + +// // 32 >> 64f +#if CV_SIMD_SCALABLE_64F +inline v_float64 v_dotprod_expand(const v_int32& a, const v_int32& b) +{ return v_cvt_f64(v_dotprod(a, b)); } +inline v_float64 v_dotprod_expand(const v_int32& a, const v_int32& b, + const v_float64& c) +{ return v_add(v_dotprod_expand(a, b) , c); } +#endif + +//////// Fast Dot Product //////// +// 16 >> 32 +inline v_int32 v_dotprod_fast(const v_int16& a, const v_int16& b) +{ + v_int32 zero = v_setzero_s32(); + return vredsum(zero, vwmul(a, b, VTraits::vlanes()), zero, VTraits::vlanes()); +} +inline v_int32 v_dotprod_fast(const v_int16& a, const v_int16& b, const v_int32& c) +{ + v_int32 zero = v_setzero_s32(); + return vredsum(zero, vwmul(a, b, VTraits::vlanes()), vredsum(zero, c, zero, VTraits::vlanes()), VTraits::vlanes()); +} + +// 32 >> 64 +inline v_int64 v_dotprod_fast(const v_int32& a, const v_int32& b) +{ + v_int64 zero = v_setzero_s64(); + return vredsum(zero, vwmul(a, b, VTraits::vlanes()), zero, VTraits::vlanes()); +} +inline v_int64 v_dotprod_fast(const v_int32& a, const v_int32& b, const v_int64& c) +{ + v_int64 zero = v_setzero_s64(); + return vadd(vredsum(zero, vwmul(a, b, VTraits::vlanes()), zero, VTraits::vlanes()) , vredsum(zero, c, zero, VTraits::vlanes()), VTraits::vlanes()); +} + + +// 8 >> 32 +inline v_uint32 v_dotprod_expand_fast(const v_uint8& a, const v_uint8& b) +{ + v_uint32 zero = v_setzero_u32(); + return vwredsumu(zero, vwmulu(a, b, VTraits::vlanes()), zero, VTraits::vlanes()); +} +inline v_uint32 v_dotprod_expand_fast(const v_uint8& a, const v_uint8& b, const v_uint32& c) +{ + v_uint32 zero = v_setzero_u32(); + return vadd(vwredsumu(zero, vwmulu(a, b, VTraits::vlanes()), zero, VTraits::vlanes()) , vredsum(zero, c, zero, VTraits::vlanes()), VTraits::vlanes()); +} +inline v_int32 v_dotprod_expand_fast(const v_int8& a, const v_int8& b) +{ + v_int32 zero = v_setzero_s32(); + return vwredsum(zero, vwmul(a, b, VTraits::vlanes()), zero, VTraits::vlanes()); +} +inline v_int32 v_dotprod_expand_fast(const v_int8& a, const v_int8& b, const v_int32& c) +{ + v_int32 zero = v_setzero_s32(); + return vadd(vwredsum(zero, vwmul(a, b, VTraits::vlanes()), zero, VTraits::vlanes()) , vredsum(zero, c, zero, VTraits::vlanes()), VTraits::vlanes()); +} + +// 16 >> 64 +inline v_uint64 v_dotprod_expand_fast(const v_uint16& a, const v_uint16& b) +{ + v_uint64 zero = v_setzero_u64(); + return vwredsumu(zero, vwmulu(a, b, VTraits::vlanes()), zero, VTraits::vlanes()); +} +inline v_uint64 v_dotprod_expand_fast(const v_uint16& a, const v_uint16& b, const v_uint64& c) +{ + v_uint64 zero = v_setzero_u64(); + return vadd(vwredsumu(zero, vwmulu(a, b, VTraits::vlanes()), zero, VTraits::vlanes()), vredsum(zero, c, zero, VTraits::vlanes()), VTraits::vlanes()); +} +inline v_int64 v_dotprod_expand_fast(const v_int16& a, const v_int16& b) +{ + v_int64 zero = v_setzero_s64(); + return vwredsum(zero, vwmul(a, b, VTraits::vlanes()), zero, VTraits::vlanes()); +} +inline v_int64 v_dotprod_expand_fast(const v_int16& a, const v_int16& b, const v_int64& c) +{ + v_int64 zero = v_setzero_s64(); + return vadd(vwredsum(zero, vwmul(a, b, VTraits::vlanes()), zero, VTraits::vlanes()), vredsum(zero, c, zero, VTraits::vlanes()), VTraits::vlanes()); +} + +// 32 >> 64f +#if CV_SIMD_SCALABLE_64F +inline v_float64 v_dotprod_expand_fast(const v_int32& a, const v_int32& b) +{ return v_cvt_f64(v_dotprod_fast(a, b)); } +inline v_float64 v_dotprod_expand_fast(const v_int32& a, const v_int32& b, const v_float64& c) +{ return v_add(v_dotprod_expand_fast(a, b) , c); } +#endif + +// TODO: only 128 bit now. +inline v_float32 v_matmul(const v_float32& v, const v_float32& m0, + const v_float32& m1, const v_float32& m2, + const v_float32& m3) +{ + vfloat32m1_t res; + res = vfmul_vf_f32m1(m0, v_extract_n(v, 0), VTraits::vlanes()); + res = vfmacc_vf_f32m1(res, v_extract_n(v, 1), m1, VTraits::vlanes()); + res = vfmacc_vf_f32m1(res, v_extract_n(v, 2), m2, VTraits::vlanes()); + res = vfmacc_vf_f32m1(res, v_extract_n(v, 3), m3, VTraits::vlanes()); + return res; +} + +// TODO: only 128 bit now. +inline v_float32 v_matmuladd(const v_float32& v, const v_float32& m0, + const v_float32& m1, const v_float32& m2, + const v_float32& a) +{ + vfloat32m1_t res = vfmul_vf_f32m1(m0, v_extract_n(v,0), VTraits::vlanes()); + res = vfmacc_vf_f32m1(res, v_extract_n(v,1), m1, VTraits::vlanes()); + res = vfmacc_vf_f32m1(res, v_extract_n(v,2), m2, VTraits::vlanes()); + return vfadd(res, a, VTraits::vlanes()); } inline void v_cleanup() {} @@ -490,4 +2077,4 @@ CV_CPU_OPTIMIZATION_HAL_NAMESPACE_END } //namespace cv -#endif //OPENCV_HAL_INTRIN_RVV_SCALABLE_HPP \ No newline at end of file +#endif //OPENCV_HAL_INTRIN_RVV_SCALABLE_HPP diff --git a/modules/core/include/opencv2/core/types.hpp b/modules/core/include/opencv2/core/types.hpp index 40f3267564..ebb3dbccea 100644 --- a/modules/core/include/opencv2/core/types.hpp +++ b/modules/core/include/opencv2/core/types.hpp @@ -57,6 +57,11 @@ #include "opencv2/core/cvstd.hpp" #include "opencv2/core/matx.hpp" +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4459) // declaration of '...' hides global declaration +#endif + namespace cv { @@ -2014,6 +2019,15 @@ double jaccardDistance(const Rect_<_Tp>& a, const Rect_<_Tp>& b) { return 1.0 - Aab / (Aa + Ab - Aab); } +/** @brief Finds out if there is any intersection between two rectangles + * + * mainly useful for language bindings + * @param rect1 First rectangle + * @param rect2 Second rectangle + * @return the area of the intersection + */ +CV_EXPORTS_W inline double rectangleIntersectionArea(const Rect2d& a, const Rect2d& b) { return (a & b).area(); } + ////////////////////////////// RotatedRect ////////////////////////////// inline @@ -2453,4 +2467,8 @@ inline TermCriteria::TermCriteria(int _maxCount, double _epsilon) } // cv +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #endif //OPENCV_CORE_TYPES_HPP diff --git a/modules/core/include/opencv2/core/utils/filesystem.hpp b/modules/core/include/opencv2/core/utils/filesystem.hpp index a98d2202fc..8619ae4d1a 100644 --- a/modules/core/include/opencv2/core/utils/filesystem.hpp +++ b/modules/core/include/opencv2/core/utils/filesystem.hpp @@ -62,7 +62,7 @@ CV_EXPORTS void glob_relative(const cv::String& directory, const cv::String& pat CV_EXPORTS bool createDirectory(const cv::String& path); CV_EXPORTS bool createDirectories(const cv::String& path); -#ifdef __OPENCV_BUILD +#if defined(__OPENCV_BUILD) || defined(BUILD_PLUGIN) // TODO //CV_EXPORTS cv::String getTempDirectory(); diff --git a/modules/core/include/opencv2/core/utils/trace.private.hpp b/modules/core/include/opencv2/core/utils/trace.private.hpp index afc41159f6..5a826b417b 100644 --- a/modules/core/include/opencv2/core/utils/trace.private.hpp +++ b/modules/core/include/opencv2/core/utils/trace.private.hpp @@ -63,7 +63,7 @@ class TraceMessage; class TraceStorage { public: TraceStorage() {} - virtual ~TraceStorage() {}; + virtual ~TraceStorage() {} virtual bool put(const TraceMessage& msg) const = 0; }; diff --git a/modules/core/src/algorithm.cpp b/modules/core/src/algorithm.cpp index 556f5a7328..7186585323 100644 --- a/modules/core/src/algorithm.cpp +++ b/modules/core/src/algorithm.cpp @@ -55,17 +55,17 @@ Algorithm::~Algorithm() CV_TRACE_FUNCTION(); } -void Algorithm::write(const Ptr& fs, const String& name) const +void Algorithm::write(FileStorage& fs, const String& name) const { CV_TRACE_FUNCTION(); if(name.empty()) { - write(*fs); + write(fs); return; } - *fs << name << "{"; - write(*fs); - *fs << "}"; + fs << name << "{"; + write(fs); + fs << "}"; } void Algorithm::save(const String& filename) const diff --git a/modules/core/src/arithm.cpp b/modules/core/src/arithm.cpp index 67c713624a..2d773be2e5 100644 --- a/modules/core/src/arithm.cpp +++ b/modules/core/src/arithm.cpp @@ -168,7 +168,7 @@ static void binary_op( InputArray _src1, InputArray _src2, OutputArray _dst, if( dims1 <= 2 && dims2 <= 2 && kind1 == kind2 && sz1 == sz2 && type1 == type2 && !haveMask ) { - _dst.create(sz1, type1); + _dst.createSameSize(*psrc1, type1); CV_OCL_RUN(use_opencl, ocl_binary_op(*psrc1, *psrc2, _dst, _mask, bitwise, oclop, false)) @@ -1225,7 +1225,7 @@ void cv::compare(InputArray _src1, InputArray _src2, OutputArray _dst, int op) if( kind1 == kind2 && src1.dims <= 2 && src2.dims <= 2 && src1.size() == src2.size() && src1.type() == src2.type() ) { int cn = src1.channels(); - _dst.create(src1.size(), CV_8UC(cn)); + _dst.createSameSize(src1, CV_8UC(cn)); Mat dst = _dst.getMat(); Size sz = getContinuousSize2D(src1, src2, dst, src1.channels()); BinaryFuncC cmpFn = getCmpFunc(depth1); diff --git a/modules/core/src/check.cpp b/modules/core/src/check.cpp index 7f1310ad0a..ffd9b302bf 100644 --- a/modules/core/src/check.cpp +++ b/modules/core/src/check.cpp @@ -97,6 +97,10 @@ void check_failed_MatChannels(const int v1, const int v2, const CheckContext& ct { check_failed_auto_(v1, v2, ctx); } +void check_failed_auto(const bool v1, const bool v2, const CheckContext& ctx) +{ + check_failed_auto_(v1, v2, ctx); +} void check_failed_auto(const int v1, const int v2, const CheckContext& ctx) { check_failed_auto_(v1, v2, ctx); @@ -151,6 +155,22 @@ void check_failed_MatChannels(const int v, const CheckContext& ctx) { check_failed_auto_(v, ctx); } +void check_failed_true(const bool v, const CheckContext& ctx) +{ + CV_UNUSED(v); + std::stringstream ss; + ss << ctx.message << ":" << std::endl + << " '" << ctx.p1_str << "' must be 'true'"; + cv::error(cv::Error::StsError, ss.str(), ctx.func, ctx.file, ctx.line); +} +void check_failed_false(const bool v, const CheckContext& ctx) +{ + CV_UNUSED(v); + std::stringstream ss; + ss << ctx.message << ":" << std::endl + << " '" << ctx.p1_str << "' must be 'false'"; + cv::error(cv::Error::StsError, ss.str(), ctx.func, ctx.file, ctx.line); +} void check_failed_auto(const int v, const CheckContext& ctx) { check_failed_auto_(v, ctx); diff --git a/modules/core/src/hal_internal.cpp b/modules/core/src/hal_internal.cpp index f6ab89c7b7..c5b1687b15 100644 --- a/modules/core/src/hal_internal.cpp +++ b/modules/core/src/hal_internal.cpp @@ -251,6 +251,11 @@ lapack_SVD(fptype* a, size_t a_step, fptype *w, fptype* u, size_t u_step, fptype lwork = (int)round(work1); //optimal buffer size std::vector buffer(lwork + 1); + // Make sure MSAN sees the memory as having been written. + // MSAN does not think it has been written because a different language is called. + // Note: we do this here because if dgesdd is C++, MSAN errors can be reported within it. + CV_ANNOTATE_MEMORY_IS_INITIALIZED(buffer, sizeof(fptype) * (lwork + 1)); + if(typeid(fptype) == typeid(float)) OCV_LAPACK_FUNC(sgesdd)(mode, &m, &n, (float*)a, &lda, (float*)w, (float*)u, &ldu, (float*)vt, &ldv, (float*)&buffer[0], &lwork, &iworkBuf[0], info); @@ -261,7 +266,6 @@ lapack_SVD(fptype* a, size_t a_step, fptype *w, fptype* u, size_t u_step, fptype // Make sure MSAN sees the memory as having been written. // MSAN does not think it has been written because a different language was called. CV_ANNOTATE_MEMORY_IS_INITIALIZED(a, a_step * n); - CV_ANNOTATE_MEMORY_IS_INITIALIZED(buffer, sizeof(fptype) * (lwork + 1)); if (u) CV_ANNOTATE_MEMORY_IS_INITIALIZED(u, u_step * m); if (vt) diff --git a/modules/core/src/logger.cpp b/modules/core/src/logger.cpp index f395571e80..7e3f8aa29d 100644 --- a/modules/core/src/logger.cpp +++ b/modules/core/src/logger.cpp @@ -233,24 +233,42 @@ void writeLogMessage(LogLevel logLevel, const char* message) (*out) << std::flush; } +static const char* stripSourceFilePathPrefix(const char* file) +{ + CV_Assert(file); + const char* pos = file; + const char* strip_pos = NULL; + char ch = 0; + while ((ch = pos[0]) != 0) + { + ++pos; + if (ch == '/' || ch == '\\') + strip_pos = pos; + } + if (strip_pos == NULL || strip_pos == pos/*eos*/) + return file; + return strip_pos; +} + void writeLogMessageEx(LogLevel logLevel, const char* tag, const char* file, int line, const char* func, const char* message) { std::ostringstream strm; if (tag) { - strm << tag << " "; + strm << tag << ' '; } if (file) { - strm << file << " "; - } - if (line > 0) - { - strm << "(" << line << ") "; + strm << stripSourceFilePathPrefix(file); + if (line > 0) + { + strm << ':' << line; + } + strm << ' '; } if (func) { - strm << func << " "; + strm << func << ' '; } strm << message; writeLogMessage(logLevel, strm.str().c_str()); diff --git a/modules/core/src/matrix_transform.cpp b/modules/core/src/matrix_transform.cpp index 05ecf450e1..57fd0c6509 100644 --- a/modules/core/src/matrix_transform.cpp +++ b/modules/core/src/matrix_transform.cpp @@ -6,6 +6,8 @@ #include "opencl_kernels_core.hpp" #include "opencv2/core/detail/dispatch_helper.impl.hpp" +#include // std::swap_ranges + namespace cv { ////////////////////////////////////// transpose ///////////////////////////////////////// @@ -812,6 +814,49 @@ void flip( InputArray _src, OutputArray _dst, int flip_mode ) flipHoriz( dst.ptr(), dst.step, dst.ptr(), dst.step, dst.size(), esz ); } +static void +flipNDImpl(uchar* data, const int* shape, const size_t* step, int axis) +{ + int total = 1; + for (int i = 0; i < axis; ++i) + total *= shape[i]; + + int shape_at_axis = shape[axis]; + size_t step_at_axis = step[axis]; + size_t offset = 0; + size_t offset_increment = axis == 0 ? 0 : step[axis - 1]; + for (int i = 0; i < total; ++i, offset += offset_increment) + for (int j = 0, k = shape_at_axis - 1; j < shape_at_axis / 2; ++j, --k) + std::swap_ranges(data + offset + j * step_at_axis, + data + offset + j * step_at_axis + step_at_axis, + data + offset + k * step_at_axis); +} + +void flipND(InputArray _src, OutputArray _dst, int _axis) +{ + CV_INSTRUMENT_REGION(); + + Mat src = _src.getMat(); + + // verify axis + int ndim = src.dims; + CV_CheckLT(_axis, ndim, "flipND: given axis is out of range"); + CV_CheckGE(_axis, -ndim, "flipND: given axis is out of range"); + int axis = (_axis + ndim) % ndim; + + // in-place flip + _src.copyTo(_dst); + + // return the src if it has only one element on the flip axis + const auto shape = src.size.p; + if (shape[axis] == 1) + return ; + + // call impl + Mat dst = _dst.getMat(); + flipNDImpl(dst.ptr(), dst.size.p, dst.step.p, axis); +} + void rotate(InputArray _src, OutputArray _dst, int rotateMode) { CV_Assert(_src.dims() <= 2); diff --git a/modules/core/src/minmax.cpp b/modules/core/src/minmax.cpp index 96a7d027ad..f738a87e4f 100644 --- a/modules/core/src/minmax.cpp +++ b/modules/core/src/minmax.cpp @@ -976,6 +976,12 @@ bool ocl_minMaxIdx( InputArray _src, double* minVal, double* maxVal, int* minLoc return false; #endif + if (dev.deviceVersionMajor() == 1 && dev.deviceVersionMinor() < 2) + { + // 'static' storage class specifier used by "minmaxloc" is available from OpenCL 1.2+ only + return false; + } + bool doubleSupport = dev.doubleFPConfig() > 0, haveMask = !_mask.empty(), haveSrc2 = _src2.kind() != _InputArray::NONE; int type = _src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type), @@ -1565,13 +1571,24 @@ void cv::minMaxLoc( InputArray _img, double* minVal, double* maxVal, { CV_INSTRUMENT_REGION(); - CV_Assert(_img.dims() <= 2); + int dims = _img.dims(); + CV_CheckLE(dims, 2, ""); minMaxIdx(_img, minVal, maxVal, (int*)minLoc, (int*)maxLoc, mask); if( minLoc ) - std::swap(minLoc->x, minLoc->y); + { + if (dims == 2) + std::swap(minLoc->x, minLoc->y); + else + minLoc->y = 0; + } if( maxLoc ) - std::swap(maxLoc->x, maxLoc->y); + { + if (dims == 2) + std::swap(maxLoc->x, maxLoc->y); + else + maxLoc->y = 0; + } } enum class ReduceMode diff --git a/modules/core/src/opengl.cpp b/modules/core/src/opengl.cpp index ab39b1b8ac..45aa121a4a 100644 --- a/modules/core/src/opengl.cpp +++ b/modules/core/src/opengl.cpp @@ -1638,14 +1638,14 @@ Context& initializeContextFromGL() cl_uint numPlatforms; cl_int status = clGetPlatformIDs(0, NULL, &numPlatforms); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLInitError, "OpenCL: Can't get number of platforms"); + CV_Error_(cv::Error::OpenCLInitError, ("OpenCL: Can't get number of platforms: %d", status)); if (numPlatforms == 0) CV_Error(cv::Error::OpenCLInitError, "OpenCL: No available platforms"); std::vector platforms(numPlatforms); status = clGetPlatformIDs(numPlatforms, &platforms[0], NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLInitError, "OpenCL: Can't get number of platforms"); + CV_Error_(cv::Error::OpenCLInitError, ("OpenCL: Can't get number of platforms: %d", status)); // TODO Filter platforms by name from OPENCV_OPENCL_DEVICE @@ -1667,7 +1667,7 @@ Context& initializeContextFromGL() status = clGetPlatformInfo(platforms[i], CL_PLATFORM_EXTENSIONS, extensionSize, (char*)extensionStr.data(), NULL); } if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLInitError, "OpenCL: Can't get platform extension string"); + CV_Error_(cv::Error::OpenCLInitError, ("OpenCL: Can't get platform extension string: %d", status)); if (!strstr((const char*)extensionStr.data(), "cl_khr_gl_sharing")) continue; @@ -1759,31 +1759,31 @@ void convertToGLTexture2D(InputArray src, Texture2D& texture) cl_int status = 0; cl_mem clImage = clCreateFromGLTexture(context, CL_MEM_WRITE_ONLY, gl::TEXTURE_2D, 0, texture.texId(), &status); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clCreateFromGLTexture failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clCreateFromGLTexture failed: %d", status)); cl_mem clBuffer = (cl_mem)u.handle(ACCESS_READ); cl_command_queue q = (cl_command_queue)Queue::getDefault().ptr(); status = clEnqueueAcquireGLObjects(q, 1, &clImage, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueAcquireGLObjects failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueAcquireGLObjects failed: %d", status)); size_t offset = 0; // TODO size_t dst_origin[3] = {0, 0, 0}; size_t region[3] = { (size_t)u.cols, (size_t)u.rows, 1}; status = clEnqueueCopyBufferToImage(q, clBuffer, clImage, offset, dst_origin, region, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueCopyBufferToImage failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueCopyBufferToImage failed: %d", status)); status = clEnqueueReleaseGLObjects(q, 1, &clImage, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueReleaseGLObjects failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueReleaseGLObjects failed: %d", status)); status = clFinish(q); // TODO Use events if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clFinish failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clFinish failed: %d", status)); status = clReleaseMemObject(clImage); // TODO RAII if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clReleaseMemObject failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clReleaseMemObject failed: %d", status)); #endif } @@ -1821,31 +1821,31 @@ void convertFromGLTexture2D(const Texture2D& texture, OutputArray dst) cl_int status = 0; cl_mem clImage = clCreateFromGLTexture(context, CL_MEM_READ_ONLY, gl::TEXTURE_2D, 0, texture.texId(), &status); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clCreateFromGLTexture failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clCreateFromGLTexture failed: %d", status)); cl_mem clBuffer = (cl_mem)u.handle(ACCESS_READ); cl_command_queue q = (cl_command_queue)Queue::getDefault().ptr(); status = clEnqueueAcquireGLObjects(q, 1, &clImage, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueAcquireGLObjects failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueAcquireGLObjects failed: %d", status)); size_t offset = 0; // TODO size_t src_origin[3] = {0, 0, 0}; size_t region[3] = { (size_t)u.cols, (size_t)u.rows, 1}; status = clEnqueueCopyImageToBuffer(q, clImage, clBuffer, src_origin, region, offset, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueCopyImageToBuffer failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueCopyImageToBuffer failed: %d", status)); status = clEnqueueReleaseGLObjects(q, 1, &clImage, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueReleaseGLObjects failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueReleaseGLObjects failed: %d", status)); status = clFinish(q); // TODO Use events if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clFinish failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clFinish failed: %d", status)); status = clReleaseMemObject(clImage); // TODO RAII if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clReleaseMemObject failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clReleaseMemObject failed: %d", status)); #endif } @@ -1883,13 +1883,13 @@ UMat mapGLBuffer(const Buffer& buffer, AccessFlag accessFlags) cl_int status = 0; cl_mem clBuffer = clCreateFromGLBuffer(context, clAccessFlags, buffer.bufId(), &status); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clCreateFromGLBuffer failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clCreateFromGLBuffer failed: %d", status)); gl::Finish(); status = clEnqueueAcquireGLObjects(clQueue, 1, &clBuffer, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueAcquireGLObjects failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueAcquireGLObjects failed: %d", status)); size_t step = buffer.cols() * buffer.elemSize(); int rows = buffer.rows(); @@ -1921,15 +1921,15 @@ void unmapGLBuffer(UMat& u) cl_int status = clEnqueueReleaseGLObjects(clQueue, 1, &clBuffer, 0, NULL, NULL); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clEnqueueReleaseGLObjects failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clEnqueueReleaseGLObjects failed: %d", status)); status = clFinish(clQueue); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clFinish failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clFinish failed: %d", status)); status = clReleaseMemObject(clBuffer); if (status != CL_SUCCESS) - CV_Error(cv::Error::OpenCLApiCallError, "OpenCL: clReleaseMemObject failed"); + CV_Error_(cv::Error::OpenCLApiCallError, ("OpenCL: clReleaseMemObject failed: %d", status)); #endif } diff --git a/modules/core/src/parallel/registry_parallel.impl.hpp b/modules/core/src/parallel/registry_parallel.impl.hpp index c8b57e7d6c..2208748bb1 100644 --- a/modules/core/src/parallel/registry_parallel.impl.hpp +++ b/modules/core/src/parallel/registry_parallel.impl.hpp @@ -43,7 +43,7 @@ std::vector& getBuiltinParallelBackendsInfo() #endif }; return g_backends; -}; +} static bool sortByPriority(const ParallelBackendInfo &lhs, const ParallelBackendInfo &rhs) diff --git a/modules/core/src/parallel_impl.cpp b/modules/core/src/parallel_impl.cpp index c118bcd3cb..b18204ce84 100644 --- a/modules/core/src/parallel_impl.cpp +++ b/modules/core/src/parallel_impl.cpp @@ -40,11 +40,13 @@ DECLARE_CV_PAUSE #endif #ifndef CV_PAUSE # if defined __GNUC__ && (defined __i386__ || defined __x86_64__) +# include /* for __rdtsc */ # if !defined(__SSE2__) static inline void cv_non_sse_mm_pause() { __asm__ __volatile__ ("rep; nop"); } # define _mm_pause cv_non_sse_mm_pause # endif -# define CV_PAUSE(v) do { for (int __delay = (v); __delay > 0; --__delay) { _mm_pause(); } } while (0) +// With Skylake CPUs and above, _mm_pause takes 140 cycles so no need for a loop. +# define CV_PAUSE(v) do { (void)v; _mm_pause(); } while (0) # elif defined __GNUC__ && defined __aarch64__ # define CV_PAUSE(v) do { for (int __delay = (v); __delay > 0; --__delay) { asm volatile("yield" ::: "memory"); } } while (0) # elif defined __GNUC__ && defined __arm__ @@ -59,6 +61,8 @@ DECLARE_CV_PAUSE // https://github.com/riscv/riscv-isa-manual/issues/43 // # define CV_PAUSE(v) do { for (int __delay = (v); __delay > 0; --__delay) { asm volatile("pause"); } } while (0) # define CV_PAUSE(v) do { for (int __delay = (v); __delay > 0; --__delay) { asm volatile("nop"); } } while (0) +# elif defined __GNUC__ && defined __loongarch__ +# define CV_PAUSE(v) do { for (int __delay = (v); __delay > 0; --__delay) { asm volatile("nop"); } } while (0) # else # warning "Can't detect 'pause' (CPU-yield) instruction on the target platform. Specify CV_PAUSE() definition via compiler flags." # define CV_PAUSE(...) do { /* no-op: works, but not effective */ } while (0) diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index 82ccf6f658..87c3d34000 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -434,6 +434,8 @@ struct HWFeatures g_hwFeatureNames[CPU_AVX512_ICL] = "AVX512-ICL"; g_hwFeatureNames[CPU_RVV] = "RVV"; + + g_hwFeatureNames[CPU_LASX] = "LASX"; } void initialize(void) @@ -689,6 +691,10 @@ struct HWFeatures have[CV_CPU_RVV] = true; #endif + #if defined __loongarch_asx + have[CV_CPU_LASX] = true; + #endif + bool skip_baseline_check = false; #ifndef NO_GETENV if (getenv("OPENCV_SKIP_CPU_BASELINE_CHECK")) diff --git a/modules/core/test/test_arithm.cpp b/modules/core/test/test_arithm.cpp index 6d50b5a8f7..ea9cda56be 100644 --- a/modules/core/test/test_arithm.cpp +++ b/modules/core/test/test_arithm.cpp @@ -2201,6 +2201,72 @@ INSTANTIATE_TEST_CASE_P(Arithm, TransposeND, testing::Combine( testing::Values(perf::MatType(CV_8UC1), CV_32FC1) )); +class FlipND : public testing::TestWithParam< tuple, perf::MatType> > +{ +public: + std::vector m_shape; + int m_type; + + void SetUp() + { + std::tie(m_shape, m_type) = GetParam(); + } +}; + +TEST_P(FlipND, basic) +{ + Mat inp(m_shape, m_type); + randu(inp, 0, 255); + + int ndim = static_cast(m_shape.size()); + std::vector axes(ndim*2); // [-shape, shape) + std::iota(axes.begin(), axes.end(), -ndim); + auto get_flipped_indices = [&inp, ndim] (size_t total, std::vector& indices, int axis) + { + const int* shape = inp.size.p; + size_t t = total, idx; + for (int i = ndim - 1; i >= 0; --i) + { + idx = t / shape[i]; + indices[i] = int(t - idx * shape[i]); + t = idx; + } + + int _axis = (axis + ndim) % ndim; + std::vector flipped_indices = indices; + flipped_indices[_axis] = shape[_axis] - 1 - indices[_axis]; + return flipped_indices; + }; + + for (size_t i = 0; i < axes.size(); ++i) + { + int axis = axes[i]; + Mat out; + cv::flipND(inp, out, axis); + // check values + std::vector indices(ndim, 0); + for (size_t j = 0; j < inp.total(); ++j) + { + auto flipped_indices = get_flipped_indices(j, indices, axis); + switch (inp.type()) + { + case CV_8UC1: + ASSERT_EQ(inp.at(indices.data()), out.at(flipped_indices.data())); + break; + case CV_32FC1: + ASSERT_EQ(inp.at(indices.data()), out.at(flipped_indices.data())); + break; + default: + FAIL() << "Unsupported type: " << inp.type(); + } + } + } +} + +INSTANTIATE_TEST_CASE_P(Arithm, FlipND, testing::Combine( + testing::Values(std::vector{5, 10}, std::vector{2, 3, 4}), + testing::Values(perf::MatType(CV_8UC1), CV_32FC1) +)); TEST(Core_minMaxIdx, regression_9207_2) { diff --git a/modules/core/test/test_hal_core.cpp b/modules/core/test/test_hal_core.cpp index 35fb977478..f9078e55f9 100644 --- a/modules/core/test/test_hal_core.cpp +++ b/modules/core/test/test_hal_core.cpp @@ -136,7 +136,11 @@ TEST_P(HAL, mat_decomp) int size = (hcase / 2) % 4; size = size == 0 ? 3 : size == 1 ? 4 : size == 2 ? 6 : 15; int nfunc = (hcase / 8); + #if CV_LASX + double eps = depth == CV_32F ? 1e-5 : 2e-10; + #else double eps = depth == CV_32F ? 1e-5 : 1e-10; + #endif if( size == 3 ) return; // TODO ??? diff --git a/modules/core/test/test_intrin_utils.hpp b/modules/core/test/test_intrin_utils.hpp index 763702bf38..4ca7707137 100644 --- a/modules/core/test/test_intrin_utils.hpp +++ b/modules/core/test/test_intrin_utils.hpp @@ -503,6 +503,34 @@ template struct TheTest return *this; } + TheTest & test_interleave_pq() + { + Data dataA; + R a = dataA; + Data resP = v_interleave_pairs(a); + Data resQ = v_interleave_quads(a); + for (int i = 0; i < VTraits::vlanes()/4; ++i) + { + SCOPED_TRACE(cv::format("i=%d", i)); + EXPECT_EQ(resP[4*i], dataA[4*i ]); + EXPECT_EQ(resP[4*i + 1], dataA[4*i+2]); + EXPECT_EQ(resP[4*i + 2], dataA[4*i+1]); + EXPECT_EQ(resP[4*i + 3], dataA[4*i+3]); + } + for (int i = 0; i < VTraits::vlanes()/8; ++i) + { + SCOPED_TRACE(cv::format("i=%d", i)); + EXPECT_EQ(resQ[8*i], dataA[8*i ]); + EXPECT_EQ(resQ[8*i + 1], dataA[8*i+4]); + EXPECT_EQ(resQ[8*i + 2], dataA[8*i+1]); + EXPECT_EQ(resQ[8*i + 3], dataA[8*i+5]); + EXPECT_EQ(resQ[8*i + 4], dataA[8*i+2]); + EXPECT_EQ(resQ[8*i + 5], dataA[8*i+6]); + EXPECT_EQ(resQ[8*i + 6], dataA[8*i+3]); + EXPECT_EQ(resQ[8*i + 7], dataA[8*i+7]); + } + return *this; + } // float32x4 only TheTest & test_interleave_2channel() @@ -578,16 +606,18 @@ template struct TheTest TheTest & test_addsub() { - Data dataA, dataB; + Data dataA, dataB, dataC; dataB.reverse(); - R a = dataA, b = dataB; + dataA[1] = static_cast(std::numeric_limits::max()); + R a = dataA, b = dataB, c = dataC; - Data resC = v_add(a, b), resD = v_sub(a, b); + Data resD = v_add(a, b), resE = v_add(a, b, c), resF = v_sub(a, b); for (int i = 0; i < VTraits::vlanes(); ++i) { SCOPED_TRACE(cv::format("i=%d", i)); - EXPECT_EQ(saturate_cast(dataA[i] + dataB[i]), resC[i]); - EXPECT_EQ(saturate_cast(dataA[i] - dataB[i]), resD[i]); + EXPECT_EQ(saturate_cast(dataA[i] + dataB[i]), resD[i]); + EXPECT_EQ(saturate_cast(dataA[i] + dataB[i] + dataC[i]), resE[i]); + EXPECT_EQ(saturate_cast(dataA[i] - dataB[i]), resF[i]); } return *this; @@ -614,16 +644,18 @@ template struct TheTest TheTest & test_mul() { - Data dataA, dataB; + Data dataA, dataB, dataC; dataA[1] = static_cast(std::numeric_limits::max()); dataB.reverse(); - R a = dataA, b = dataB; + R a = dataA, b = dataB, c = dataC; - Data resC = v_mul(a, b); + Data resD = v_mul(a, b); + Data resE = v_mul(a, b, c); for (int i = 0; i < VTraits::vlanes(); ++i) { SCOPED_TRACE(cv::format("i=%d", i)); - EXPECT_EQ(saturate_cast(dataA[i] * dataB[i]), resC[i]); + EXPECT_EQ(saturate_cast(dataA[i] * dataB[i]), resD[i]); + EXPECT_EQ(saturate_cast(dataA[i] * dataB[i] * dataC[i]), resE[i]); } return *this; @@ -699,7 +731,7 @@ template struct TheTest for (int i = 0; i < VTraits::vlanes(); ++i) { SCOPED_TRACE(cv::format("i=%d", i)); - R_type ssub = dataA[i] - dataB[i] < std::numeric_limits::min() ? std::numeric_limits::min() : dataA[i] - dataB[i]; + R_type ssub = dataA[i] - dataB[i] < std::numeric_limits::lowest() ? std::numeric_limits::lowest() : dataA[i] - dataB[i]; EXPECT_EQ((u_type)std::abs(ssub), resC[i]); } @@ -1049,6 +1081,7 @@ template struct TheTest typedef typename VTraits::lane_type uint_type; Data dataA, dataB(0), dataC, dataD(1), dataE(2); + dataA[0] = (LaneType)std::numeric_limits::max(); dataA[1] *= (LaneType)-1; union { @@ -1131,6 +1164,22 @@ template struct TheTest return *this; } + TheTest & test_pack_triplets() + { + Data dataA; + R a = dataA; + Data res = v_pack_triplets(a); + + for (int i = 0; i < VTraits::vlanes()/4; ++i) + { + SCOPED_TRACE(cv::format("i=%d", i)); + EXPECT_EQ(dataA[4*i], res[3*i]); + EXPECT_EQ(dataA[4*i+1], res[3*i+1]); + EXPECT_EQ(dataA[4*i+2], res[3*i+2]); + } + return *this; + } + template TheTest & test_pack_u() { @@ -1573,19 +1622,27 @@ template struct TheTest v_transpose4x4(a, b, c, d, e, f, g, h); - Data res[4] = {e, f, g, h}; - // for (int i = 0; i < VTraits::vlanes(); i += 4) - // { - int i = 0; - for (int j = 0; j < 4; ++j) - { - SCOPED_TRACE(cv::format("i=%d j=%d", i, j)); - EXPECT_EQ(dataA[i + j], res[j][i]); - EXPECT_EQ(dataB[i + j], res[j][i + 1]); - EXPECT_EQ(dataC[i + j], res[j][i + 2]); - EXPECT_EQ(dataD[i + j], res[j][i + 3]); - } - // } + // Data res[4] = {e, f, g, h}; // Generates incorrect data in certain RVV case. + Data res0 = e, res1 = f, res2 = g, res3 = h; + EXPECT_EQ(dataA[0], res0[0]); + EXPECT_EQ(dataB[0], res0[1]); + EXPECT_EQ(dataC[0], res0[2]); + EXPECT_EQ(dataD[0], res0[3]); + + EXPECT_EQ(dataA[1], res1[0]); + EXPECT_EQ(dataB[1], res1[1]); + EXPECT_EQ(dataC[1], res1[2]); + EXPECT_EQ(dataD[1], res1[3]); + + EXPECT_EQ(dataA[2], res2[0]); + EXPECT_EQ(dataB[2], res2[1]); + EXPECT_EQ(dataC[2], res2[2]); + EXPECT_EQ(dataD[2], res2[3]); + + EXPECT_EQ(dataA[3], res3[0]); + EXPECT_EQ(dataB[3], res3[1]); + EXPECT_EQ(dataC[3], res3[2]); + EXPECT_EQ(dataD[3], res3[3]); return *this; } @@ -1599,15 +1656,14 @@ template struct TheTest R a = dataA, b = dataB, c = dataC, d = dataD; Data res = v_reduce_sum4(a, b, c, d); - // for (int i = 0; i < VTraits::vlanes(); i += 4) - // { - int i = 0; + for (int i = 0; i < VTraits::vlanes(); i += 4) + { SCOPED_TRACE(cv::format("i=%d", i)); EXPECT_COMPARE_EQ(dataA.sum(i, 4), res[i]); EXPECT_COMPARE_EQ(dataB.sum(i, 4), res[i + 1]); EXPECT_COMPARE_EQ(dataC.sum(i, 4), res[i + 2]); EXPECT_COMPARE_EQ(dataD.sum(i, 4), res[i + 3]); - // } + } return *this; } @@ -1725,132 +1781,16 @@ template struct TheTest #endif }; -#if CV_SIMD_SCALABLE //Temporary -#define DUMP_ENTRY(type) printf("SIMD: %s\n", CV__TRACE_FUNCTION); - - +#define DUMP_ENTRY(type) printf("SIMD%d: %s\n", 8*VTraits::vlanes(), CV__TRACE_FUNCTION); //============= 8-bit integer ===================================================================== void test_hal_intrin_uint8() { DUMP_ENTRY(v_uint8); - // typedef v_uint8 R; - TheTest() - .test_loadstore() - .test_min_max() - ; -} - -void test_hal_intrin_int8() -{ - DUMP_ENTRY(v_int8); - // typedef v_int8 R; - TheTest() - .test_loadstore() - .test_min_max() - ; -} - -//============= 16-bit integer ===================================================================== - -void test_hal_intrin_uint16() -{ - DUMP_ENTRY(v_uint16); - // typedef v_uint16 R; - TheTest() - .test_loadstore() - .test_min_max() - ; -} - -void test_hal_intrin_int16() -{ - DUMP_ENTRY(v_int16); - // typedef v_int16 R; - TheTest() - .test_loadstore() - .test_min_max() - ; -} - -//============= 32-bit integer ===================================================================== - -void test_hal_intrin_uint32() -{ - DUMP_ENTRY(v_uint32); - // typedef v_uint32 R; - TheTest() - .test_loadstore() - .test_min_max() - ; -} - -void test_hal_intrin_int32() -{ - DUMP_ENTRY(v_int32); - // typedef v_int32 R; - TheTest() - .test_loadstore() - .test_min_max() - ; -} - -//============= 64-bit integer ===================================================================== - -void test_hal_intrin_uint64() -{ - DUMP_ENTRY(v_uint64); - // typedef v_uint64 R; - TheTest() - .test_loadstore() - ; -} - -void test_hal_intrin_int64() -{ - DUMP_ENTRY(v_int64); - // typedef v_int64 R; - TheTest() - .test_loadstore() - ; -} - -//============= Floating point ===================================================================== -void test_hal_intrin_float32() -{ - DUMP_ENTRY(v_float32); - // typedef v_float32 R; - TheTest() - .test_loadstore() - .test_min_max() - ; -} - -void test_hal_intrin_float64() -{ - DUMP_ENTRY(v_float64); -#if CV_SIMD_64F - // typedef v_float64 R; - TheTest() - .test_loadstore() - .test_min_max() - ; - -#endif -} - -#else - -#define DUMP_ENTRY(type) printf("SIMD%d: %s\n", 8*(int)sizeof(v_uint8), CV__TRACE_FUNCTION); -//============= 8-bit integer ===================================================================== - -void test_hal_intrin_uint8() -{ - DUMP_ENTRY(v_uint8); - typedef v_uint8 R; TheTest() .test_loadstore() .test_interleave() + .test_interleave_pq() .test_expand() .test_expand_q() .test_addsub() @@ -1873,8 +1813,10 @@ void test_hal_intrin_uint8() .test_reverse() .test_extract<0>().test_extract<1>().test_extract<8>().test_extract<15>() .test_rotate<0>().test_rotate<1>().test_rotate<8>().test_rotate<15>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - //.test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_extract_highest() + .test_pack_triplets() + //.test_broadcast_element<0>().test_broadcast_element<1>() #if CV_SIMD_WIDTH == 32 .test_pack<9>().test_pack<10>().test_pack<13>().test_pack<15>() .test_pack_u<9>().test_pack_u<10>().test_pack_u<13>().test_pack_u<15>() @@ -1887,10 +1829,10 @@ void test_hal_intrin_uint8() void test_hal_intrin_int8() { DUMP_ENTRY(v_int8); - typedef v_int8 R; TheTest() .test_loadstore() .test_interleave() + .test_interleave_pq() .test_expand() .test_expand_q() .test_addsub() @@ -1913,8 +1855,10 @@ void test_hal_intrin_int8() .test_reverse() .test_extract<0>().test_extract<1>().test_extract<8>().test_extract<15>() .test_rotate<0>().test_rotate<1>().test_rotate<8>().test_rotate<15>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - //.test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_extract_highest() + .test_pack_triplets() + //.test_broadcast_element<0>().test_broadcast_element<1>() ; } @@ -1923,10 +1867,10 @@ void test_hal_intrin_int8() void test_hal_intrin_uint16() { DUMP_ENTRY(v_uint16); - typedef v_uint16 R; TheTest() .test_loadstore() .test_interleave() + .test_interleave_pq() .test_expand() .test_addsub() .test_arithm_wrap() @@ -1950,18 +1894,20 @@ void test_hal_intrin_uint16() .test_reverse() .test_extract<0>().test_extract<1>().test_extract<4>().test_extract<7>() .test_rotate<0>().test_rotate<1>().test_rotate<4>().test_rotate<7>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - //.test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_extract_highest() + .test_pack_triplets() + //.test_broadcast_element<0>().test_broadcast_element<1>() ; } void test_hal_intrin_int16() { DUMP_ENTRY(v_int16); - typedef v_int16 R; TheTest() .test_loadstore() .test_interleave() + .test_interleave_pq() .test_expand() .test_addsub() .test_arithm_wrap() @@ -1987,8 +1933,10 @@ void test_hal_intrin_int16() .test_reverse() .test_extract<0>().test_extract<1>().test_extract<4>().test_extract<7>() .test_rotate<0>().test_rotate<1>().test_rotate<4>().test_rotate<7>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - //.test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_extract_highest() + .test_pack_triplets() + //.test_broadcast_element<0>().test_broadcast_element<1>() ; } @@ -1997,10 +1945,10 @@ void test_hal_intrin_int16() void test_hal_intrin_uint32() { DUMP_ENTRY(v_uint32); - typedef v_uint32 R; TheTest() .test_loadstore() .test_interleave() + // .test_interleave_pq() //not implemented in AVX .test_expand() .test_addsub() .test_mul() @@ -2020,19 +1968,22 @@ void test_hal_intrin_uint32() .test_reverse() .test_extract<0>().test_extract<1>().test_extract<2>().test_extract<3>() .test_rotate<0>().test_rotate<1>().test_rotate<2>().test_rotate<3>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - .test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_broadcast_element<0>().test_broadcast_element<1>() + .test_extract_highest() + .test_broadcast_highest() .test_transpose() + .test_pack_triplets() ; } void test_hal_intrin_int32() { DUMP_ENTRY(v_int32); - typedef v_int32 R; TheTest() .test_loadstore() .test_interleave() + // .test_interleave_pq() //not implemented in AVX .test_expand() .test_addsub() .test_mul() @@ -2053,11 +2004,14 @@ void test_hal_intrin_int32() .test_reverse() .test_extract<0>().test_extract<1>().test_extract<2>().test_extract<3>() .test_rotate<0>().test_rotate<1>().test_rotate<2>().test_rotate<3>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - .test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_broadcast_element<0>().test_broadcast_element<1>() .test_float_cvt32() .test_float_cvt64() .test_transpose() + .test_extract_highest() + .test_broadcast_highest() + .test_pack_triplets() ; } @@ -2066,7 +2020,6 @@ void test_hal_intrin_int32() void test_hal_intrin_uint64() { DUMP_ENTRY(v_uint64); - typedef v_uint64 R; TheTest() .test_loadstore() .test_addsub() @@ -2078,15 +2031,15 @@ void test_hal_intrin_uint64() .test_reverse() .test_extract<0>().test_extract<1>() .test_rotate<0>().test_rotate<1>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - //.test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_extract_highest() + //.test_broadcast_element<0>().test_broadcast_element<1>() ; } void test_hal_intrin_int64() { DUMP_ENTRY(v_int64); - typedef v_int64 R; TheTest() .test_loadstore() .test_addsub() @@ -2098,8 +2051,9 @@ void test_hal_intrin_int64() .test_reverse() .test_extract<0>().test_extract<1>() .test_rotate<0>().test_rotate<1>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - //.test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_extract_highest() + //.test_broadcast_element<0>().test_broadcast_element<1>() .test_cvt64_double() ; } @@ -2108,14 +2062,15 @@ void test_hal_intrin_int64() void test_hal_intrin_float32() { DUMP_ENTRY(v_float32); - typedef v_float32 R; TheTest() .test_loadstore() .test_interleave() .test_interleave_2channel() + // .test_interleave_pq() //not implemented in AVX .test_addsub() .test_mul() .test_div() + .test_abs() .test_cmp() .test_sqrt_abs() .test_min_max() @@ -2132,8 +2087,11 @@ void test_hal_intrin_float32() .test_reverse() .test_extract<0>().test_extract<1>().test_extract<2>().test_extract<3>() .test_rotate<0>().test_rotate<1>().test_rotate<2>().test_rotate<3>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - .test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_broadcast_element<0>().test_broadcast_element<1>() + .test_extract_highest() + .test_broadcast_highest() + .test_pack_triplets() #if CV_SIMD_WIDTH == 32 .test_extract<4>().test_extract<5>().test_extract<6>().test_extract<7>() .test_rotate<4>().test_rotate<5>().test_rotate<6>().test_rotate<7>() @@ -2145,12 +2103,12 @@ void test_hal_intrin_float64() { DUMP_ENTRY(v_float64); #if CV_SIMD_64F - typedef v_float64 R; TheTest() .test_loadstore() .test_addsub() .test_mul() .test_div() + .test_abs() .test_cmp() .test_sqrt_abs() .test_min_max() @@ -2162,8 +2120,9 @@ void test_hal_intrin_float64() .test_reverse() .test_extract<0>().test_extract<1>() .test_rotate<0>().test_rotate<1>() - .test_extract_n<0>().test_extract_n<1>().test_extract_n() - //.test_broadcast_element<0>().test_broadcast_element<1>().test_broadcast_element() + .test_extract_n<0>().test_extract_n<1>() + .test_extract_highest() + //.test_broadcast_element<0>().test_broadcast_element<1>() #if CV_SIMD_WIDTH == 32 .test_extract<2>().test_extract<3>() .test_rotate<2>().test_rotate<3>() @@ -2189,8 +2148,6 @@ void test_hal_intrin_float16() #endif } -#endif - /*#if defined(CV_CPU_DISPATCH_MODE_FP16) && CV_CPU_DISPATCH_MODE == FP16 void test_hal_intrin_float16() diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index e1a72a94bc..d222bbc223 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -2353,6 +2353,96 @@ TEST(Mat, regression_18473) EXPECT_EQ((int)5, (int)m.at(19, 49, 99)); } +// FITIT: remove DISABLE_ when 1D Mat is supported +TEST(Mat1D, DISABLED_basic) +{ + std::vector sizes { 100 }; + Mat m1(sizes, CV_8UC1, Scalar::all(5)); + m1.at(50) = 10; + EXPECT_FALSE(m1.empty()); + ASSERT_EQ(1, m1.dims); + ASSERT_EQ(1, m1.size.dims()); // hack map on .rows + EXPECT_EQ(Size(100, 1), m1.size()); + + { + SCOPED_TRACE("clone"); + Mat m = m1.clone(); + EXPECT_EQ(1, m.dims); + EXPECT_EQ(Size(100, 1), m.size()); + } + + { + SCOPED_TRACE("colRange()"); + Mat m = m1.colRange(Range(10, 30)); + EXPECT_EQ(1, m.dims); + EXPECT_EQ(Size(20, 1), m.size()); + } + + { + SCOPED_TRACE("reshape(1, 1)"); + Mat m = m1.reshape(1, 1); + EXPECT_EQ(1, m.dims); + EXPECT_EQ(Size(100, 1), m.size()); + } + + { + SCOPED_TRACE("reshape(1, 100)"); + Mat m = m1.reshape(1, 100); + EXPECT_EQ(2, m.dims); + EXPECT_EQ(Size(1, 100), m.size()); + } + + { + SCOPED_TRACE("reshape(1, {1, 100})"); + Mat m = m1.reshape(1, {1, 100}); + EXPECT_EQ(2, m.dims); + EXPECT_EQ(Size(100, 1), m.size()); + } + + { + SCOPED_TRACE("copyTo(std::vector)"); + std::vector dst; + m1.copyTo(dst); + EXPECT_EQ(100u, dst.size()); + } + + { + SCOPED_TRACE("copyTo(row2D)"); + Mat m(5, 100, CV_8UC1, Scalar::all(0)); + const Mat row2D = m.row(2); + EXPECT_NO_THROW(m1.copyTo(row2D)); + } + + { + SCOPED_TRACE("convertTo(row2D)"); + Mat m(5, 100, CV_32FC1, Scalar::all(0)); + const Mat row2D = m.row(2); + EXPECT_NO_THROW(m1.convertTo(row2D, CV_32FC1)); + } + + { + SCOPED_TRACE("CvMat"); + CvMat c_mat = cvMat(m1); + EXPECT_EQ(100, c_mat.cols); + EXPECT_EQ(1, c_mat.rows); + } + + { + SCOPED_TRACE("CvMatND"); + CvMatND c_mat = cvMatND(m1); + EXPECT_EQ(2, c_mat.dims); + EXPECT_EQ(100, c_mat.dim[0].size); + EXPECT_EQ(1, c_mat.dim[1].size); + } + + { + SCOPED_TRACE("minMaxLoc"); + Point pt; + minMaxLoc(m1, 0, 0, 0, &pt); + EXPECT_EQ(50, pt.x); + EXPECT_EQ(0, pt.y); + } +} TEST(Mat, ptrVecni_20044) { diff --git a/modules/dnn/CMakeLists.txt b/modules/dnn/CMakeLists.txt index e0773d5214..1ec21c085d 100644 --- a/modules/dnn/CMakeLists.txt +++ b/modules/dnn/CMakeLists.txt @@ -8,23 +8,31 @@ endif() set(the_description "Deep neural network module. It allows to load models from different frameworks and to make forward pass") -ocv_add_dispatched_file_force_all("layers/layers_common" AVX AVX2 AVX512_SKX RVV) -ocv_add_dispatched_file_force_all("int8layers/layers_common" AVX2 AVX512_SKX) +ocv_add_dispatched_file_force_all("layers/layers_common" AVX AVX2 AVX512_SKX RVV LASX) +ocv_add_dispatched_file_force_all("int8layers/layers_common" AVX2 AVX512_SKX LASX) ocv_add_module(dnn opencv_core opencv_imgproc WRAP python java objc js) + +include(${CMAKE_CURRENT_LIST_DIR}/cmake/plugin.cmake) + + ocv_option(OPENCV_DNN_OPENCL "Build with OpenCL support" HAVE_OPENCL AND NOT APPLE) if(OPENCV_DNN_OPENCL AND HAVE_OPENCL) - add_definitions(-DCV_OCL4DNN=1) + ocv_target_compile_definitions(${the_module} PRIVATE "CV_OCL4DNN=1") endif() if(WITH_WEBNN AND HAVE_WEBNN) - add_definitions(-DHAVE_WEBNN=1) + ocv_target_compile_definitions(${the_module} PRIVATE "HAVE_WEBNN=1") endif() if(HAVE_TIMVX) - add_definitions(-DHAVE_TIMVX=1) + ocv_target_compile_definitions(${the_module} PRIVATE "HAVE_TIMVX=1") +endif() + +if(HAVE_CANN) + ocv_target_compile_definitions(${the_module} PRIVATE "HAVE_CANN=1") endif() ocv_option(OPENCV_DNN_CUDA "Build with CUDA support" @@ -35,7 +43,7 @@ ocv_option(OPENCV_DNN_CUDA "Build with CUDA support" if(OPENCV_DNN_CUDA) if(HAVE_CUDA AND HAVE_CUBLAS AND HAVE_CUDNN) - add_definitions(-DCV_CUDA4DNN=1) + ocv_target_compile_definitions(${the_module} PRIVATE "CV_CUDA4DNN=1") else() if(NOT HAVE_CUDA) message(SEND_ERROR "DNN: CUDA backend requires CUDA Toolkit. Please resolve dependency or disable OPENCV_DNN_CUDA=OFF") @@ -47,12 +55,15 @@ if(OPENCV_DNN_CUDA) endif() endif() + ocv_cmake_hook_append(INIT_MODULE_SOURCES_opencv_dnn "${CMAKE_CURRENT_LIST_DIR}/cmake/hooks/INIT_MODULE_SOURCES_opencv_dnn.cmake") + if(HAVE_TENGINE) - add_definitions(-DHAVE_TENGINE=1) + ocv_target_compile_definitions(${the_module} PRIVATE "HAVE_TENGINE=1") endif() + if(MSVC) add_definitions( -D_CRT_SECURE_NO_WARNINGS=1 ) ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4244 /wd4267 /wd4018 /wd4355 /wd4800 /wd4251 /wd4996 /wd4146 @@ -87,10 +98,10 @@ if(ANDROID) endif() if(NOT BUILD_PROTOBUF) - add_definitions(-DOPENCV_DNN_EXTERNAL_PROTOBUF=1) + ocv_target_compile_definitions(${the_module} PRIVATE "OPENCV_DNN_EXTERNAL_PROTOBUF=1") endif() -add_definitions(-DHAVE_PROTOBUF=1) +ocv_target_compile_definitions(${the_module} PRIVATE "HAVE_PROTOBUF=1") #suppress warnings in autogenerated caffe.pb.* files ocv_warnings_disable(CMAKE_CXX_FLAGS @@ -155,6 +166,11 @@ if(HAVE_TIMVX) list(APPEND libs -Wl,--whole-archive ${TIMVX_LIBRARY} -Wl,--no-whole-archive) endif() +if(HAVE_CANN) + list(APPEND include_dirs ${CANN_INCLUDE_DIRS}) + list(APPEND libs -Wl,--whole-archive ${CANN_LIBRARIES} -Wl,--no-whole-archive) +endif() + set(webnn_srcs "") if(NOT EMSCRIPTEN) if(HAVE_WEBNN) @@ -175,12 +191,34 @@ endif() set(dnn_runtime_libs "") +file(GLOB_RECURSE dnn_srcs + "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp" +) +file(GLOB_RECURSE dnn_int_hdrs + "${CMAKE_CURRENT_LIST_DIR}/src/*.hpp" + "${CMAKE_CURRENT_LIST_DIR}/src/*.h" +) +set(dnn_plugin_srcs ${dnn_srcs} ${dnn_int_hdrs}) +ocv_list_filterout_ex(dnn_plugin_srcs + "/src/dnn.cpp$|/src/dnn_utils.cpp$|/src/dnn_utils.cpp$|/src/dnn_read.cpp$|/src/registry.cpp$|/src/backend.cpp$" + # importers + "/src/(caffe|darknet|onnx|tensorflow|torch)/" + # executors + "/src/(cuda|cuda4dnn|ocl4dnn|vkcom|webnn)/" +) + ocv_option(OPENCV_DNN_OPENVINO "Build with OpenVINO support (2021.4+)" (TARGET ocv.3rdparty.openvino)) if(TARGET ocv.3rdparty.openvino AND OPENCV_DNN_OPENVINO) if(NOT HAVE_OPENVINO AND NOT HAVE_NGRAPH) message(FATAL_ERROR "DNN: Inference Engine is not supported without enabled 'nGraph'. Check build configuration.") endif() - list(APPEND dnn_runtime_libs ocv.3rdparty.openvino) + if("openvino" IN_LIST DNN_PLUGIN_LIST OR DNN_PLUGIN_LIST STREQUAL "all") + # plugin doesn't support PCH, separate directory scope is necessary + # opencv_world requires absolute path + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/misc/plugin/openvino" "${CMAKE_CURRENT_BINARY_DIR}/dnn_plugin_openvino") + elseif(NOT OPENCV_DNN_BUILTIN_BACKEND) + list(APPEND dnn_runtime_libs ocv.3rdparty.openvino) + endif() endif() ocv_glob_module_sources(${sources_options} SOURCES ${fw_srcs} ${webnn_srcs}) @@ -205,7 +243,7 @@ if(BUILD_PERF_TESTS) ) find_package(Caffe QUIET) if (Caffe_FOUND) - add_definitions(-DHAVE_CAFFE=1) + ocv_target_compile_definitions(opencv_perf_dnn PRIVATE "HAVE_CAFFE=1") ocv_target_link_libraries(opencv_perf_dnn caffe) endif() elseif(OPENCV_DNN_PERF_CLCAFFE @@ -213,8 +251,32 @@ if(BUILD_PERF_TESTS) ) find_package(Caffe QUIET) if (Caffe_FOUND) - add_definitions(-DHAVE_CLCAFFE=1) + ocv_target_compile_definitions(opencv_perf_dnn PRIVATE "HAVE_CLCAFFE=1") ocv_target_link_libraries(opencv_perf_dnn caffe) endif() endif() endif() + +if(DNN_ENABLE_PLUGINS) + ocv_target_compile_definitions(${the_module} PRIVATE ENABLE_PLUGINS) + if(TARGET opencv_test_dnn) + ocv_target_compile_definitions(opencv_test_dnn PRIVATE ENABLE_PLUGINS) + endif() + if(OPENCV_DEBUG_POSTFIX) + ocv_append_source_file_compile_definitions("${CMAKE_CURRENT_LIST_DIR}/src/backend.cpp" "DEBUG_POSTFIX=${OPENCV_DEBUG_POSTFIX}") + endif() +endif() + +ocv_option(OPENCV_TEST_DNN_OPENVINO "Build test with OpenVINO code" (TARGET ocv.3rdparty.openvino)) +if(TARGET ocv.3rdparty.openvino AND OPENCV_TEST_DNN_OPENVINO) + if(TARGET opencv_test_dnn) + ocv_target_link_libraries(opencv_test_dnn ocv.3rdparty.openvino) + endif() +endif() + +ocv_option(OPENCV_TEST_DNN_CANN "Build test with CANN" (TARGET ocv.3rdparty.cann)) +if(TARGET ocv.3rdparty.cann AND OPENCV_TEST_DNN_CANN) + if(TARGET opencv_test_dnn) + ocv_target_link_libraries(opencv_test_dnn ocv.3rdparty.cann) + endif() +endif() diff --git a/modules/dnn/cmake/init.cmake b/modules/dnn/cmake/init.cmake new file mode 100644 index 0000000000..f4493c53e9 --- /dev/null +++ b/modules/dnn/cmake/init.cmake @@ -0,0 +1,29 @@ +if(PROJECT_NAME STREQUAL "OpenCV") + set(ENABLE_PLUGINS_DEFAULT ON) + if(EMSCRIPTEN OR IOS OR WINRT) + set(ENABLE_PLUGINS_DEFAULT OFF) + endif() + set(DNN_PLUGIN_LIST "" CACHE STRING "List of DNN backends to be compiled as plugins (openvino, etc or special value 'all')") + set(DNN_ENABLE_PLUGINS "${ENABLE_PLUGINS_DEFAULT}" CACHE BOOL "Allow building and using of DNN plugins") + mark_as_advanced(DNN_PLUGIN_LIST DNN_ENABLE_PLUGINS) + + string(REPLACE "," ";" DNN_PLUGIN_LIST "${DNN_PLUGIN_LIST}") # support comma-separated list (,) too + string(TOLOWER "${DNN_PLUGIN_LIST}" DNN_PLUGIN_LIST) + if(NOT DNN_ENABLE_PLUGINS) + if(DNN_PLUGIN_LIST) + message(WARNING "DNN: plugins are disabled through DNN_ENABLE_PLUGINS, so DNN_PLUGIN_LIST='${DNN_PLUGIN_LIST}' is ignored") + set(DNN_PLUGIN_LIST "") + endif() + else() + # Make virtual plugins target + if(NOT TARGET opencv_dnn_plugins) + add_custom_target(opencv_dnn_plugins ALL) + endif() + endif() +endif() + +# +# Detect available dependencies +# + +# OpenVINO - detected by main CMake scripts (shared with G-API) diff --git a/modules/dnn/cmake/plugin.cmake b/modules/dnn/cmake/plugin.cmake new file mode 100644 index 0000000000..055d21efc3 --- /dev/null +++ b/modules/dnn/cmake/plugin.cmake @@ -0,0 +1,80 @@ +function(ocv_create_builtin_dnn_plugin name target) + + ocv_debug_message("ocv_create_builtin_dnn_plugin(${ARGV})") + + if(NOT TARGET ${target}) + message(FATAL_ERROR "${target} does not exist!") + endif() + if(NOT OpenCV_SOURCE_DIR) + message(FATAL_ERROR "OpenCV_SOURCE_DIR must be set to build the plugin!") + endif() + + message(STATUS "DNN: add builtin plugin '${name}'") + + set(ENABLE_PRECOMPILED_HEADERS OFF) # no support for PCH in plugins, conflicts with module's source files + + # TODO: update CPU optimizations scripts to support plugins + add_definitions(-D__OPENCV_BUILD=1) + add_definitions(-DBUILD_PLUGIN=1) + include_directories("${OPENCV_MODULE_opencv_dnn_BINARY_DIR}") # Cannot open include file: 'layers/layers_common.simd_declarations.hpp' + + foreach(src ${ARGN}) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/src/${src}") + list(APPEND sources "${CMAKE_CURRENT_LIST_DIR}/src/${src}") + elseif(IS_ABSOLUTE "${src}") + list(APPEND sources "${src}") + else() + message(FATAL_ERROR "Unknown source: ${src}") + endif() + endforeach() + + if(OPENCV_MODULE_${the_module}_SOURCES_DISPATCHED) + list(APPEND sources ${OPENCV_MODULE_${the_module}_SOURCES_DISPATCHED}) + endif() + + set(__${name}_DEPS_EXT "") + ocv_compiler_optimization_process_sources(sources __${name}_DEPS_EXT ${name}) + + add_library(${name} MODULE ${sources}) + target_include_directories(${name} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(${name} PRIVATE ${target} ${__${name}_DEPS_EXT}) + target_link_libraries(${name} PRIVATE ${__plugin_libs}) + + foreach(mod opencv_dnn + opencv_core + opencv_imgproc + opencv_dnn + ) + ocv_target_link_libraries(${name} LINK_PRIVATE ${mod}) + ocv_target_include_directories(${name} "${OPENCV_MODULE_${mod}_LOCATION}/include") + endforeach() + + if(WIN32) + set(OPENCV_PLUGIN_VERSION "${OPENCV_DLLVERSION}" CACHE STRING "") + if(CMAKE_CXX_SIZEOF_DATA_PTR EQUAL 8) + set(OPENCV_PLUGIN_ARCH "_64" CACHE STRING "") + else() + set(OPENCV_PLUGIN_ARCH "" CACHE STRING "") + endif() + else() + set(OPENCV_PLUGIN_VERSION "" CACHE STRING "") + set(OPENCV_PLUGIN_ARCH "" CACHE STRING "") + endif() + + set_target_properties(${name} PROPERTIES + CXX_STANDARD 11 + CXX_VISIBILITY_PRESET hidden + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + OUTPUT_NAME "${name}${OPENCV_PLUGIN_VERSION}${OPENCV_PLUGIN_ARCH}" + ) + + if(WIN32) + set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) + install(TARGETS ${name} OPTIONAL LIBRARY DESTINATION ${OPENCV_BIN_INSTALL_PATH} COMPONENT plugins) + else() + install(TARGETS ${name} OPTIONAL LIBRARY DESTINATION ${OPENCV_LIB_INSTALL_PATH} COMPONENT plugins) + endif() + + add_dependencies(opencv_dnn_plugins ${name}) + +endfunction() diff --git a/modules/dnn/include/opencv2/dnn/all_layers.hpp b/modules/dnn/include/opencv2/dnn/all_layers.hpp index 263e48c760..37af0ddea5 100644 --- a/modules/dnn/include/opencv2/dnn/all_layers.hpp +++ b/modules/dnn/include/opencv2/dnn/all_layers.hpp @@ -256,6 +256,10 @@ CV__DNN_INLINE_NS_BEGIN { public: static Ptr create(const LayerParams& params); + bool fusedActivation = false; + bool fusedAdd = false; + bool isConv2D = false; // Should be deleted after fastconv branch support Conv1D and Conv3D. + bool useWinograd = false; // Flag whether to use Winograd to speed up 3x3 convolution. }; class CV_EXPORTS ConvolutionLayerInt8 : public BaseConvolutionLayer @@ -267,6 +271,7 @@ CV__DNN_INLINE_NS_BEGIN // quantization type flag. The perChannel default is true, that means it contains the parameters // of per-Channel quantization. Otherwise, that means this layer contains per-Tensor quantized parameters. bool per_channel; + bool useWinograd = true; // Flag whether to use Winograd to speed up 3x3 convolution. static Ptr create(const LayerParams& params); }; @@ -298,6 +303,14 @@ CV__DNN_INLINE_NS_BEGIN static Ptr create(const LayerParams& params); }; + /** @brief Gather layer + */ + class CV_EXPORTS GatherLayer : public Layer + { + public: + static Ptr create(const LayerParams& params); + }; + class CV_EXPORTS PoolingLayer : public Layer { public: @@ -409,16 +422,16 @@ CV__DNN_INLINE_NS_BEGIN class CV_EXPORTS QuantizeLayer : public Layer { public: - float scale; - int zeropoint; + std::vector scales; + std::vector zeropoints; static Ptr create(const LayerParams ¶ms); }; class CV_EXPORTS DequantizeLayer : public Layer { public: - float scale; - int zeropoint; + std::vector scales; + std::vector zeropoints; static Ptr create(const LayerParams ¶ms); }; @@ -1054,6 +1067,24 @@ CV__DNN_INLINE_NS_BEGIN static Ptr create(const LayerParams& params); }; + class CV_EXPORTS ScatterLayer : public Layer + { + public: + static Ptr create(const LayerParams& params); + }; + + class CV_EXPORTS ScatterNDLayer : public Layer + { + public: + static Ptr create(const LayerParams& params); + }; + + class CV_EXPORTS TileLayer : public Layer + { + public: + static Ptr create(const LayerParams& params); + }; + //! @} //! @} CV__DNN_INLINE_NS_END diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index 6f03a8c32e..ffc9473c6e 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -52,6 +52,11 @@ namespace cv { namespace dnn { + +namespace accessor { +class DnnNetAccessor; // forward declaration +} + CV__DNN_INLINE_NS_BEGIN //! @addtogroup dnn //! @{ @@ -65,20 +70,23 @@ CV__DNN_INLINE_NS_BEGIN enum Backend { //! DNN_BACKEND_DEFAULT equals to DNN_BACKEND_INFERENCE_ENGINE if - //! OpenCV is built with Intel's Inference Engine library or + //! OpenCV is built with Intel OpenVINO or //! DNN_BACKEND_OPENCV otherwise. DNN_BACKEND_DEFAULT = 0, DNN_BACKEND_HALIDE, - DNN_BACKEND_INFERENCE_ENGINE, //!< Intel's Inference Engine computational backend - //!< @sa setInferenceEngineBackendType + DNN_BACKEND_INFERENCE_ENGINE, //!< Intel OpenVINO computational backend + //!< @note Tutorial how to build OpenCV with OpenVINO: @ref tutorial_dnn_openvino DNN_BACKEND_OPENCV, DNN_BACKEND_VKCOM, DNN_BACKEND_CUDA, DNN_BACKEND_WEBNN, DNN_BACKEND_TIMVX, -#ifdef __OPENCV_BUILD + DNN_BACKEND_CANN, +#if defined(__OPENCV_BUILD) || defined(BUILD_PLUGIN) +#if !defined(OPENCV_BINDING_PARSER) DNN_BACKEND_INFERENCE_ENGINE_NGRAPH = 1000000, // internal - use DNN_BACKEND_INFERENCE_ENGINE + setInferenceEngineBackendType() DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019, // internal - use DNN_BACKEND_INFERENCE_ENGINE + setInferenceEngineBackendType() +#endif #endif }; @@ -336,6 +344,15 @@ CV__DNN_INLINE_NS_BEGIN const std::vector > &outputsWrapper, bool isLast); + /** + * @brief Returns a CANN backend node + * + * @param inputsWrapper layer inputs + * @param index layer id for op name + * @param nodes inputs of this node + */ + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes); + /** * @brief Automatic Halide scheduling based on layer hyper-parameters. * @param[in] node Backend node with Halide functions. @@ -830,6 +847,12 @@ CV__DNN_INLINE_NS_BEGIN */ CV_WRAP void enableFusion(bool fusion); + /** @brief Enables or disables the Winograd compute branch. The Winograd compute branch can speed up + * 3x3 Convolution at a small loss of accuracy. + * @param useWinograd true to enable the Winograd compute branch. The default is true. + */ + CV_WRAP void enableWinograd(bool useWinograd); + /** @brief Returns overall time for inference and timings (in ticks) for layers. * * Indexes in returned vector correspond to layers ids. Some layers can be fused with others, @@ -840,8 +863,12 @@ CV__DNN_INLINE_NS_BEGIN */ CV_WRAP int64 getPerfProfile(CV_OUT std::vector& timings); - private: + struct Impl; + inline Impl* getImpl() const { return impl.get(); } + inline Impl& getImplRef() const { CV_DbgAssert(impl); return *impl.get(); } + friend class accessor::DnnNetAccessor; + protected: Ptr impl; }; @@ -1177,6 +1204,27 @@ CV__DNN_INLINE_NS_BEGIN CV_OUT std::vector& indices, const float eta = 1.f, const int top_k = 0); + /** @brief Performs batched non maximum suppression on given boxes and corresponding scores across different classes. + + * @param bboxes a set of bounding boxes to apply NMS. + * @param scores a set of corresponding confidences. + * @param class_ids a set of corresponding class ids. Ids are integer and usually start from 0. + * @param score_threshold a threshold used to filter boxes by score. + * @param nms_threshold a threshold used in non maximum suppression. + * @param indices the kept indices of bboxes after NMS. + * @param eta a coefficient in adaptive threshold formula: \f$nms\_threshold_{i+1}=eta\cdot nms\_threshold_i\f$. + * @param top_k if `>0`, keep at most @p top_k picked indices. + */ + CV_EXPORTS void NMSBoxesBatched(const std::vector& bboxes, const std::vector& scores, const std::vector& class_ids, + const float score_threshold, const float nms_threshold, + CV_OUT std::vector& indices, + const float eta = 1.f, const int top_k = 0); + + CV_EXPORTS_W void NMSBoxesBatched(const std::vector& bboxes, const std::vector& scores, const std::vector& class_ids, + const float score_threshold, const float nms_threshold, + CV_OUT std::vector& indices, + const float eta = 1.f, const int top_k = 0); + /** * @brief Enum of Soft NMS methods. * @see softNMSBoxes diff --git a/modules/dnn/include/opencv2/dnn/shape_utils.hpp b/modules/dnn/include/opencv2/dnn/shape_utils.hpp index 9bbbc806a8..6f4c0d57a9 100644 --- a/modules/dnn/include/opencv2/dnn/shape_utils.hpp +++ b/modules/dnn/include/opencv2/dnn/shape_utils.hpp @@ -160,22 +160,49 @@ static inline MatShape shape(int a0, int a1=-1, int a2=-1, int a3=-1) static inline int total(const MatShape& shape, int start = -1, int end = -1) { - if (start == -1) start = 0; - if (end == -1) end = (int)shape.size(); - if (shape.empty()) return 0; + int dims = (int)shape.size(); + + if (start == -1) start = 0; + if (end == -1) end = dims; + + CV_CheckLE(0, start, ""); + CV_CheckLE(start, end, ""); + CV_CheckLE(end, dims, ""); + int elems = 1; - CV_Assert(start <= (int)shape.size() && end <= (int)shape.size() && - start <= end); - for(int i = start; i < end; i++) + for (int i = start; i < end; i++) { elems *= shape[i]; } return elems; } +// TODO: rename to countDimsElements() +static inline int total(const Mat& mat, int start = -1, int end = -1) +{ + if (mat.empty()) + return 0; + + int dims = mat.dims; + + if (start == -1) start = 0; + if (end == -1) end = dims; + + CV_CheckLE(0, start, ""); + CV_CheckLE(start, end, ""); + CV_CheckLE(end, dims, ""); + + int elems = 1; + for (int i = start; i < end; i++) + { + elems *= mat.size[i]; + } + return elems; +} + static inline MatShape concat(const MatShape& a, const MatShape& b) { MatShape c = a; diff --git a/modules/dnn/include/opencv2/dnn/version.hpp b/modules/dnn/include/opencv2/dnn/version.hpp index 961ae1e02b..ea5a218904 100644 --- a/modules/dnn/include/opencv2/dnn/version.hpp +++ b/modules/dnn/include/opencv2/dnn/version.hpp @@ -6,7 +6,7 @@ #define OPENCV_DNN_VERSION_HPP /// Use with major OpenCV version only. -#define OPENCV_DNN_API_VERSION 20220821 +#define OPENCV_DNN_API_VERSION 20221220 #if !defined CV_DOXYGEN && !defined CV_STATIC_ANALYSIS && !defined CV_DNN_DONT_ADD_INLINE_NS #define CV__DNN_INLINE_NS __CV_CAT(dnn5_v, OPENCV_DNN_API_VERSION) diff --git a/modules/dnn/misc/plugin/openvino/CMakeLists.txt b/modules/dnn/misc/plugin/openvino/CMakeLists.txt new file mode 100644 index 0000000000..398218484e --- /dev/null +++ b/modules/dnn/misc/plugin/openvino/CMakeLists.txt @@ -0,0 +1,2 @@ +#include_directories("${OPENCV_MODULE_opencv_dnn_BINARY_DIR}") # Cannot open include file: 'layers/layers_common.simd_declarations.hpp' +ocv_create_builtin_dnn_plugin(opencv_dnn_openvino ocv.3rdparty.openvino ${dnn_plugin_srcs}) diff --git a/modules/dnn/misc/python/test/test_dnn.py b/modules/dnn/misc/python/test/test_dnn.py index 272121ba36..82d07f402b 100644 --- a/modules/dnn/misc/python/test/test_dnn.py +++ b/modules/dnn/misc/python/test/test_dnn.py @@ -113,10 +113,10 @@ class dnn_test(NewOpenCVTests): proto = self.find_dnn_file('dnn/layers/layer_convolution.prototxt') model = self.find_dnn_file('dnn/layers/layer_convolution.caffemodel') net = cv.dnn.readNet(proto, model) - net.setPreferableBackend(backend) - net.setPreferableTarget(target) - inp = np.random.standard_normal([1, 2, 10, 11]).astype(np.float32) try: + net.setPreferableBackend(backend) + net.setPreferableTarget(target) + inp = np.random.standard_normal([1, 2, 10, 11]).astype(np.float32) net.setInput(inp) net.forward() except BaseException as e: diff --git a/modules/dnn/perf/perf_layer.cpp b/modules/dnn/perf/perf_layer.cpp index 03ba8ab0e9..ffe0240a18 100644 --- a/modules/dnn/perf/perf_layer.cpp +++ b/modules/dnn/perf/perf_layer.cpp @@ -55,6 +55,8 @@ struct Layer_Slice : public TestBaseWithParam > } }; +static std::set nary_eltwise_cuda_deny_ops = {"add", "equal", "greater", "less", "mean", "mul", "pow", "sub"}; + struct Layer_NaryEltwise : public TestBaseWithParam > { void test_layer(const std::vector& a_shape, const std::vector& b_shape, const String op, bool isRef = false) @@ -62,6 +64,13 @@ struct Layer_NaryEltwise : public TestBaseWithParam > int backendId = get<0>(GetParam()); int targetId = get<1>(GetParam()); + if (!isRef && backendId == DNN_BACKEND_CUDA) + { + if (a_shape != b_shape) + throw SkipTestException("The test is skipped because inputs with different shapes are not supported."); + if (nary_eltwise_cuda_deny_ops.find(op) != nary_eltwise_cuda_deny_ops.end()) + throw SkipTestException("The operator '" + op + "' is skipped because is not support with cuda currently."); + } Mat a(a_shape, CV_32FC1); Mat b(b_shape, CV_32FC1); @@ -239,7 +248,181 @@ PERF_TEST_P_(Layer_Slice, FastNeuralStyle_eccv16) test_slice<4>(inputShape, begin, end); } +struct Layer_Scatter : public TestBaseWithParam > +{ + void test_layer(const std::vector& shape, const String reduction = "none", int axis = 0) + { + int backendId = get<0>(GetParam()); + int targetId = get<1>(GetParam()); + + Mat data(shape, CV_32FC1); + Mat indices(shape, CV_32FC1); + Mat updates(shape, CV_32FC1); + + Scalar mean = 0.f; + Scalar std = 1.f; + randn(data, mean, std); + randu(indices, 0, shape[axis]); + randn(updates, mean, std); + + indices.convertTo(indices, CV_32SC1, 1, -1); + + Net net; + LayerParams lp; + lp.type = "Scatter"; + lp.name = "testLayer"; + lp.set("reduction", reduction); + lp.set("axis", axis); + + int id = net.addLayerToPrev(lp.name, lp.type, lp); + net.connect(0, 0, id, 0); + net.connect(0, 1, id, 1); + net.connect(0, 2, id, 2); + + // warmup + { + std::vector inpNames(3); + inpNames[0] = "data"; + inpNames[1] = "indices"; + inpNames[2] = "updates"; + net.setInputsNames(inpNames); + net.setInput(data, inpNames[0]); + net.setInput(indices, inpNames[1]); + net.setInput(updates, inpNames[2]); + + net.setPreferableBackend(backendId); + net.setPreferableTarget(targetId); + Mat out = net.forward(); + } + + TEST_CYCLE() + { + Mat res = net.forward(); + } + + SANITY_CHECK_NOTHING(); + } + + int N = 8; + int C = 256; + int H = 128; + int W = 100; +}; + +PERF_TEST_P_(Layer_Scatter, DISABLED_Scatter) +{ + test_layer({N, C, H, W}); +} + +PERF_TEST_P_(Layer_Scatter, DISABLED_Scatter_add) +{ + test_layer({N, C, H, W}, "add"); +} + +struct Layer_ScatterND : public TestBaseWithParam > +{ + void test_layer(const std::vector& shape, const String reduction = "none") + { + int backendId = get<0>(GetParam()); + int targetId = get<1>(GetParam()); + + std::vector indices_shape(shape); + indices_shape.push_back(int(shape.size())); + Mat data(shape, CV_32FC1); + Mat indices(indices_shape, CV_32FC1); + Mat updates(shape, CV_32FC1); + + Scalar mean = 0.f; + Scalar std = 1.f; + randn(data, mean, std); + randn(updates, mean, std); + + // initialize the indices with index tuples like [0...N, 0...C, 0...H, 0...W] + std::vector current_index_tuple(shape.size()); + int total = data.total(); + std::vector indices_step; + for (int i = 0; i < indices.dims; i++) + { + int step = indices.step.p[i] / sizeof(float); + indices_step.push_back(step); + } + int t, j, idx, offset_at_idx, offset; + for (int i = 0; i < total; i++) + { + t = i; + for (j = shape.size() - 1; j >= 0; j--) + { + idx = t / shape[j]; + offset_at_idx = (int)(t - idx * shape[j]); + current_index_tuple[j] = offset_at_idx; + t = idx; + } + + offset = 0; + for (j = 0; j < shape.size(); j++) + offset += current_index_tuple[j] * indices_step[j]; + + for (j = 0; j < shape.size(); j++) + indices.at(offset + j) = current_index_tuple[j]; + } + + Net net; + LayerParams lp; + lp.type = "ScatterND"; + lp.name = "testLayer"; + lp.set("reduction", reduction); + + int id = net.addLayerToPrev(lp.name, lp.type, lp); + net.connect(0, 0, id, 0); + net.connect(0, 1, id, 1); + net.connect(0, 2, id, 2); + + // warmup + { + std::vector inpNames(3); + inpNames[0] = "data"; + inpNames[1] = "indices"; + inpNames[2] = "updates"; + net.setInputsNames(inpNames); + net.setInput(data, inpNames[0]); + net.setInput(indices, inpNames[1]); + net.setInput(updates, inpNames[2]); + + net.setPreferableBackend(backendId); + net.setPreferableTarget(targetId); + Mat out = net.forward(); + } + + TEST_CYCLE() + { + Mat res = net.forward(); + } + + SANITY_CHECK_NOTHING(); + } + + int N = 8; + int C = 256; + int H = 128; + int W = 100; +}; + +PERF_TEST_P_(Layer_ScatterND, DISABLED_ScatterND) +{ + test_layer({N, C, H ,W}); +} + +PERF_TEST_P_(Layer_ScatterND, DISABLED_ScatterND_add) +{ + test_layer({N, C, H , W}, "add"); +} + INSTANTIATE_TEST_CASE_P(/**/, Layer_Slice, dnnBackendsAndTargets(false, false)); INSTANTIATE_TEST_CASE_P(/**/, Layer_NaryEltwise, testing::Values(std::make_tuple(DNN_BACKEND_OPENCV, DNN_TARGET_CPU))); +#ifdef HAVE_CUDA +INSTANTIATE_TEST_CASE_P(CUDA, Layer_NaryEltwise, testing::Values(std::make_tuple(DNN_BACKEND_CUDA, DNN_TARGET_CUDA))); +#endif +INSTANTIATE_TEST_CASE_P(/**/, Layer_Scatter, testing::Values(std::make_tuple(DNN_BACKEND_OPENCV, DNN_TARGET_CPU))); +INSTANTIATE_TEST_CASE_P(/**/, Layer_ScatterND, testing::Values(std::make_tuple(DNN_BACKEND_OPENCV, DNN_TARGET_CPU))); } // namespace diff --git a/modules/dnn/perf/perf_net.cpp b/modules/dnn/perf/perf_net.cpp index 46db47bc4c..cfbb45b173 100644 --- a/modules/dnn/perf/perf_net.cpp +++ b/modules/dnn/perf/perf_net.cpp @@ -198,6 +198,7 @@ PERF_TEST_P_(DNNTestNetwork, Inception_v2_SSD_TensorFlow) PERF_TEST_P_(DNNTestNetwork, YOLOv3) { + applyTestTag(CV_TEST_TAG_MEMORY_2GB); if (backend == DNN_BACKEND_HALIDE) throw SkipTestException(""); #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2020040000) // nGraph compilation failure @@ -220,6 +221,7 @@ PERF_TEST_P_(DNNTestNetwork, YOLOv3) PERF_TEST_P_(DNNTestNetwork, YOLOv4) { + applyTestTag(CV_TEST_TAG_MEMORY_2GB); if (backend == DNN_BACKEND_HALIDE) throw SkipTestException(""); if (target == DNN_TARGET_MYRIAD) // not enough resources @@ -249,7 +251,7 @@ PERF_TEST_P_(DNNTestNetwork, YOLOv4_tiny) cvtColor(sample, sample, COLOR_BGR2RGB); Mat inp; sample.convertTo(inp, CV_32FC3, 1.0f / 255, 0); - processNet("dnn/yolov4-tiny.weights", "dnn/yolov4-tiny.cfg", "", inp); + processNet("dnn/yolov4-tiny-2020-12.weights", "dnn/yolov4-tiny-2020-12.cfg", "", inp); } PERF_TEST_P_(DNNTestNetwork, EAST_text_detection) diff --git a/modules/dnn/src/backend.cpp b/modules/dnn/src/backend.cpp new file mode 100644 index 0000000000..f6c6fecdad --- /dev/null +++ b/modules/dnn/src/backend.cpp @@ -0,0 +1,31 @@ +// 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 "backend.hpp" + +#include + +#include +#include +#ifdef NDEBUG +#define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_DEBUG + 1 +#else +#define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_VERBOSE + 1 +#endif +#include + +#include "factory.hpp" + +#include "plugin_api.hpp" +#include "plugin_wrapper.impl.hpp" + + +namespace cv { namespace dnn_backend { + +NetworkBackend::~NetworkBackend() +{ + // nothing +} + +}} // namespace cv::dnn_backend diff --git a/modules/dnn/src/backend.hpp b/modules/dnn/src/backend.hpp new file mode 100644 index 0000000000..37cc8a3cc0 --- /dev/null +++ b/modules/dnn/src/backend.hpp @@ -0,0 +1,43 @@ +// 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_DNN_BACKEND_HPP +#define OPENCV_DNN_BACKEND_HPP + +#include +#include + +namespace cv { namespace dnn_backend { + +using namespace cv::dnn; + +class CV_EXPORTS NetworkBackend +{ +public: + virtual ~NetworkBackend(); + + virtual void switchBackend(Net& net) = 0; + + /** + @param loaderID use empty "" for auto + @param model see cv::dnn::readNetwork + @param config see cv::dnn::readNetwork + */ + virtual Net readNetwork(const std::string& loaderID, const std::string& model, const std::string& config) = 0; + + /** @overload */ + virtual Net readNetwork( + const std::string& loaderID, + const uchar* bufferModelConfigPtr, size_t bufferModelConfigSize, + const uchar* bufferWeightsPtr, size_t bufferWeightsSize + ) = 0; + + // TODO: target as string + configuration + virtual bool checkTarget(Target target) = 0; +}; + + +} // namespace dnn_backend +} // namespace cv + +#endif // OPENCV_DNN_BACKEND_HPP diff --git a/modules/dnn/src/cuda4dnn/primitives/matmul.hpp b/modules/dnn/src/cuda4dnn/primitives/matmul.hpp index e29036d5f4..e4ab3d2721 100644 --- a/modules/dnn/src/cuda4dnn/primitives/matmul.hpp +++ b/modules/dnn/src/cuda4dnn/primitives/matmul.hpp @@ -23,9 +23,14 @@ namespace cv { namespace dnn { namespace cuda4dnn { public: using wrapper_type = GetCUDABackendWrapperType; - MatMulOp(csl::Stream stream_, csl::cublas::Handle handle) + MatMulOp(csl::Stream stream_, csl::cublas::Handle handle, const Mat& constInp) : stream(std::move(stream_)), cublasHandle(std::move(handle)) { + if (!constInp.empty()) + { + constTensor = csl::makeTensorHeader(constInp); + csl::copyMatToTensor(constInp, constTensor, stream); + } } void forward( @@ -33,13 +38,20 @@ namespace cv { namespace dnn { namespace cuda4dnn { const std::vector>& outputs, csl::Workspace& workspace) override { - CV_Assert(inputs.size() == 2 && outputs.size() == 1); + CV_Assert((inputs.size() == 2 && constTensor.empty() || + inputs.size() == 1 && !constTensor.empty()) && outputs.size() == 1); auto input1_wrapper = inputs[0].dynamicCast(); auto input1 = input1_wrapper->getView(); - auto input2_wrapper = inputs[1].dynamicCast(); - auto input2 = input2_wrapper->getView(); + csl::TensorView input2; + if (constTensor.empty()) + { + auto input2_wrapper = inputs[1].dynamicCast(); + input2 = input2_wrapper->getView(); + } + else + input2 = csl::TensorView(constTensor); auto output_wrapper = outputs[0].dynamicCast(); auto output = output_wrapper->getSpan(); @@ -59,9 +71,18 @@ namespace cv { namespace dnn { namespace cuda4dnn { auto m = input1.get_axis_size(-2); auto n = input1.get_axis_size(-1); - auto k = input2.get_axis_size(-1); auto b = input1.size() / m / n; - CV_Assert(input2.get_axis_size(-2) == n); + int k; + if (constTensor.empty()) + { + k = input2.get_axis_size(-1); + CV_Assert(input2.get_axis_size(-2) == n); + } + else + { + k = input2.get_axis_size(-2); + CV_Assert(input2.get_axis_size(-1) == n); + } CV_Assert(output.get_axis_size(-2) == m); CV_Assert(output.get_axis_size(-1) == k); @@ -70,24 +91,28 @@ namespace cv { namespace dnn { namespace cuda4dnn { CV_Assert(b == 1); CV_Assert(get_effective_rank(input1) <= 2); CV_Assert(get_effective_rank(input2) <= 2); - csl::tensor_ops::gemm(cublasHandle, 0.0, output, 1.0, false, input1, false, input2); + csl::tensor_ops::gemm(cublasHandle, 0.0, output, 1.0, false, input1, !constTensor.empty(), input2); } else { CV_Assert(rank >= 3); input1.reshape(b, m, n); - input2.reshape(b, n, k); + if (constTensor.empty()) + input2.reshape(b, n, k); + else + input2.reshape(b, k, n); output.reshape(b, m, k); input1.squeeze_to(3); input2.squeeze_to(3); output.squeeze_to(3); - csl::tensor_ops::gemmStridedBatched(cublasHandle, 0.0, output, 1.0, false, input1, false, input2); + csl::tensor_ops::gemmStridedBatched(cublasHandle, 0.0, output, 1.0, false, input1, !constTensor.empty(), input2); } } private: csl::Stream stream; csl::cublas::Handle cublasHandle; + csl::Tensor constTensor; }; }}} /* namespace cv::dnn::cuda4dnn */ diff --git a/modules/dnn/src/darknet/darknet_io.cpp b/modules/dnn/src/darknet/darknet_io.cpp index 520f3c94be..54140f8dc0 100644 --- a/modules/dnn/src/darknet/darknet_io.cpp +++ b/modules/dnn/src/darknet/darknet_io.cpp @@ -229,7 +229,7 @@ namespace cv { activation_param.set("negative_slope", 0.1f); activation_param.type = "ReLU"; } - else if (type == "swish") + else if (type == "swish" || type == "silu") // swish is an extension of silu. { activation_param.type = "Swish"; } diff --git a/modules/dnn/src/dnn_common.hpp b/modules/dnn/src/dnn_common.hpp index ae4d9c295e..f5c3cce7ca 100644 --- a/modules/dnn/src/dnn_common.hpp +++ b/modules/dnn/src/dnn_common.hpp @@ -13,6 +13,7 @@ namespace cv { namespace dnn { CV__DNN_INLINE_NS_BEGIN #define IS_DNN_OPENCL_TARGET(id) (id == DNN_TARGET_OPENCL || id == DNN_TARGET_OPENCL_FP16) +#define IS_DNN_CPU_TARGET(id) (id == DNN_TARGET_CPU) // TODO: add DNN_TARGET_CPU_FP16 Mutex& getInitializationMutex(); void initializeLayerFactory(); @@ -155,6 +156,18 @@ static inline std::string toString(const Mat& blob, const std::string& name = st CV__DNN_INLINE_NS_END + +namespace accessor { +class DnnNetAccessor +{ +public: + static inline Ptr& getImplPtrRef(Net& net) + { + return net.impl; + } +}; +} + }} // namespace #endif // __OPENCV_DNN_COMMON_HPP__ diff --git a/modules/dnn/src/dnn_params.cpp b/modules/dnn/src/dnn_params.cpp index 48e89c6fac..86a43db757 100644 --- a/modules/dnn/src/dnn_params.cpp +++ b/modules/dnn/src/dnn_params.cpp @@ -36,11 +36,7 @@ bool getParam_DNN_OPENCL_ALLOW_ALL_DEVICES() int getParam_DNN_BACKEND_DEFAULT() { static int PARAM_DNN_BACKEND_DEFAULT = (int)utils::getConfigurationParameterSizeT("OPENCV_DNN_BACKEND_DEFAULT", -#ifdef HAVE_INF_ENGINE - (size_t)DNN_BACKEND_INFERENCE_ENGINE -#else (size_t)DNN_BACKEND_OPENCV -#endif ); return PARAM_DNN_BACKEND_DEFAULT; } diff --git a/modules/dnn/src/factory.hpp b/modules/dnn/src/factory.hpp new file mode 100644 index 0000000000..0d8750a8c0 --- /dev/null +++ b/modules/dnn/src/factory.hpp @@ -0,0 +1,30 @@ +// 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_DNN_FACTORY_HPP +#define OPENCV_DNN_FACTORY_HPP + +#include "backend.hpp" + +namespace cv { namespace dnn_backend { + +class IDNNBackendFactory +{ +public: + virtual ~IDNNBackendFactory() {} + virtual std::shared_ptr createNetworkBackend() const = 0; +}; + +// +// PluginDNNBackendFactory is implemented in plugin_wrapper +// + +std::shared_ptr createPluginDNNBackendFactory(const std::string& baseName); + +/// @brief Returns createPluginDNNBackendFactory()->createNetworkBackend() +cv::dnn_backend::NetworkBackend& createPluginDNNNetworkBackend(const std::string& baseName); + +}} // namespace + +#endif // OPENCV_DNN_FACTORY_HPP diff --git a/modules/dnn/src/halide_scheduler.cpp b/modules/dnn/src/halide_scheduler.cpp index 78335ddaf9..679c5ab7d5 100644 --- a/modules/dnn/src/halide_scheduler.cpp +++ b/modules/dnn/src/halide_scheduler.cpp @@ -71,9 +71,17 @@ static void applyFuse(const FileNode& directive, Halide::Func& func) static void applyParallel(const FileNode& directive, Halide::Func& func) { std::string varName; - for (int i = 0, n = directive.size(); i < n; ++i) + if (directive.isSeq()) { - directive[i] >> varName; + for (int i = 0, n = directive.size(); i < n; ++i) + { + directive[i] >> varName; + func.parallel(Halide::Var(varName)); + } + } + else + { + directive >> varName; func.parallel(Halide::Var(varName)); } } @@ -81,9 +89,17 @@ static void applyParallel(const FileNode& directive, Halide::Func& func) static void applyUnroll(const FileNode& directive, Halide::Func& func) { std::string varName; - for (int i = 0, n = directive.size(); i < n; ++i) + if (directive.isSeq()) { - directive[i] >> varName; + for (int i = 0, n = directive.size(); i < n; ++i) + { + directive[i] >> varName; + func.unroll(Halide::Var(varName)); + } + } + else + { + directive >> varName; func.unroll(Halide::Var(varName)); } } diff --git a/modules/dnn/src/ie_ngraph.cpp b/modules/dnn/src/ie_ngraph.cpp index d2bb2f189c..a49976de74 100644 --- a/modules/dnn/src/ie_ngraph.cpp +++ b/modules/dnn/src/ie_ngraph.cpp @@ -35,6 +35,7 @@ static bool DNN_IE_SERIALIZE = utils::getConfigurationParameterBool("OPENCV_DNN_ static std::string kDefaultInpLayerName = "opencv_ngraph_empty_inp_layer_name"; static constexpr const char* kOpenCVLayersType = "opencv_ngraph_layer"; +#if INF_ENGINE_VER_MAJOR_LT(INF_ENGINE_RELEASE_2022_1) static std::string shapesToStr(const std::vector& mats) { std::ostringstream shapes; @@ -62,6 +63,7 @@ static void strToShapes(const std::string& str, std::vector ss >> shapes[i][j]; } } +#endif // OpenVINO < 2022.1 static std::vector > ngraphWrappers(const std::vector >& ptrs) @@ -76,6 +78,61 @@ ngraphWrappers(const std::vector >& ptrs) return wrappers; } +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + +class NgraphCustomOp: public ov::op::Op { +public: + OPENVINO_OP(kOpenCVLayersType); + + NgraphCustomOp(const ngraph::OutputVector& inputs, Ptr& cvLayer, const std::vector& outputs, const std::vector& internals): + Op(inputs), cvLayer(cvLayer), outputs(outputs), internals(internals) + { + constructor_validate_and_infer_types(); + } + + void validate_and_infer_types() override + { + set_output_size(outputs.size()); + for (int i = 0; i < outputs.size(); ++i) + { + ov::PartialShape shape; + for (int j = 0; j < outputs[i].dims; ++j) { + shape.push_back(outputs[i].size[j]); + } + set_output_type(i, get_input_element_type(0), shape); + } + } + + std::shared_ptr clone_with_new_inputs(const ngraph::OutputVector& new_args) const override + { + return std::make_shared(new_args, cvLayer, outputs, internals); + } + + bool has_evaluate() const { + return true; + } + + bool evaluate(ov::TensorVector& outputs, const ov::TensorVector& inputs) const override { + std::vector inpMats, outMats; + infEngineBlobsToMats(inputs, inpMats); + infEngineBlobsToMats(outputs, outMats); + try + { + cvLayer->forward(inpMats, outMats, internals); + return true; + } + catch (...) + { + return false; + } + } + + Ptr& cvLayer; + std::vector outputs, internals; +}; + +#else + class NgraphCustomOp: public ngraph::op::Op { public: const ngraph::NodeTypeInfo& get_type_info() const override @@ -204,14 +261,13 @@ public: std::vector outDataConfig; #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_2) InferenceEngine::SizeVector order; - size_t offset = std::numeric_limits::max(); for (int i = 0; i < node->get_input_size(); ++i) { InferenceEngine::DataConfig conf; auto shape = node->input_value(i).get_shape(); order.resize(shape.size()); std::iota(order.begin(), order.end(), 0); - conf.desc = InferenceEngine::TensorDesc(InferenceEngine::Precision::FP32, shape, {shape, order, offset}); + conf.desc = InferenceEngine::TensorDesc(InferenceEngine::Precision::FP32, shape, {shape, order}); inDataConfig.push_back(conf); } @@ -221,7 +277,7 @@ public: auto shape = node->output(i).get_shape(); order.resize(shape.size()); std::iota(order.begin(), order.end(), 0); - conf.desc = InferenceEngine::TensorDesc(InferenceEngine::Precision::FP32, shape, {shape, order, offset}); + conf.desc = InferenceEngine::TensorDesc(InferenceEngine::Precision::FP32, shape, {shape, order}); outDataConfig.push_back(conf); } #else @@ -325,7 +381,7 @@ public: #endif }; - +#endif // OpenVINO >= 2022.1 InfEngineNgraphNode::InfEngineNgraphNode(std::shared_ptr&& _node) : BackendNode(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH), node(std::move(_node)) {} @@ -338,15 +394,6 @@ InfEngineNgraphNode::InfEngineNgraphNode(const std::vector >& n std::vector& outputs, std::vector& internals) : BackendNode(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH), cvLayer(cvLayer_) { - std::ostringstream oss; - oss << (size_t)cvLayer.get(); - - std::map params = { - {"impl", oss.str()}, - {"outputs", shapesToStr(outputs)}, - {"internals", shapesToStr(internals)} - }; - #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2020_3) ngraph::OutputVector inp_nodes; #else @@ -354,7 +401,19 @@ InfEngineNgraphNode::InfEngineNgraphNode(const std::vector >& n #endif for (const auto& node : nodes) inp_nodes.emplace_back(node.dynamicCast()->node); + +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + node = std::make_shared(inp_nodes, cvLayer, outputs, internals); +#else + std::ostringstream oss; + oss << (size_t)cvLayer.get(); + std::map params = { + {"impl", oss.str()}, + {"outputs", shapesToStr(outputs)}, + {"internals", shapesToStr(internals)} + }; node = std::make_shared(inp_nodes, params); +#endif CV_Assert(!cvLayer->name.empty()); setName(cvLayer->name); @@ -384,7 +443,7 @@ void InfEngineNgraphNet::addOutput(const Ptr& node) CV_Assert(node); CV_Assert(node->node); const std::string& name = node->node->get_friendly_name(); - requestedOutputs.insert({name, node}); + requestedOutputs.insert({name, node.get()}); } void InfEngineNgraphNet::setNodePtr(std::shared_ptr* ptr) { @@ -458,6 +517,9 @@ void InfEngineNgraphNet::createNet(Target targetId) { CV_LOG_DEBUG(NULL, "DNN/NGRAPH: Add 'Result' output: " << output_node_it->first); CV_Assert(output_node_it->second); auto out = std::make_shared(output_node_it->second->node); +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + out->set_friendly_name(output_node_it->first + (output_node_it->second->node->get_output_size() == 1 ? "" : ".0")); +#endif outs.push_back(out); } CV_Assert_N(!inputs_vec.empty(), !outs.empty()); @@ -505,12 +567,20 @@ void InfEngineNgraphNet::createNet(Target targetId) { } } +#if INF_ENGINE_VER_MAJOR_LT(INF_ENGINE_RELEASE_2022_1) +static inline +InferenceEngine::Layout estimateLayout(size_t dims); +#endif + void InfEngineNgraphNet::init(Target targetId) { if (!hasNetOwner) { if (targetId == DNN_TARGET_OPENCL_FP16) { +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + ov::pass::ConvertFP32ToFP16().run_on_model(ngraph_function); +#else auto nodes = ngraph_function->get_ordered_ops(); for (auto& node : nodes) { @@ -534,6 +604,7 @@ void InfEngineNgraphNet::init(Target targetId) } } ngraph_function->validate_nodes_and_infer_types(); +#endif // OpenVINO >= 2022.1 } cnn = InferenceEngine::CNNNetwork(ngraph_function); @@ -581,20 +652,45 @@ void InfEngineNgraphNet::init(Target targetId) CV_Error(Error::StsNotImplemented, "Unknown target"); }; - if (!hasNetOwner) { - for (size_t i = 0; i < ngraph_function->get_output_size(); ++i) { - auto node = ngraph_function->output(i).get_node(); - for (size_t j = 0; j < node->get_input_size(); ++j) { - std::string name = node->input_value(j).get_node()->get_friendly_name(); - auto iter = requestedOutputs.find(name); - if (iter != requestedOutputs.end()) { - requestedOutputs.erase(iter); - cnn.addOutput(name); - } - } +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + auto model = cnn.getFunction(); + ov::preprocess::PrePostProcessor ppp(model); + int i = 0; + for (const auto& inp : model->inputs()) { // TODO: not sure why but ngraph_function->inputs() here causes segfault. + const std::string& name = inp.get_node()->get_friendly_name(); + auto blobIt = allBlobs.find(name); + CV_Assert(blobIt != allBlobs.end()); + + auto srcT = blobIt->second.get_element_type(); + if (srcT != inp.get_node()->get_element_type()) { + ppp.input(i++).tensor().set_element_type(srcT); } } + i = 0; + for (const auto& it : model->outputs()) + { + const std::string& name = it.get_node()->get_friendly_name(); + auto blobIt = allBlobs.find(name); + CV_Assert(blobIt != allBlobs.end()); + const auto& src = blobIt->second; + + // A workaround for single dimension output for which OpenCV allocates 2d Mat. + // For example, face-detection-0105 with Result of shape {200} while output blob is {200, 1} + auto outShape = it.get_partial_shape().get_max_shape(); + if (outShape != src.get_shape()) { + size_t sz = std::accumulate(outShape.begin(), outShape.end(), 1, std::multiplies()); + CV_Assert(sz == src.get_size()); + allBlobs[name] = ov::Tensor(src.get_element_type(), outShape, src.data()); + } + + ppp.output(i++).tensor().set_element_type(ov::element::f32); // Should be always FP32 + } + + ppp.build(); + +#else + for (const auto& it : cnn.getInputsInfo()) { const std::string& name = it.first; @@ -608,8 +704,16 @@ void InfEngineNgraphNet::init(Target targetId) const std::string& name = it.first; auto blobIt = allBlobs.find(name); CV_Assert(blobIt != allBlobs.end()); + InferenceEngine::TensorDesc& desc = blobIt->second->getTensorDesc(); + + auto outShape = it.second->getDims(); + if (outShape != desc.getDims()) { + desc.reshape(outShape, estimateLayout(outShape.size())); + } + it.second->setPrecision(blobIt->second->getTensorDesc().getPrecision()); // Should be always FP32 } +#endif // OpenVINO >= 2022.1 initPlugin(cnn); } @@ -661,6 +765,9 @@ void InfEngineNgraphNet::initPlugin(InferenceEngine::CNNNetwork& net) const std::string& libName = candidates[i]; try { +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + ie.add_extension(libName); +#else InferenceEngine::IExtensionPtr extension = #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_4) std::make_shared(libName); @@ -669,6 +776,7 @@ void InfEngineNgraphNet::initPlugin(InferenceEngine::CNNNetwork& net) #endif ie.AddExtension(extension, "CPU"); +#endif CV_LOG_INFO(NULL, "DNN-IE: Loaded extension plugin: " << libName); found = true; break; @@ -679,6 +787,7 @@ void InfEngineNgraphNet::initPlugin(InferenceEngine::CNNNetwork& net) { CV_LOG_WARNING(NULL, "DNN-IE: Can't load extension plugin (extra layers for some networks). Specify path via OPENCV_DNN_IE_EXTRA_PLUGIN_PATH parameter"); } +#if INF_ENGINE_VER_MAJOR_LT(INF_ENGINE_RELEASE_2022_1) // Some of networks can work without a library of extra layers. // OpenCV fallbacks as extensions. try @@ -689,12 +798,17 @@ void InfEngineNgraphNet::initPlugin(InferenceEngine::CNNNetwork& net) { CV_LOG_INFO(NULL, "DNN-IE: Can't register OpenCV custom layers nGraph extension: " << e.what()); } +#endif // OpenVINO < 2022.1 #ifndef _WIN32 // Limit the number of CPU threads. if (device_name == "CPU") +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + ie.set_property(device_name, ov::inference_num_threads(getNumThreads())); +#else ie.SetConfig({{ InferenceEngine::PluginConfigParams::KEY_CPU_THREADS_NUM, format("%d", getNumThreads()), }}, device_name); +#endif // OpenVINO >= 2022.1 #endif #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_2) if (device_name.find("GPU") == 0) @@ -707,9 +821,13 @@ void InfEngineNgraphNet::initPlugin(InferenceEngine::CNNNetwork& net) if (!cache_path.empty() && cache_path != "disabled") { CV_LOG_INFO(NULL, "OpenCV/nGraph: using GPU kernels cache: " << cache_path); +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + ie.set_property(device_name, ov::cache_dir(cache_path)); +#else ie.SetConfig({{ InferenceEngine::PluginConfigParams::KEY_CACHE_DIR, cache_path, }}, device_name); +#endif // OpenVINO >= 2022.1 } } #endif @@ -717,9 +835,9 @@ void InfEngineNgraphNet::initPlugin(InferenceEngine::CNNNetwork& net) std::map config; if (device_name == "MYRIAD" || device_name == "HDDL") { #if INF_ENGINE_VER_MAJOR_GT(INF_ENGINE_RELEASE_2020_4) - config.emplace("MYRIAD_DETECT_NETWORK_BATCH", CONFIG_VALUE(NO)); + config.emplace("MYRIAD_DETECT_NETWORK_BATCH", "NO"); #else - config.emplace("VPU_DETECT_NETWORK_BATCH", CONFIG_VALUE(NO)); + config.emplace("VPU_DETECT_NETWORK_BATCH", "NO"); #endif } @@ -758,16 +876,17 @@ bool NgraphBackendLayer::getMemoryShapes(const std::vector &inputs, std::vector &outputs, std::vector &internals) const { - InferenceEngine::ICNNNetwork::InputShapes inShapes = t_net.getInputShapes(); - InferenceEngine::ICNNNetwork::InputShapes::iterator itr; + auto ngraphFunction = t_net.getFunction(); bool equal_flag = true; - size_t i = 0; - for (itr = inShapes.begin(); itr != inShapes.end(); ++itr) + std::map > inShapes; + int i = 0; + for (const auto& inp : ngraphFunction->get_parameters()) { - InferenceEngine::SizeVector currentInShape(inputs[i].begin(), inputs[i].end()); - if (itr->second != currentInShape) + std::vector oldShape = inp->get_shape(); + std::vector newShape(inputs[i].begin(), inputs[i].end()); + inShapes.insert({inp->get_friendly_name(), newShape}); + if (oldShape != newShape) { - itr->second = currentInShape; equal_flag = false; } i++; @@ -778,7 +897,18 @@ bool NgraphBackendLayer::getMemoryShapes(const std::vector &inputs, InferenceEngine::CNNNetwork curr_t_net(t_net); curr_t_net.reshape(inShapes); } +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + std::vector dims; + for (const auto& it : ngraphFunction->outputs()) { + if (it.get_node()->get_friendly_name() == name) { + dims = it.get_partial_shape().get_max_shape(); + } + } + if (dims.empty()) + CV_Error(Error::StsError, format("Unable find result with name %s", name.c_str())); +#else std::vector dims = t_net.getOutputsInfo()[name]->getDims(); +#endif outputs.push_back(MatShape(dims.begin(), dims.end())); return false; } @@ -796,6 +926,21 @@ void NgraphBackendLayer::forward(InputArrayOfArrays inputs, OutputArrayOfArrays CV_Error(Error::StsInternal, "Choose Inference Engine as a preferable backend."); } +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + +ov::Tensor wrapToNgraphBlob(const Mat& m) { + std::vector shape = getShape(m); + if (m.type() == CV_32F) + return ov::Tensor(ov::element::f32, shape, m.data); + else if (m.type() == CV_8U) + return ov::Tensor(ov::element::u8, shape, m.data); + else if (m.type() == CV_32SC1) + return ov::Tensor(ov::element::i32, shape, m.data); + else + CV_Error(Error::StsNotImplemented, format("Unsupported data type %s", typeToString(m.type()).c_str())); +} + +#else static InferenceEngine::Layout estimateLayout(int dims) { @@ -824,19 +969,6 @@ InferenceEngine::Layout estimateLayout(const Mat& m) return estimateLayout(m.dims); } -static InferenceEngine::DataPtr wrapToInfEngineDataNode(const Mat& m, const std::string& name = "") -{ - std::vector shape = getShape(m); - if (m.type() == CV_32F) - return InferenceEngine::DataPtr(new InferenceEngine::Data(name, - {InferenceEngine::Precision::FP32, shape, estimateLayout(m)})); - else if (m.type() == CV_8U) - return InferenceEngine::DataPtr(new InferenceEngine::Data(name, - {InferenceEngine::Precision::U8, shape, estimateLayout(m)})); - else - CV_Error(Error::StsNotImplemented, format("Unsupported data type %s", typeToString(m.type()).c_str())); -} - InferenceEngine::Blob::Ptr wrapToNgraphBlob(const Mat& m, const std::vector& shape, InferenceEngine::Layout layout) { @@ -846,6 +978,9 @@ InferenceEngine::Blob::Ptr wrapToNgraphBlob(const Mat& m, const std::vector( {InferenceEngine::Precision::U8, shape, layout}, (uint8_t*)m.data); + else if (m.type() == CV_32SC1) + return InferenceEngine::make_shared_blob( + {InferenceEngine::Precision::I32, shape, layout}, (int32_t*)m.data); else CV_Error(Error::StsNotImplemented, format("Unsupported data type %s", typeToString(m.type()).c_str())); } @@ -856,12 +991,15 @@ InferenceEngine::Blob::Ptr wrapToNgraphBlob(const Mat& m, InferenceEngine::Layou return wrapToNgraphBlob(m, shape, layout); } +InferenceEngine::Blob::Ptr wrapToNgraphBlob(const Mat& m) { return wrapToNgraphBlob(m, estimateLayout(m)); } + +#endif // OpenVINO >= 2022.1 + NgraphBackendWrapper::NgraphBackendWrapper(int targetId, const cv::Mat& m) : BackendWrapper(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, targetId) , host((Mat*)&m) { - dataPtr = wrapToInfEngineDataNode(m); - blob = wrapToNgraphBlob(m, estimateLayout(m)); + blob = wrapToNgraphBlob(m); } NgraphBackendWrapper::NgraphBackendWrapper(Ptr wrapper) @@ -869,8 +1007,7 @@ NgraphBackendWrapper::NgraphBackendWrapper(Ptr wrapper) { Ptr ieWrapper = wrapper.dynamicCast(); CV_Assert(!ieWrapper.empty()); - InferenceEngine::DataPtr srcData = ieWrapper->dataPtr; - dataPtr = InferenceEngine::DataPtr(new InferenceEngine::Data(srcData->getName(), srcData->getTensorDesc())); + name = ieWrapper->name; blob = ieWrapper->blob; } @@ -896,6 +1033,12 @@ void NgraphBackendWrapper::setHostDirty() //CV_Error(Error::StsNotImplemented, ""); } +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) +ov::Tensor copyBlob(const ov::Tensor& blob) +{ + return ov::Tensor(blob.get_element_type(), blob.get_shape()); +} +#else InferenceEngine::Blob::Ptr copyBlob(const InferenceEngine::Blob::Ptr& blob) { InferenceEngine::Blob::Ptr copy; @@ -919,96 +1062,13 @@ InferenceEngine::Blob::Ptr copyBlob(const InferenceEngine::Blob::Ptr& blob) return copy; } -InferenceEngine::DataPtr ngraphDataNode(const Ptr& ptr) -{ - CV_Assert(!ptr.empty()); - Ptr p = ptr.dynamicCast(); - CV_Assert(!p.empty()); - return p->dataPtr; -} - -static -InferenceEngine::Blob::Ptr reallocateBlob(Mat &m, const InferenceEngine::TensorDesc& description) -{ - auto dims = description.getDims(); - auto layout = estimateLayout(dims.size()); - MatShape matShape(dims.begin(), dims.end()); - if (description.getPrecision() == InferenceEngine::Precision::FP32) - { - m.create(matShape, CV_32FC1); - return InferenceEngine::make_shared_blob( - {description.getPrecision(), dims, layout}, (float*)m.data); - } - else if (description.getPrecision() == InferenceEngine::Precision::I32) - { - m.create(matShape, CV_32SC1); - return InferenceEngine::make_shared_blob( - {description.getPrecision(), dims, layout}, (int*)m.data); - } - else if (description.getPrecision() == InferenceEngine::Precision::U8) - { - m.create(matShape, CV_8UC1); - return InferenceEngine::make_shared_blob( - {description.getPrecision(), dims, layout}, (uchar*)m.data); - } - std::ostringstream msg; - msg << "Unsupported IE precision: " << description.getPrecision(); - CV_Error(Error::StsNotImplemented, msg.str()); -} - -InferenceEngine::DataPtr ngraphDataOutputNode( - const Ptr& ptr, - const InferenceEngine::TensorDesc& description, - const std::string name) -{ - CV_Assert(!ptr.empty()); - Ptr p = ptr.dynamicCast(); - CV_Assert(!p.empty()); - NgraphBackendWrapper& w = *p; - const InferenceEngine::TensorDesc& blobDesc = w.blob.get()->getTensorDesc(); - auto dims = description.getDims(); - bool reallocate = false; - if (blobDesc.getPrecision() != description.getPrecision()) - { - reallocate = true; - CV_LOG_WARNING(NULL, "Reallocate output '" << name << "' blob due to wrong precision: " << blobDesc.getPrecision() << " => " << description.getPrecision() << " ndims=" << dims.size()); - } - if (dims.size() != blobDesc.getDims().size()) - { - reallocate = true; - CV_LOG_WARNING(NULL, "Reallocate output '" << name << "' blob due to wrong dims: " << blobDesc.getDims().size() << " => " << dims.size()); - } - if (reallocate) - { - auto layout = estimateLayout(dims.size()); - w.dataPtr = InferenceEngine::DataPtr(new InferenceEngine::Data(name, - {description.getPrecision(), dims, layout})); - w.blob = reallocateBlob(*w.host, description); - } - return w.dataPtr; -} - -void forwardNgraph(const std::vector >& outBlobsWrappers, - Ptr& node, bool isAsync) -{ - CV_Assert(!node.empty()); - Ptr ieNode = node.dynamicCast(); - CV_Assert(!ieNode.empty()); - ieNode->net->forward(outBlobsWrappers, isAsync); -} +#endif // OpenVINO < 2022.1 void InfEngineNgraphNet::reset() { allBlobs.clear(); infRequests.clear(); isInit = false; - - outputsDesc.clear(); - for (const auto& it : cnn.getOutputsInfo()) - { - const std::string& name = it.first; - outputsDesc.insert({name, it.second->getTensorDesc()}); - } } void InfEngineNgraphNet::addBlobs(const std::vector >& ptrs) @@ -1016,7 +1076,7 @@ void InfEngineNgraphNet::addBlobs(const std::vector >& p auto wrappers = ngraphWrappers(ptrs); for (const auto& wrapper : wrappers) { - std::string name = wrapper->dataPtr->getName(); + std::string name = wrapper->name; name = name.empty() ? kDefaultInpLayerName : name; allBlobs.insert({name, wrapper->blob}); } @@ -1031,27 +1091,10 @@ void InfEngineNgraphNet::NgraphReqWrapper::makePromises(const std::vectorfutureMat = outProms[i].getArrayResult(); - outsNames[i] = outs[i]->dataPtr->getName(); + outsNames[i] = outs[i]->name; } } -Mat ngraphBlobToMat(const InferenceEngine::Blob::Ptr& blob) -{ - std::vector dims = blob->getTensorDesc().getDims(); - std::vector size(dims.begin(), dims.end()); - auto precision = blob->getTensorDesc().getPrecision(); - - int type = -1; - switch (precision) - { - case InferenceEngine::Precision::FP32: type = CV_32F; break; - case InferenceEngine::Precision::U8: type = CV_8U; break; - default: - CV_Error(Error::StsNotImplemented, "Unsupported blob precision"); - } - return Mat(size, type, (void*)blob->buffer()); -} - void InfEngineNgraphNet::forward(const std::vector >& outBlobsWrappers, bool isAsync) { CV_LOG_DEBUG(NULL, "InfEngineNgraphNet::forward(" << (isAsync ? "async" : "sync") << ")"); @@ -1079,6 +1122,25 @@ void InfEngineNgraphNet::forward(const std::vector >& outBlo } infRequests.push_back(reqWrapper); +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + int i = 0; + for (const auto& it : netExec.inputs()) + { + const std::string& name = it.get_node()->get_friendly_name(); + auto blobIt = allBlobs.find(name); + CV_Assert(blobIt != allBlobs.end()); + reqWrapper->req.set_input_tensor(i++, isAsync ? copyBlob(blobIt->second) : blobIt->second); + } + + i = 0; + for (const auto& it : netExec.outputs()) + { + const std::string& name = it.get_node()->get_friendly_name(); + auto blobIt = allBlobs.find(name); + CV_Assert(blobIt != allBlobs.end()); + reqWrapper->req.set_output_tensor(i++, isAsync ? copyBlob(blobIt->second) : blobIt->second); + } +#else InferenceEngine::BlobMap inpBlobs, outBlobs; for (const auto& it : cnn.getInputsInfo()) { @@ -1096,6 +1158,53 @@ void InfEngineNgraphNet::forward(const std::vector >& outBlo } reqWrapper->req.SetInput(inpBlobs); reqWrapper->req.SetOutput(outBlobs); +#endif + +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + if (isAsync) { + bool* isReady = &reqWrapper->isReady; + auto* promises = &reqWrapper->outProms; + auto* req = &reqWrapper->req; + reqWrapper->req.set_callback([isReady, promises, req](std::exception_ptr ex) { + CV_LOG_DEBUG(NULL, "DNN(nGraph): completionCallback()"); + + size_t processedOutputs = 0; + try + { + for (; processedOutputs < promises->size(); ++processedOutputs) + { + Mat m = infEngineBlobToMat(req->get_output_tensor(processedOutputs)); + + try + { + (*promises)[processedOutputs].setValue(m.clone()); + } + catch (...) + { + try { + (*promises)[processedOutputs].setException(std::current_exception()); + } catch(...) { + CV_LOG_ERROR(NULL, "DNN: Exception occurred during async inference exception propagation"); + } + } + } + } + catch (...) + { + std::exception_ptr e = std::current_exception(); + for (; processedOutputs < promises->size(); ++processedOutputs) + { + try { + (*promises)[processedOutputs].setException(e); + } catch(...) { + CV_LOG_ERROR(NULL, "DNN: Exception occurred during async inference exception propagation"); + } + } + } + *isReady = true; + }); + } +#else // OpenVINO >= 2022.1 #if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2021_4) InferenceEngine::InferRequest infRequest = reqWrapper->req; @@ -1134,7 +1243,7 @@ void InfEngineNgraphNet::forward(const std::vector >& outBlo for (; processedOutputs < wrapper.outProms.size(); ++processedOutputs) { const std::string& name = wrapper.outsNames[processedOutputs]; - Mat m = ngraphBlobToMat(wrapper.req.GetBlob(name)); + Mat m = infEngineBlobToMat(wrapper.req.GetBlob(name)); try { @@ -1166,8 +1275,34 @@ void InfEngineNgraphNet::forward(const std::vector >& outBlo wrapper.isReady = true; } ); +#endif // OpenVINO >= 2022.1 } +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + if (isAsync) + { + // Copy actual data to infer request's input blobs. + int i = 0; + for (const auto& it : cnn.getFunction()->get_parameters()) + { + const std::string& name = it->get_friendly_name(); + auto blobIt = allBlobs.find(name); + Mat srcMat = infEngineBlobToMat(blobIt->second); + Mat dstMat = infEngineBlobToMat(reqWrapper->req.get_input_tensor(i++)); + srcMat.copyTo(dstMat); + } + + // Set promises to output blobs wrappers. + reqWrapper->makePromises(outBlobsWrappers); + + reqWrapper->isReady = false; + reqWrapper->req.start_async(); + } + else + { + reqWrapper->req.infer(); + } +#else if (isAsync) { // Copy actual data to infer request's input blobs. @@ -1175,8 +1310,8 @@ void InfEngineNgraphNet::forward(const std::vector >& outBlo { const std::string& name = it.first; auto blobIt = allBlobs.find(name); - Mat srcMat = ngraphBlobToMat(blobIt->second); - Mat dstMat = ngraphBlobToMat(reqWrapper->req.GetBlob(name)); + Mat srcMat = infEngineBlobToMat(blobIt->second); + Mat dstMat = infEngineBlobToMat(reqWrapper->req.GetBlob(name)); srcMat.copyTo(dstMat); } @@ -1190,14 +1325,9 @@ void InfEngineNgraphNet::forward(const std::vector >& outBlo { reqWrapper->req.Infer(); } +#endif // OpenVINO >= 2022.1 } -#else -void forwardNgraph(const std::vector >& outBlobsWrappers, - Ptr& node, bool isAsync) -{ - CV_Assert(false && "nGraph is not enabled in this OpenCV build"); -} #endif }} diff --git a/modules/dnn/src/ie_ngraph.hpp b/modules/dnn/src/ie_ngraph.hpp index 0d287a22a5..09afc7f117 100644 --- a/modules/dnn/src/ie_ngraph.hpp +++ b/modules/dnn/src/ie_ngraph.hpp @@ -68,7 +68,11 @@ public: std::unordered_map* > all_nodes; InferenceEngine::ExecutableNetwork netExec; +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + std::map allBlobs; +#else InferenceEngine::BlobMap allBlobs; +#endif std::string device_name; bool isInit = false; @@ -87,9 +91,7 @@ public: InferenceEngine::CNNNetwork cnn; bool hasNetOwner; - std::unordered_map > requestedOutputs; - - std::map outputsDesc; + std::unordered_map requestedOutputs; }; class InfEngineNgraphNode : public BackendNode @@ -123,17 +125,15 @@ public: virtual void setHostDirty() CV_OVERRIDE; Mat* host; - InferenceEngine::DataPtr dataPtr; + std::string name; +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + ov::Tensor blob; +#else InferenceEngine::Blob::Ptr blob; +#endif AsyncArray futureMat; }; -InferenceEngine::DataPtr ngraphDataNode(const Ptr& ptr); -InferenceEngine::DataPtr ngraphDataOutputNode( - const Ptr& ptr, - const InferenceEngine::TensorDesc& description, - const std::string name); - // This is a fake class to run networks from Model Optimizer. Objects of that // class simulate responses of layers are imported by OpenCV and supported by // Inference Engine. The main difference is that they do not perform forward pass. @@ -158,9 +158,6 @@ private: #endif // HAVE_DNN_NGRAPH -void forwardNgraph(const std::vector >& outBlobsWrappers, - Ptr& node, bool isAsync); - }} // namespace cv::dnn diff --git a/modules/dnn/src/init.cpp b/modules/dnn/src/init.cpp index e3ce6de40d..d20f9dff8d 100644 --- a/modules/dnn/src/init.cpp +++ b/modules/dnn/src/init.cpp @@ -42,7 +42,9 @@ #include "precomp.hpp" #include +#if !defined(BUILD_PLUGIN) #include +#endif namespace cv { namespace dnn { @@ -58,6 +60,7 @@ Mutex& getInitializationMutex() // force initialization (single-threaded environment) Mutex* __initialization_mutex_initializer = &getInitializationMutex(); +#if !defined(BUILD_PLUGIN) namespace { using namespace google::protobuf; class ProtobufShutdown { @@ -71,12 +74,15 @@ public: } }; } // namespace +#endif void initializeLayerFactory() { CV_TRACE_FUNCTION(); +#if !defined(BUILD_PLUGIN) static ProtobufShutdown protobufShutdown; CV_UNUSED(protobufShutdown); +#endif CV_DNN_REGISTER_LAYER_CLASS(Slice, SliceLayer); CV_DNN_REGISTER_LAYER_CLASS(Split, SplitLayer); @@ -147,6 +153,7 @@ void initializeLayerFactory() CV_DNN_REGISTER_LAYER_CLASS(Const, ConstLayer); CV_DNN_REGISTER_LAYER_CLASS(Arg, ArgLayer); CV_DNN_REGISTER_LAYER_CLASS(Reciprocal, ReciprocalLayer); + CV_DNN_REGISTER_LAYER_CLASS(Gather, GatherLayer); CV_DNN_REGISTER_LAYER_CLASS(Crop, CropLayer); CV_DNN_REGISTER_LAYER_CLASS(Eltwise, EltwiseLayer); @@ -174,6 +181,10 @@ void initializeLayerFactory() CV_DNN_REGISTER_LAYER_CLASS(GRU, GRULayer); CV_DNN_REGISTER_LAYER_CLASS(CumSum, CumSumLayer); + CV_DNN_REGISTER_LAYER_CLASS(Scatter, ScatterLayer); + CV_DNN_REGISTER_LAYER_CLASS(ScatterND, ScatterNDLayer); + CV_DNN_REGISTER_LAYER_CLASS(Tile, TileLayer); + CV_DNN_REGISTER_LAYER_CLASS(Quantize, QuantizeLayer); CV_DNN_REGISTER_LAYER_CLASS(Dequantize, DequantizeLayer); CV_DNN_REGISTER_LAYER_CLASS(Requantize, RequantizeLayer); diff --git a/modules/dnn/src/int8layers/convolution_layer.cpp b/modules/dnn/src/int8layers/convolution_layer.cpp index dfa58b09fe..728ef24d91 100644 --- a/modules/dnn/src/int8layers/convolution_layer.cpp +++ b/modules/dnn/src/int8layers/convolution_layer.cpp @@ -41,7 +41,7 @@ public: BaseConvolutionLayerInt8Impl(const LayerParams ¶ms) { setParamsFrom(params); - getConvolutionKernelParams(params, kernel_size, pads_begin, pads_end, strides, dilations, padMode, adjust_pads); + getConvolutionKernelParams(params, kernel_size, pads_begin, pads_end, strides, dilations, padMode, adjust_pads, useWinograd); numOutput = params.get("num_output"); int ngroups = params.get("group", 1); @@ -579,13 +579,14 @@ public: bool is1x1_; bool useAVX2; bool useAVX512; + bool useLASX; int blk_size_cn; int inpZp, outZp; const std::vector* multiplier; ParallelConv() : input_(0), weights_(0), output_(0), ngroups_(0), nstripes_(0), - biasvec_(0), activLUT_(0), activ_(0), is1x1_(false), useAVX2(false), useAVX512(false) + biasvec_(0), activLUT_(0), activ_(0), is1x1_(false), useAVX2(false), useAVX512(false), useLASX(false) , blk_size_cn(0), inpZp(0), outZp(0), multiplier(0) {} @@ -641,6 +642,8 @@ public: p.useAVX2 = checkHardwareSupport(CPU_AVX2) && isConv2D; p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX && isConv2D; + p.useLASX = checkHardwareSupport(CPU_LASX) && isConv2D; + int kernel_d = isConv3D? kernel_size[0] : 1; int kernel_h = isConv1D? 1 : kernel_size[kernel_size.size() - 2]; int kernel_w = kernel_size.back(); @@ -837,6 +840,13 @@ public: stride_h, stride_w, dilation_h, dilation_w, pad_t, pad_l, biasptr, multptr, inptr_, height, width, outptr_, out_d, outH, outW, inpZp, outZp); else + #endif + #if CV_TRY_LASX + if(useLASX) + opt_LASX::fastDepthwiseConv(wptr, kernel_h, kernel_w, + stride_h, stride_w, dilation_h, dilation_w, pad_t, pad_l, + biasptr, multptr, inptr_, height, width, outptr_, out_d, outH, outW, inpZp, outZp); + else #endif { const int8_t w00_ = wptr[0], w01_ = wptr[1], w02_ = wptr[2], @@ -1210,6 +1220,12 @@ public: opt_AVX2::fastConv(wptr, wstep, biasptr, rowbuf0, data_out0 + ofs0, outShape, bsz, vsz, vsz_a, outZp, multptr, cn0 == 0, cn1 == inpCn); else + #endif + #if CV_TRY_LASX + if(useLASX) + opt_LASX::fastConv(wptr, wstep, biasptr, rowbuf0, data_out0 + ofs0, + outShape, bsz, vsz, vsz_a, outZp, multptr, cn0 == 0, cn1 == inpCn); + else #endif for( int i = 0; i < outCn; i += 2 ) { diff --git a/modules/dnn/src/int8layers/fully_connected_layer.cpp b/modules/dnn/src/int8layers/fully_connected_layer.cpp index dc759ebdbc..867f002dd4 100644 --- a/modules/dnn/src/int8layers/fully_connected_layer.cpp +++ b/modules/dnn/src/int8layers/fully_connected_layer.cpp @@ -226,7 +226,7 @@ public: { public: FullyConnected() : srcMat(0), weights(0), biasMat(0), outputMultiplier(0), activationLUT(0), activ(0), - dstMat(0), nstripes(0), outZp(0), useAVX2(false), useAVX512(false) {} + dstMat(0), nstripes(0), outZp(0), useAVX2(false), useAVX512(false), useLASX(false) {} static void run(const Mat& srcMat, const Mat& weights, const Mat& biasMat, const Mat& outputMultiplier, const Mat& activationLUT, Mat& dstMat, const ActivationLayerInt8* activ, int nstripes, int outZp) @@ -250,6 +250,7 @@ public: p.activ = !activationLUT.empty() ? activ : 0; p.useAVX2 = checkHardwareSupport(CPU_AVX2); p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX; + p.useLASX = checkHardwareSupport(CPU_LASX); parallel_for_(Range(0, nstripes), p, nstripes); } @@ -294,6 +295,11 @@ public: if( useAVX2 ) opt_AVX2::fastGEMM1T( sptr, wptr, wstep, biasptr, multptr, dptr, nw, vecsize, outZp ); else + #endif + #if CV_TRY_LASX + if( useLASX ) + opt_LASX::fastGEMM1T( sptr, wptr, wstep, biasptr, multptr, dptr, nw, vecsize, outZp ); + else #endif { int i = 0; @@ -349,6 +355,7 @@ public: int nstripes, outZp; bool useAVX2; bool useAVX512; + bool useLASX; }; void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE diff --git a/modules/dnn/src/int8layers/layers_common.hpp b/modules/dnn/src/int8layers/layers_common.hpp index cb185a9eda..5fdafbeab8 100644 --- a/modules/dnn/src/int8layers/layers_common.hpp +++ b/modules/dnn/src/int8layers/layers_common.hpp @@ -23,7 +23,7 @@ namespace dnn { void getConvolutionKernelParams(const LayerParams ¶ms, std::vector& kernel, std::vector& pads_begin, std::vector& pads_end, std::vector& strides, std::vector& dilations, - cv::String &padMode, std::vector& adjust_pads); + cv::String &padMode, std::vector& adjust_pads, bool& useWinograd); void getPoolingKernelParams(const LayerParams ¶ms, std::vector& kernel, std::vector& globalPooling, std::vector& pads_begin, std::vector& pads_end, std::vector& strides, cv::String &padMode); diff --git a/modules/dnn/src/int8layers/layers_common.simd.hpp b/modules/dnn/src/int8layers/layers_common.simd.hpp index bf6149e5c9..1b3ac7a4b8 100644 --- a/modules/dnn/src/int8layers/layers_common.simd.hpp +++ b/modules/dnn/src/int8layers/layers_common.simd.hpp @@ -633,5 +633,629 @@ void fastGEMM1T( const int8_t* vec, const int8_t* weights, } #endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY + +#if !defined(CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY) && CV_LASX + +inline __m256i _v256_fmadds8_s32(const __m256i& a, const __m256i& b, const __m256i& c) +{ + __m256i vzero = __lasx_xvreplgr2vr_d(0); + __m256i even_ab = __lasx_xvmaddwev_h_b(vzero, a, b); + __m256i madd_ab = __lasx_xvmaddwod_h_b(even_ab, a, b); + + __m256i even_madd_ab = __lasx_xvsrai_w(__lasx_xvslli_w(madd_ab, 16), 16); + __m256i odd_madd_ab = __lasx_xvsrai_w(madd_ab, 16); + + return __lasx_xvadd_w(__lasx_xvadd_w(even_madd_ab, odd_madd_ab), c); +} + +enum { FASCONV_BASE_VECSZ = 4 }; + +void fastConv( const int8_t* weights, size_t wstep, const int* bias, + const int8_t* rowbuf, int* output, const int* outShape, + int blockSize, int vecsize, int vecsize_aligned, int outZp, + const float* multiplier, bool initOutput, bool finalOutput ) +{ + int outCn = outShape[1]; + size_t outPlaneSize = outShape[2]*outShape[3]; + int CV_DECL_ALIGNED(16) maskbuf[FASCONV_BASE_VECSZ] = {0}; + int rsz = blockSize % FASCONV_BASE_VECSZ; + for( int i = 0; i < rsz; i++ ) + maskbuf[FASCONV_BASE_VECSZ - i - 1] = -1; + __m128i mask = __lsx_vld((const float*)maskbuf, 0); + + // now compute dot product of the weights + // and im2row-transformed part of the tensor + for( int i = 0; i < outCn; i += 3 ) + { + const int8_t* wptr0 = weights + i*wstep; + const int8_t* wptr1 = wptr0 + wstep; + const int8_t* wptr2 = wptr1 + wstep; + int* outptr0 = output + i*outPlaneSize; + int* outptr1 = outptr0 + outPlaneSize; + int* outptr2 = outptr1 + outPlaneSize; + int bias0 = bias[i], bias1 = bias[i+1], bias2 = bias[i+2]; + float mult0 = multiplier[i], mult1 = multiplier[i+1], mult2 = multiplier[i+2]; + + if( i+2 >= outCn ) + { + wptr2 = wptr1; + outptr2 = outptr1; + bias2 = bias1; + mult2 = mult1; + + if( i+1 >= outCn ) + { + wptr2 = wptr1 = wptr0; + outptr2 = outptr1 = outptr0; + bias2 = bias1 = bias0; + mult2 = mult1 = mult0; + } + } + int j = 0; + for( ; j < blockSize; j += FASCONV_BASE_VECSZ ) + { + bool tail = false; + if (j + FASCONV_BASE_VECSZ > blockSize) + { + if (j == 0) + break; + j = blockSize - FASCONV_BASE_VECSZ; + tail = true; + } + int k = 0; + const int8_t* rptr = rowbuf + j*vecsize_aligned; + + __m256i vs00 = __lasx_xvreplgr2vr_d(0), vs01 = __lasx_xvreplgr2vr_d(0), + vs02 = __lasx_xvreplgr2vr_d(0), vs03 = __lasx_xvreplgr2vr_d(0), + vs10 = __lasx_xvreplgr2vr_d(0), vs11 = __lasx_xvreplgr2vr_d(0), + vs12 = __lasx_xvreplgr2vr_d(0), vs13 = __lasx_xvreplgr2vr_d(0), + vs20 = __lasx_xvreplgr2vr_d(0), vs21 = __lasx_xvreplgr2vr_d(0), + vs22 = __lasx_xvreplgr2vr_d(0), vs23 = __lasx_xvreplgr2vr_d(0); + + for (; k < vecsize; k += 32, rptr += 32 ) + { + __m256i w0 = __lasx_xvld((const __m256i*)(wptr0 + k), 0); + __m256i w1 = __lasx_xvld((const __m256i*)(wptr1 + k), 0); + __m256i w2 = __lasx_xvld((const __m256i*)(wptr2 + k), 0); + __m256i r0 = __lasx_xvld((const __m256i*)(rptr), 0); + + vs00 = _v256_fmadds8_s32(w0, r0, vs00); + vs10 = _v256_fmadds8_s32(w1, r0, vs10); + vs20 = _v256_fmadds8_s32(w2, r0, vs20); + + r0 = __lasx_xvld((const __m256i*)(rptr + vecsize_aligned), 0); + vs01 = _v256_fmadds8_s32(w0, r0, vs01); + vs11 = _v256_fmadds8_s32(w1, r0, vs11); + vs21 = _v256_fmadds8_s32(w2, r0, vs21); + + r0 = __lasx_xvld((const __m256i*)(rptr + vecsize_aligned*2), 0); + vs02 = _v256_fmadds8_s32(w0, r0, vs02); + vs12 = _v256_fmadds8_s32(w1, r0, vs12); + vs22 = _v256_fmadds8_s32(w2, r0, vs22); + + r0 = __lasx_xvld((const __m256i*)(rptr + vecsize_aligned*3), 0); + vs03 = _v256_fmadds8_s32(w0, r0, vs03); + vs13 = _v256_fmadds8_s32(w1, r0, vs13); + vs23 = _v256_fmadds8_s32(w2, r0, vs23); + } + + /*t0*/ + __m256i vs00_hadd_w = __lasx_xvhaddw_d_w(vs00, vs00); + __m256i vs00_hadd_d = __lasx_xvhaddw_q_d(vs00_hadd_w, vs00_hadd_w); + + __m256i vs01_hadd_w = __lasx_xvhaddw_d_w(vs01, vs01); + __m256i vs01_hadd_d = __lasx_xvhaddw_q_d(vs01_hadd_w, vs01_hadd_w); + + __m256i vs02_hadd_w = __lasx_xvhaddw_d_w(vs02, vs02); + __m256i vs02_hadd_d = __lasx_xvhaddw_q_d(vs02_hadd_w, vs02_hadd_w); + + __m256i vs03_hadd_w = __lasx_xvhaddw_d_w(vs03, vs03); + __m256i vs03_hadd_d = __lasx_xvhaddw_q_d(vs03_hadd_w, vs03_hadd_w); + + __m256i vs01_vs00 = __lasx_xvpackev_w(vs01_hadd_d, vs00_hadd_d); + __m256i vs03_vs02 = __lasx_xvpackev_w(vs03_hadd_d, vs02_hadd_d); + __m256i t0 = __lasx_xvpackev_d(vs03_vs02, vs01_vs00); + + /*t1*/ + __m256i vs10_hadd_w = __lasx_xvhaddw_d_w(vs10, vs10); + __m256i vs10_hadd_d = __lasx_xvhaddw_q_d(vs10_hadd_w, vs10_hadd_w); + + __m256i vs11_hadd_w = __lasx_xvhaddw_d_w(vs11, vs11); + __m256i vs11_hadd_d = __lasx_xvhaddw_q_d(vs11_hadd_w, vs11_hadd_w); + + __m256i vs12_hadd_w = __lasx_xvhaddw_d_w(vs12, vs12); + __m256i vs12_hadd_d = __lasx_xvhaddw_q_d(vs12_hadd_w, vs12_hadd_w); + + __m256i vs13_hadd_w = __lasx_xvhaddw_d_w(vs13, vs13); + __m256i vs13_hadd_d = __lasx_xvhaddw_q_d(vs13_hadd_w, vs13_hadd_w); + + __m256i vs11_vs10 = __lasx_xvpackev_w(vs11_hadd_d, vs10_hadd_d); + __m256i vs13_vs12 = __lasx_xvpackev_w(vs13_hadd_d, vs12_hadd_d); + __m256i t1 = __lasx_xvpackev_d(vs13_vs12, vs11_vs10); + + /*t2*/ + __m256i vs20_hadd_w = __lasx_xvhaddw_d_w(vs20, vs20); + __m256i vs20_hadd_d = __lasx_xvhaddw_q_d(vs20_hadd_w, vs20_hadd_w); + + __m256i vs21_hadd_w = __lasx_xvhaddw_d_w(vs21, vs21); + __m256i vs21_hadd_d = __lasx_xvhaddw_q_d(vs21_hadd_w, vs21_hadd_w); + + __m256i vs22_hadd_w = __lasx_xvhaddw_d_w(vs22, vs22); + __m256i vs22_hadd_d = __lasx_xvhaddw_q_d(vs22_hadd_w, vs22_hadd_w); + + __m256i vs23_hadd_w = __lasx_xvhaddw_d_w(vs23, vs23); + __m256i vs23_hadd_d = __lasx_xvhaddw_q_d(vs23_hadd_w, vs23_hadd_w); + + __m256i vs21_vs20 = __lasx_xvpackev_w(vs21_hadd_d, vs20_hadd_d); + __m256i vs23_vs22 = __lasx_xvpackev_w(vs23_hadd_d, vs22_hadd_d); + __m256i t2 = __lasx_xvpackev_d(vs23_vs22, vs21_vs20); + + t0 = __lasx_xvadd_w(t0, __lasx_xvpermi_q(t0, t0, 1)); + t1 = __lasx_xvadd_w(t1, __lasx_xvpermi_q(t1, t1, 1)); + t2 = __lasx_xvadd_w(t2, __lasx_xvpermi_q(t2, t2, 1)); + + __m128i s0, s1, s2; + + if( initOutput ) + { + s0 = __lsx_vreplgr2vr_w(bias0); + s1 = __lsx_vreplgr2vr_w(bias1); + s2 = __lsx_vreplgr2vr_w(bias2); + } + else + { + s0 = __lsx_vld((__m128i*)(outptr0 + j), 0); + s1 = __lsx_vld((__m128i*)(outptr1 + j), 0); + s2 = __lsx_vld((__m128i*)(outptr2 + j), 0); + } + + s0 = __lsx_vadd_w(s0, *(__m128i*)&t0); + s1 = __lsx_vadd_w(s1, *(__m128i*)&t1); + s2 = __lsx_vadd_w(s2, *(__m128i*)&t2); + + if( finalOutput ) + { + __m128i voutzp = __lsx_vreplgr2vr_w(outZp); + __m128i outmin = __lsx_vreplgr2vr_w(-128), outmax = __lsx_vreplgr2vr_w(127); + __m256 v_mult0 = _v256_setall_ps(mult0); + __m256 v_mult1 = _v256_setall_ps(mult1); + __m256 v_mult2 = _v256_setall_ps(mult2); + + s0 = __lsx_vadd_w(voutzp, __lsx_vftint_w_s(__lsx_vfmul_s(__lsx_vffint_s_w(s0), *(__m128*)&v_mult0))); + s1 = __lsx_vadd_w(voutzp, __lsx_vftint_w_s(__lsx_vfmul_s(__lsx_vffint_s_w(s1), *(__m128*)&v_mult1))); + s2 = __lsx_vadd_w(voutzp, __lsx_vftint_w_s(__lsx_vfmul_s(__lsx_vffint_s_w(s2), *(__m128*)&v_mult2))); + + s0 = __lsx_vmin_w(__lsx_vmax_w(s0, outmin), outmax); + s1 = __lsx_vmin_w(__lsx_vmax_w(s1, outmin), outmax); + s2 = __lsx_vmin_w(__lsx_vmax_w(s2, outmin), outmax); + } + if( tail ) + { + s0 = __lsx_vbitsel_v(__lsx_vld((const float*)outptr0 + j, 0), s0, mask); + s1 = __lsx_vbitsel_v(__lsx_vld((const float*)outptr1 + j, 0), s1, mask); + s2 = __lsx_vbitsel_v(__lsx_vld((const float*)outptr2 + j, 0), s2, mask); + } + __lsx_vst(s0, (__m128i*)(outptr0 + j), 0); + __lsx_vst(s1, (__m128i*)(outptr1 + j), 0); + __lsx_vst(s2, (__m128i*)(outptr2 + j), 0); + } + + for( ; j <= blockSize - 2; j += 2 ) + { + const int8_t* rptr0 = rowbuf + j*vecsize_aligned; + const int8_t* rptr1 = rowbuf + (j+1)*vecsize_aligned; + int s00, s01, s10, s11, s20, s21; + + if( initOutput ) + { + s00 = s01 = bias0; + s10 = s11 = bias1; + s20 = s21 = bias2; + } + else + { + s00 = outptr0[j]; s01 = outptr0[j+1]; + s10 = outptr1[j]; s11 = outptr1[j+1]; + s20 = outptr2[j]; s21 = outptr2[j+1]; + } + + for( int k = 0; k < vecsize; k++ ) + { + int8_t w0 = wptr0[k], w1 = wptr1[k], w2 = wptr2[k]; + int8_t r = rptr0[k]; + s00 += (int)w0*r; s10 += (int)w1*r; s20 += (int)w2*r; + r = rptr1[k]; + s01 += (int)w0*r; s11 += (int)w1*r; s21 += (int)w2*r; + } + + if( finalOutput ) + { + s00 = std::min(std::max(outZp + (int)std::round(s00*mult0), -128), 127); + s01 = std::min(std::max(outZp + (int)std::round(s01*mult0), -128), 127); + s10 = std::min(std::max(outZp + (int)std::round(s10*mult1), -128), 127); + s11 = std::min(std::max(outZp + (int)std::round(s11*mult1), -128), 127); + s20 = std::min(std::max(outZp + (int)std::round(s20*mult2), -128), 127); + s21 = std::min(std::max(outZp + (int)std::round(s21*mult2), -128), 127); + } + outptr0[j] = s00; + outptr0[j+1] = s01; + outptr1[j] = s10; + outptr1[j+1] = s11; + outptr2[j] = s20; + outptr2[j+1] = s21; + } + + for( ; j < blockSize; j++ ) + { + const int8_t* rptr0 = rowbuf + j*vecsize_aligned; + int s00, s10, s20; + + if( initOutput ) + { + s00 = bias0; + s10 = bias1; + s20 = bias2; + } + else + { + s00 = outptr0[j]; + s10 = outptr1[j]; + s20 = outptr2[j]; + } + + for( int k = 0; k < vecsize; k++ ) + { + int8_t w0 = wptr0[k], w1 = wptr1[k], w2 = wptr2[k]; + int8_t r = rptr0[k]; + s00 += (int)w0*r; s10 += (int)w1*r; s20 += (int)w2*r; + } + + if( finalOutput ) + { + s00 = std::min(std::max(outZp + (int)std::round(s00*mult0), -128), 127); + s10 = std::min(std::max(outZp + (int)std::round(s10*mult1), -128), 127); + s20 = std::min(std::max(outZp + (int)std::round(s20*mult2), -128), 127); + } + outptr0[j] = s00; + outptr1[j] = s10; + outptr2[j] = s20; + } + } +} + +static inline void _v256_expand_mul_add(const __m256i& a, const __m256i& b, + __m256i& out0, __m256i& out1, __m256i& out2, __m256i& out3) +{ + __m256i a0 = __lasx_xvsllwil_h_b(__lasx_xvpermi_d(a, 0x10), 0); + __m256i a1 = __lasx_xvsllwil_h_b(__lasx_xvpermi_d(a, 0x32), 0); + + __m256i b0 = __lasx_xvsllwil_h_b(__lasx_xvpermi_d(b, 0x10), 0); + __m256i b1 = __lasx_xvsllwil_h_b(__lasx_xvpermi_d(b, 0x32), 0); + + __m256i a0b0 = __lasx_xvmul_h(a0, b0); + __m256i a1b1 = __lasx_xvmul_h(a1, b1); + + out0 = __lasx_xvadd_w(out0, __lasx_xvsllwil_w_h(__lasx_xvpermi_d(a0b0, 0x10), 0)); + out1 = __lasx_xvadd_w(out1, __lasx_xvsllwil_w_h(__lasx_xvpermi_d(a0b0, 0x32), 0)); + out2 = __lasx_xvadd_w(out2, __lasx_xvsllwil_w_h(__lasx_xvpermi_d(a1b1, 0x10), 0)); + out3 = __lasx_xvadd_w(out3, __lasx_xvsllwil_w_h(__lasx_xvpermi_d(a1b1, 0x32), 0)); +} + +static inline void _v256_load_deinterleave(const int8_t* ptr, __m256i& a, __m256i& b) +{ + __m256i t0 = __lasx_xvld((const __m256i*)ptr, 0); + __m256i t1 = __lasx_xvld((const __m256i*)ptr, 32*1); + + const __m256i sh = _v256_setr_b(0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15, + 0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15); + __m256i p0 = __lasx_xvshuf_b(t0, t0, sh); + __m256i p1 = __lasx_xvshuf_b(t1, t1, sh); + __m256i lo = __lasx_xvpermi_q(p0, p1, 0x02); + __m256i hi = __lasx_xvpermi_q(p0, p1, 0x13); + + a = __lasx_xvilvl_d(hi, lo); + b = __lasx_xvilvh_d(hi, lo); +} + +void fastDepthwiseConv( const int8_t* wptr, + int kernel_h, int kernel_w, + int stride_h, int stride_w, + int dilation_h, int dilation_w, + int pad_t, int pad_l, + const int* biasptr, const float* multptr, + const int8_t* inptr_, + int height, int width, + int* outptr_, + int out_d, int outH, int outW, + int inpZp, int outZp) +{ + const int8_t w00_ = wptr[0], w01_ = wptr[1], w02_ = wptr[2], + w10 = wptr[3], w11 = wptr[4], w12 = wptr[5], + w20_ = wptr[6], w21_ = wptr[7], w22_ = wptr[8]; + int outW1 = min(outW, (width - dilation_w*(kernel_w - 1) + pad_l)/stride_w); + float mult = multptr[out_d]; + int bias = biasptr[out_d]; + int biasCopy; + + for (int out_i = 0; out_i < outH; out_i++) + { + int in_i = out_i * stride_h - pad_t, out_j = 0; + const int8_t* imgptr0 = inptr_ + in_i*width; + const int8_t* imgptr1 = imgptr0 + dilation_h*width; + const int8_t* imgptr2 = imgptr0 + (dilation_h*2)*width; + int8_t w00 = w00_, w01 = w01_, w02 = w02_; + int8_t w20 = w20_, w21 = w21_, w22 = w22_; + int out; + biasCopy = bias; + if (in_i < 0) + { + biasCopy += inpZp * (w00 + w01 + w02); + w00 = w01 = w02 = 0; + imgptr0 = imgptr1; + } + else if (in_i + dilation_h*(kernel_h-1) >= height) + { + biasCopy += inpZp * (w20 + w21 + w22); + w20 = w21 = w22 = 0; + imgptr2 = imgptr1; + } + int* outptr = outptr_ + out_i*outW; + if (pad_l > 0) + { + out = (int)imgptr0[0]*w01 + (int)imgptr0[dilation_w]*w02 + + (int)imgptr1[0]*w11 + (int)imgptr1[dilation_w]*w12 + + (int)imgptr2[0]*w21 + (int)imgptr2[dilation_w]*w22 + + biasCopy + inpZp*(w00 + w10 + w20); + outptr[0] = std::min(std::max(outZp + (int)std::round(out*mult), -128), 127); + out_j = 1; + } + + if (stride_w == 1 || (stride_w == 2 && dilation_w == 1)) + { + const int VECSZ = 32; + __m256i vw00 = __lasx_xvreplgr2vr_b(w00), vw01 = __lasx_xvreplgr2vr_b(w01), vw02 = __lasx_xvreplgr2vr_b(w02), + vw10 = __lasx_xvreplgr2vr_b(w10), vw11 = __lasx_xvreplgr2vr_b(w11), vw12 = __lasx_xvreplgr2vr_b(w12), + vw20 = __lasx_xvreplgr2vr_b(w20), vw21 = __lasx_xvreplgr2vr_b(w21), vw22 = __lasx_xvreplgr2vr_b(w22); + __m256i vbias = __lasx_xvreplgr2vr_w(biasCopy), voutzp = __lasx_xvreplgr2vr_w(outZp), + outmin = __lasx_xvreplgr2vr_w(-128), outmax = __lasx_xvreplgr2vr_w(127); + __m256 vmult = _v256_setall_ps(mult); + __m256i vout0, vout1, vout2, vout3; + + if( stride_w == 1 ) + { + for( ; out_j < outW1; out_j += VECSZ ) + { + if (out_j + VECSZ > outW1) + { + if (out_j <= pad_l) + break; + out_j = outW1 - VECSZ; + } + int in_j = out_j * stride_w - pad_l; + __m256i v00 = __lasx_xvld((const __m256i*)(imgptr0 + in_j), 0), + v01 = __lasx_xvld((const __m256i*)(imgptr0 + in_j + dilation_w), 0), + v02 = __lasx_xvld((const __m256i*)(imgptr0 + in_j + dilation_w*2), 0), + v10 = __lasx_xvld((const __m256i*)(imgptr1 + in_j), 0), + v11 = __lasx_xvld((const __m256i*)(imgptr1 + in_j + dilation_w), 0), + v12 = __lasx_xvld((const __m256i*)(imgptr1 + in_j + dilation_w*2), 0), + v20 = __lasx_xvld((const __m256i*)(imgptr2 + in_j), 0), + v21 = __lasx_xvld((const __m256i*)(imgptr2 + in_j + dilation_w), 0), + v22 = __lasx_xvld((const __m256i*)(imgptr2 + in_j + dilation_w*2), 0); + + vout0 = vout1 = vout2 = vout3 = vbias; + _v256_expand_mul_add(v00, vw00, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v01, vw01, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v02, vw02, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v10, vw10, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v11, vw11, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v12, vw12, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v20, vw20, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v21, vw21, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v22, vw22, vout0, vout1, vout2, vout3); + + vout0 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout0), vmult))); + vout1 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout1), vmult))); + vout2 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout2), vmult))); + vout3 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout3), vmult))); + + vout0 = __lasx_xvmin_w(__lasx_xvmax_w(vout0, outmin), outmax); + vout1 = __lasx_xvmin_w(__lasx_xvmax_w(vout1, outmin), outmax); + vout2 = __lasx_xvmin_w(__lasx_xvmax_w(vout2, outmin), outmax); + vout3 = __lasx_xvmin_w(__lasx_xvmax_w(vout3, outmin), outmax); + + __lasx_xvst(vout0, (__m256i*)(outptr + out_j), 0); + __lasx_xvst(vout1, (__m256i*)(outptr + out_j), 8*4); + __lasx_xvst(vout2, (__m256i*)(outptr + out_j), 16*4); + __lasx_xvst(vout3, (__m256i*)(outptr + out_j), 24*4); + } + } + else + { + for( ; out_j < outW1; out_j += VECSZ ) + { + if (out_j + VECSZ > outW1) + { + if (out_j <= pad_l) + break; + out_j = outW1 - VECSZ; + } + int in_j = out_j * stride_w - pad_l; + __m256i v00, v01, v02, v10, v11, v12, v20, v21, v22, unused; + _v256_load_deinterleave(imgptr0 + in_j, v00, v01); + _v256_load_deinterleave(imgptr0 + in_j + 2, v02, unused); + _v256_load_deinterleave(imgptr1 + in_j, v10, v11); + _v256_load_deinterleave(imgptr1 + in_j + 2, v12, unused); + _v256_load_deinterleave(imgptr2 + in_j, v20, v21); + _v256_load_deinterleave(imgptr2 + in_j + 2, v22, unused); + + vout0 = vout1 = vout2 = vout3 = vbias; + _v256_expand_mul_add(v00, vw00, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v01, vw01, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v02, vw02, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v10, vw10, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v11, vw11, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v12, vw12, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v20, vw20, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v21, vw21, vout0, vout1, vout2, vout3); + _v256_expand_mul_add(v22, vw22, vout0, vout1, vout2, vout3); + + vout0 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout0), vmult))); + vout1 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout1), vmult))); + vout2 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout2), vmult))); + vout3 = __lasx_xvadd_w(voutzp, __lasx_xvftint_w_s(__lasx_xvfmul_s(__lasx_xvffint_s_w(vout3), vmult))); + + vout0 = __lasx_xvmin_w(__lasx_xvmax_w(vout0, outmin), outmax); + vout1 = __lasx_xvmin_w(__lasx_xvmax_w(vout1, outmin), outmax); + vout2 = __lasx_xvmin_w(__lasx_xvmax_w(vout2, outmin), outmax); + vout3 = __lasx_xvmin_w(__lasx_xvmax_w(vout3, outmin), outmax); + + __lasx_xvst(vout0, (__m256i*)(outptr + out_j), 0); + __lasx_xvst(vout1, (__m256i*)(outptr + out_j), 8*4); + __lasx_xvst(vout2, (__m256i*)(outptr + out_j), 16*4); + __lasx_xvst(vout3, (__m256i*)(outptr + out_j), 24*4); + } + } + } + + for (; out_j < outW1; out_j++) + { + int in_j = out_j * stride_w - pad_l; + out = (int)imgptr0[in_j]*w00 + (int)imgptr0[in_j + dilation_w]*w01 + (int)imgptr0[in_j + dilation_w*2]*w02 + + (int)imgptr1[in_j]*w10 + (int)imgptr1[in_j + dilation_w]*w11 + (int)imgptr1[in_j + dilation_w*2]*w12 + + (int)imgptr2[in_j]*w20 + (int)imgptr2[in_j + dilation_w]*w21 + (int)imgptr2[in_j + dilation_w*2]*w22 + biasCopy; + outptr[out_j] = std::min(std::max(outZp + (int)std::round(out*mult), -128), 127); + } + + for (; out_j < outW; out_j++ ) + { + int in_j0 = out_j * stride_w - pad_l, in_j1 = in_j0 + dilation_w, in_j2 = in_j0 + dilation_w*2; + int s0 = 1, s1 = 1, s2 = 1; + if (in_j0 >= width) + { + in_j0 = 0; + s0 = 0; + biasCopy += inpZp*(w00 + w10 + w20); + } + if (in_j1 >= width) + { + in_j1 = 0; + s1 = 0; + biasCopy += inpZp*(w01 + w11 + w21); + } + if (in_j2 >= width) + { + in_j2 = 0; + s2 = 0; + biasCopy += inpZp*(w02 + w12 + w22); + } + out = (int)imgptr0[in_j0]*w00*s0 + (int)imgptr0[in_j1]*w01*s1 + (int)imgptr0[in_j2]*w02*s2 + + (int)imgptr1[in_j0]*w10*s0 + (int)imgptr1[in_j1]*w11*s1 + (int)imgptr1[in_j2]*w12*s2 + + (int)imgptr2[in_j0]*w20*s0 + (int)imgptr2[in_j1]*w21*s1 + (int)imgptr2[in_j2]*w22*s2 + biasCopy; + outptr[out_j] = std::min(std::max(outZp + (int)std::round(out*mult), -128), 127); + } + } +} + +// dst = vec * weights^t + bias +void fastGEMM1T( const int8_t* vec, const int8_t* weights, + size_t wstep, const int* bias, const float* multiplier, + int* dst, int nvecs, int vecsize, int outZp ) +{ + int i = 0; + + for( ; i <= nvecs - 8; i += 8 ) + { + const int8_t* wptr = weights + i*wstep; + __m256i vs0 = __lasx_xvreplgr2vr_d(0), vs1 = __lasx_xvreplgr2vr_d(0), + vs2 = __lasx_xvreplgr2vr_d(0), vs3 = __lasx_xvreplgr2vr_d(0), + vs4 = __lasx_xvreplgr2vr_d(0), vs5 = __lasx_xvreplgr2vr_d(0), + vs6 = __lasx_xvreplgr2vr_d(0), vs7 = __lasx_xvreplgr2vr_d(0); + + __m128i voutzp = __lsx_vreplgr2vr_w(outZp); + __m128i outmin = __lsx_vreplgr2vr_w(-128), outmax = __lsx_vreplgr2vr_w(127); + + for( int k = 0; k < vecsize; k += 32, wptr += 32 ) + { + __m256i v = __lasx_xvld((const __m256i*)(vec + k), 0); + + vs0 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)wptr, 0), v, vs0); + vs1 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)(wptr + wstep), 0), v, vs1); + vs2 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)(wptr + wstep*2), 0), v, vs2); + vs3 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)(wptr + wstep*3), 0), v, vs3); + vs4 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)(wptr + wstep*4), 0), v, vs4); + vs5 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)(wptr + wstep*5), 0), v, vs5); + vs6 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)(wptr + wstep*6), 0), v, vs6); + vs7 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)(wptr + wstep*7), 0), v, vs7); + } + + /*s0*/ + __m256i vs0_hadd_w = __lasx_xvhaddw_d_w(vs0, vs0); + __m256i vs0_hadd_d = __lasx_xvhaddw_q_d(vs0_hadd_w, vs0_hadd_w); + + __m256i vs1_hadd_w = __lasx_xvhaddw_d_w(vs1, vs1); + __m256i vs1_hadd_d = __lasx_xvhaddw_q_d(vs1_hadd_w, vs1_hadd_w); + + __m256i vs2_hadd_w = __lasx_xvhaddw_d_w(vs2, vs2); + __m256i vs2_hadd_d = __lasx_xvhaddw_q_d(vs2_hadd_w, vs2_hadd_w); + + __m256i vs3_hadd_w = __lasx_xvhaddw_d_w(vs3, vs3); + __m256i vs3_hadd_d = __lasx_xvhaddw_q_d(vs3_hadd_w, vs3_hadd_w); + + __m256i vs1_vs0 = __lasx_xvpackev_w(vs1_hadd_d, vs0_hadd_d); + __m256i vs3_vs2 = __lasx_xvpackev_w(vs3_hadd_d, vs2_hadd_d); + __m256i s0 = __lasx_xvpackev_d(vs3_vs2, vs1_vs0); + + /*s1*/ + __m256i vs4_hadd_w = __lasx_xvhaddw_d_w(vs4, vs4); + __m256i vs4_hadd_d = __lasx_xvhaddw_q_d(vs4_hadd_w, vs4_hadd_w); + + __m256i vs5_hadd_w = __lasx_xvhaddw_d_w(vs5, vs5); + __m256i vs5_hadd_d = __lasx_xvhaddw_q_d(vs5_hadd_w, vs5_hadd_w); + + __m256i vs6_hadd_w = __lasx_xvhaddw_d_w(vs6, vs6); + __m256i vs6_hadd_d = __lasx_xvhaddw_q_d(vs6_hadd_w, vs6_hadd_w); + + __m256i vs7_hadd_w = __lasx_xvhaddw_d_w(vs7, vs7); + __m256i vs7_hadd_d = __lasx_xvhaddw_q_d(vs7_hadd_w, vs7_hadd_w); + + __m256i vs5_vs4 = __lasx_xvpackev_w(vs5_hadd_d, vs4_hadd_d); + __m256i vs7_vs6 = __lasx_xvpackev_w(vs7_hadd_d, vs6_hadd_d); + __m256i s1 = __lasx_xvpackev_d(vs7_vs6, vs5_vs4); + + s0 = __lasx_xvadd_w(s0, __lasx_xvpermi_q(s0, s0, 1)); + s1 = __lasx_xvadd_w(s1, __lasx_xvpermi_q(s1, s1, 1)); + + __m128i t0 = __lsx_vadd_w(*(__m128i*)(&s0), __lsx_vld((__m128i*)(bias + i), 0)); + __m128i t1 = __lsx_vadd_w(*(__m128i*)(&s1), __lsx_vld((__m128i*)(bias + i), 4*4)); + + t0 = __lsx_vadd_w(voutzp, __lsx_vftint_w_s(__lsx_vfmul_s(__lsx_vffint_s_w(t0), (__m128)__lsx_vld(multiplier + i, 0)))); + t1 = __lsx_vadd_w(voutzp, __lsx_vftint_w_s(__lsx_vfmul_s(__lsx_vffint_s_w(t1), (__m128)__lsx_vld(multiplier + i, 4*4)))); + + t0 = __lsx_vmin_w(__lsx_vmax_w(t0, outmin), outmax); + t1 = __lsx_vmin_w(__lsx_vmax_w(t1, outmin), outmax); + + __lsx_vst(t0, (__m128i*)(dst + i), 0); + __lsx_vst(t1, (__m128i*)(dst + i), 4*4); + } + + for( ; i < nvecs; i++ ) + { + const int8_t* wptr = weights + i*wstep; + __m256i vs0 = __lasx_xvreplgr2vr_d(0); + + for( int k = 0; k < vecsize; k += 32, wptr += 32 ) + { + __m256i v = __lasx_xvld((const __m256i*)(vec + k), 0); + vs0 = _v256_fmadds8_s32(__lasx_xvld((const __m256i*)wptr, 0), v, vs0); + } + + __m256i s0_hadd_w = __lasx_xvhaddw_d_w(vs0, vs0); + int temp = ((v4i64)s0_hadd_w)[0] + ((v4i64)s0_hadd_w)[1] + ((v4i64)s0_hadd_w)[2] + ((v4i64)s0_hadd_w)[3]; + dst[i] = outZp + (int)std::round((temp + bias[i]) * multiplier[i]); + } + +} +#endif // CV_LASX + CV_CPU_OPTIMIZATION_NAMESPACE_END }} // namespace diff --git a/modules/dnn/src/int8layers/pooling_layer.cpp b/modules/dnn/src/int8layers/pooling_layer.cpp index 98cf17c06c..6c52d19e83 100644 --- a/modules/dnn/src/int8layers/pooling_layer.cpp +++ b/modules/dnn/src/int8layers/pooling_layer.cpp @@ -26,11 +26,11 @@ public: computeMaxIdx = false; globalPooling = false; isGlobalPooling = std::vector(3, false); - output_zp = params.get("zeropoints"); + output_zp = params.get("zeropoints", 0); input_zp = params.get("input_zeropoint", output_zp); multiplier = params.get("multiplier", 1.f); - output_sc = params.get("scales"); + output_sc = params.get("scales", 1.f); input_sc = multiplier * output_sc; hasDynamicShapes = params.get("has_dynamic_shapes", false); diff --git a/modules/dnn/src/int8layers/quantization_utils.cpp b/modules/dnn/src/int8layers/quantization_utils.cpp index 6e2f0bb61c..a4a822efdd 100644 --- a/modules/dnn/src/int8layers/quantization_utils.cpp +++ b/modules/dnn/src/int8layers/quantization_utils.cpp @@ -11,14 +11,88 @@ namespace cv namespace dnn { +static void broadcast1D2TargetMat(Mat& data, const MatShape& targetShape, int axis) +{ + // The data is the 1-D scales or zeropoints. + CV_Assert(axis >= 0 && targetShape.size() > axis && data.total() == targetShape[axis]); + std::vector broadcast_axes; + for (int i = 0; i < targetShape.size(); i++) + { + if (i != axis) + broadcast_axes.push_back(i); + } + + MatShape subTargetShape = shape(data); + + // convert std::vector to 1D Mat. + for (auto broadcast_axis : broadcast_axes) + { + subTargetShape[broadcast_axis] = targetShape[broadcast_axis]; + data = data.reshape(0, total(data, 0, broadcast_axis)); + Mat tmp = cv::repeat(data, 1, subTargetShape[broadcast_axis]); + data = tmp.reshape(0, subTargetShape); + } +} + +static void broadcastScaleAndZeropoint(Mat& scalesMat, Mat& zeropointsMat, const std::vector& scales, + const std::vector& zeropoints, const MatShape& targetShape, int axis) +{ + // broad cast the scales and zeropoint to the input shape. + MatShape subTargetShape(targetShape.size(), 1); + subTargetShape[axis] = scales.size(); + + zeropointsMat.create(subTargetShape.size(), subTargetShape.data(), CV_32FC1); + scalesMat.create(subTargetShape.size(), subTargetShape.data(), CV_32FC1); + + const int len = scales.size(); + // Deep copy the scales and zeropoint data and prevent the original data from being changed. + + float * scalePtr = scalesMat.ptr(0); + for (int i = 0; i < len; i++) + scalePtr[i] = scales[i]; + + float * zpPtr = zeropointsMat.ptr(0); + for (int i = 0; i < len; i++) + zpPtr[i] = (float )zeropoints[i]; + + broadcast1D2TargetMat(scalesMat, targetShape, axis); + broadcast1D2TargetMat(zeropointsMat, targetShape, axis); +} + // Quantize FP32/FP16 Inputs to INT8 class QuantizeLayerImpl CV_FINAL : public QuantizeLayer { public: + int axis; + bool is1D; + Mat scalesMat, zeropointsMat; // Saving the broadcasetd scales data. + QuantizeLayerImpl(const LayerParams& params) { - scale = params.get("scales", 1.0f); - zeropoint = params.get("zeropoints", 0); + is1D = params.get("is1D", false); + axis = params.get("axis", 1); + if (!is1D) + { + scales.push_back(params.get("scales", 1.0f)); + zeropoints.push_back(params.get("zeropoints", 0)); + } + else + { + DictValue paramScales = params.get("scales"); + int i, n = paramScales.size(); + + CV_Assert(n > 0); + scales.resize(n, 0.); + for (i = 0; i < n; i++) + scales[i] = paramScales.get(i); + + zeropoints.resize(n, 0); + DictValue paramZp = params.get("zeropoints"); + n = paramZp.size(); + + for (i = 0; i < n; i++) + zeropoints[i] = paramZp.get(i); + } setParamsFrom(params); } @@ -42,6 +116,14 @@ public: std::vector inputs, outputs; inputs_arr.getMatVector(inputs); outputs_arr.getMatVector(outputs); + + axis = normalize_axis(axis, shape(inputs[0]).size()); + + if (is1D) + { + MatShape inputShape = shape(inputs[0]); + broadcastScaleAndZeropoint(scalesMat, zeropointsMat, scales, zeropoints, inputShape, axis); + } } #ifdef HAVE_OPENCL @@ -58,7 +140,7 @@ public: inputs[0] = inputFp32; // replace } - inputs[0].convertTo(outputs[0], CV_8S, 1.f/scale, zeropoint); + inputs[0].convertTo(outputs[0], CV_8S, 1.f/scales[0], zeropoints[0]); return true; } #endif @@ -68,14 +150,26 @@ public: CV_TRACE_FUNCTION(); CV_TRACE_ARG_VALUE(name, "name", name.c_str()); - CV_OCL_RUN(IS_DNN_OPENCL_TARGET(preferableTarget), + CV_OCL_RUN(IS_DNN_OPENCL_TARGET(preferableTarget) && !is1D, forward_ocl(inputs_arr, outputs_arr, internals_arr)) std::vector inputs, outputs; inputs_arr.getMatVector(inputs); outputs_arr.getMatVector(outputs); - inputs[0].convertTo(outputs[0], CV_8S, 1.f/scale, zeropoint); + if (outputs[0].depth() != CV_8S) + outputs[0].convertTo(outputs[0], CV_8S); + + if (is1D) + { + Mat inputTmp; + divide(inputs[0], scalesMat, inputTmp); + subtract(inputTmp, zeropointsMat, inputTmp); + + inputTmp.convertTo(outputs[0], CV_8S); + } + else + inputs[0].convertTo(outputs[0], CV_8S, 1.f/scales[0], zeropoints[0]); } }; @@ -83,10 +177,38 @@ public: class DequantizeLayerImpl CV_FINAL : public DequantizeLayer { public: + int axis; + bool is1D; + Mat scalesMat, zeropointsMat; // Saving the broadcasetd scales data. + DequantizeLayerImpl(const LayerParams& params) { - scale = params.get("scales", 1.0f); - zeropoint = params.get("zeropoints", 0); + is1D = params.get("is1D", false); + axis = params.get("axis", 1); + + if (!is1D) + { + scales.push_back(params.get("scales", 1.0f)); + zeropoints.push_back(params.get("zeropoints", 0)); + } + else + { + DictValue paramScales = params.get("scales"); + int i, n = paramScales.size(); + + CV_Assert(n > 0); + scales.resize(n); + for (i = 0; i < n; i++) + scales[i] = paramScales.get(i); + + zeropoints.resize(n, 0); + DictValue paramZp = params.get("zeropoints"); + n = paramZp.size(); + + for (i = 0; i < n; i++) + zeropoints[i] = paramZp.get(i); + } + setParamsFrom(params); } @@ -110,6 +232,14 @@ public: std::vector inputs, outputs; inputs_arr.getMatVector(inputs); outputs_arr.getMatVector(outputs); + + axis = normalize_axis(axis, shape(inputs[0]).size()); + + if (is1D) + { + MatShape inputShape = shape(inputs[0]); + broadcastScaleAndZeropoint(scalesMat, zeropointsMat, scales, zeropoints, inputShape, axis); + } } #ifdef HAVE_OPENCL @@ -120,7 +250,7 @@ public: outputs_.getUMatVector(outputs); UMat outputFp32; - inputs[0].convertTo(outputFp32, CV_32F, scale, -(scale*zeropoint)); + inputs[0].convertTo(outputFp32, CV_32F, scales[0], -(scales[0]*zeropoints[0])); if (outputs_.depth() == CV_16S) convertFp16(outputFp32, outputs[0]); @@ -135,14 +265,25 @@ public: CV_TRACE_FUNCTION(); CV_TRACE_ARG_VALUE(name, "name", name.c_str()); - CV_OCL_RUN(IS_DNN_OPENCL_TARGET(preferableTarget), + CV_OCL_RUN(IS_DNN_OPENCL_TARGET(preferableTarget) && !is1D, forward_ocl(inputs_arr, outputs_arr, internals_arr)) std::vector inputs, outputs; inputs_arr.getMatVector(inputs); outputs_arr.getMatVector(outputs); - inputs[0].convertTo(outputs[0], CV_32F, scale, -(scale*zeropoint)); + if (outputs[0].depth() != CV_32F) + outputs[0].convertTo(outputs[0], CV_32F); + + if (is1D) + { + Mat inputTmp; + inputs[0].convertTo(inputTmp, CV_32F); + subtract(inputTmp, zeropointsMat, inputTmp); + multiply(inputTmp, scalesMat, outputs[0]); + } + else + inputs[0].convertTo(outputs[0], CV_32F, scales[0], -(scales[0]*zeropoints[0])); } }; diff --git a/modules/dnn/src/layer.cpp b/modules/dnn/src/layer.cpp index 0ed3488da6..5305a5221d 100644 --- a/modules/dnn/src/layer.cpp +++ b/modules/dnn/src/layer.cpp @@ -84,6 +84,12 @@ Ptr Layer::initTimVX(void* timVxInfo, return Ptr(); } +Ptr Layer::initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) +{ + CV_Error(Error::StsNotImplemented, "CANN pipeline of " + type + " layers is not defined."); + return Ptr(); +} + Ptr Layer::tryAttach(const Ptr& node) { return Ptr(); diff --git a/modules/dnn/src/layer_factory.cpp b/modules/dnn/src/layer_factory.cpp index 5c80cd09ad..e5b835143e 100644 --- a/modules/dnn/src/layer_factory.cpp +++ b/modules/dnn/src/layer_factory.cpp @@ -4,8 +4,6 @@ #include "precomp.hpp" -#include - #include // getLayerFactoryImpl diff --git a/modules/dnn/src/layer_internals.hpp b/modules/dnn/src/layer_internals.hpp index 9ded3543e1..f19b99f260 100644 --- a/modules/dnn/src/layer_internals.hpp +++ b/modules/dnn/src/layer_internals.hpp @@ -96,21 +96,29 @@ struct LayerData int flag; - Ptr getLayerInstance() + + void resetAllocation() { - CV_TRACE_FUNCTION(); - CV_TRACE_ARG_VALUE(type, "type", type.c_str()); + if (id == 0) + return; // skip "input" layer (assertion in Net::Impl::allocateLayers) - if (layerInstance) - return layerInstance; + layerInstance.release(); + outputBlobs.clear(); + inputBlobs.clear(); + internals.clear(); - layerInstance = LayerFactory::createLayerInstance(type, params); - if (!layerInstance) - { - CV_Error(Error::StsError, "Can't create layer \"" + name + "\" of type \"" + type + "\""); - } + outputBlobsWrappers.clear(); + inputBlobsWrappers.clear(); + internalBlobsWrappers.clear(); - return layerInstance; + backendNodes.clear(); + + skip = false; + flag = 0; + +#ifdef HAVE_CUDA + cudaD2HBackgroundTransfers.clear(); +#endif } }; diff --git a/modules/dnn/src/layers/batch_norm_layer.cpp b/modules/dnn/src/layers/batch_norm_layer.cpp index 377e05f5cc..e112ba0746 100644 --- a/modules/dnn/src/layers/batch_norm_layer.cpp +++ b/modules/dnn/src/layers/batch_norm_layer.cpp @@ -16,6 +16,7 @@ Implementation of Batch Normalization layer. #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" #include "../op_webnn.hpp" +#include "../op_cann.hpp" #include @@ -40,6 +41,7 @@ public: Mat weights_, bias_; UMat umat_weight, umat_bias; mutable int dims; + float momentum; BatchNormLayerImpl(const LayerParams& params) @@ -55,6 +57,9 @@ public: hasWeights = hasBias = true; epsilon = params.get("eps", 1E-5); + // std::cout << params.get("momentum", 0.9) << std::endl; + momentum = params.get("momentum", 0.9); + size_t n = blobs[0].total(); CV_Assert(blobs[1].total() == n && blobs[0].isContinuous() && blobs[1].isContinuous() && @@ -177,7 +182,8 @@ public: return (backendId == DNN_BACKEND_OPENCV) || backendId == DNN_BACKEND_CUDA || (backendId == DNN_BACKEND_HALIDE && haveHalide()) || - backendId == DNN_BACKEND_WEBNN; + backendId == DNN_BACKEND_WEBNN || + backendId == DNN_BACKEND_CANN; } #ifdef HAVE_OPENCL @@ -385,6 +391,66 @@ public: } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + CV_Assert(nodes.size() == 1); + CV_Assert(blobs.size() == 4); // must have scale, offset, mean and variance + + auto x = inputsWrapper[0].dynamicCast(); + auto channel = x->host->size[1]; + + // create operator + std::string op_name = cv::format("bn_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_epsilon(epsilon); + op->set_attr_data_format("NCHW"); + op->set_attr_is_training(false); + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + // set inputs : scale (blobs[2]) + std::vector shape_{channel}; + auto op_const_scale = std::make_shared(blobs[2].data, blobs[2].type(), shape_, cv::format("%s_scale", op_name.c_str())); + op->set_input_scale(*(op_const_scale->getOp())); + op->update_input_desc_scale(*(op_const_scale->getTensorDesc())); + // set inputs : offset (blobs[3]) + auto op_const_offset = std::make_shared(blobs[3].data, blobs[3].type(), shape_, cv::format("%s_offset", op_name.c_str())); + op->set_input_offset(*(op_const_offset->getOp())); + op->update_input_desc_offset(*(op_const_offset->getTensorDesc())); + // set inputs : mean (blobs[0]) + auto op_const_mean = std::make_shared(blobs[0].data, blobs[0].type(), shape_, cv::format("%s_mean", op_name.c_str())); + op->set_input_mean(*(op_const_mean->getOp())); + op->update_input_desc_mean(*(op_const_mean->getTensorDesc())); + // set inputs : variance (blobs[1]) + auto op_const_var = std::make_shared(blobs[1].data, blobs[1].type(), shape_, cv::format("%s_var", op_name.c_str())); + op->set_input_variance(*(op_const_var->getOp())); + op->update_input_desc_variance(*(op_const_var->getTensorDesc())); + + // set outputs + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_y_desc); + auto output_bm_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_batch_mean(*output_bm_desc); + auto output_bv_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_batch_variance(*output_bv_desc); + auto output_rs1_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_reserve_space_1(*output_rs1_desc); + auto output_rs2_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_reserve_space_2(*output_rs2_desc); + auto output_rs3_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_reserve_space_3(*output_rs3_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, const std::vector >& nodes) CV_OVERRIDE diff --git a/modules/dnn/src/layers/blank_layer.cpp b/modules/dnn/src/layers/blank_layer.cpp index 0d6ab19e4d..972aa7c9c8 100644 --- a/modules/dnn/src/layers/blank_layer.cpp +++ b/modules/dnn/src/layers/blank_layer.cpp @@ -43,6 +43,7 @@ #include "../op_cuda.hpp" #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" +#include "../op_cann.hpp" #ifdef HAVE_CUDA #include "../cuda4dnn/primitives/reshape.hpp" @@ -68,7 +69,8 @@ public: return true; #endif return backendId == DNN_BACKEND_OPENCV || - backendId == DNN_BACKEND_CUDA; + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_CANN; } bool getMemoryShapes(const std::vector &inputs, @@ -118,6 +120,28 @@ public: inputs[i].copyTo(outputs[i]); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + auto x_desc = x->getTensorDesc(); + auto op_x = nodes[0].dynamicCast()->getOp(); + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + // create operator + std::string op_name = cv::format("identity_%d", index); + auto op = std::make_shared(op_name); + + // set inputs + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + + // set output + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, diff --git a/modules/dnn/src/layers/concat_layer.cpp b/modules/dnn/src/layers/concat_layer.cpp index 5ba0cd199b..52330a8e42 100644 --- a/modules/dnn/src/layers/concat_layer.cpp +++ b/modules/dnn/src/layers/concat_layer.cpp @@ -49,6 +49,7 @@ #include "../op_vkcom.hpp" #include "../op_webnn.hpp" #include "../op_timvx.hpp" +#include "../op_cann.hpp" #ifdef HAVE_OPENCL #include "opencl_kernels_dnn.hpp" @@ -140,7 +141,8 @@ public: backendId == DNN_BACKEND_CUDA || (backendId == DNN_BACKEND_HALIDE && haveHalide() && axis == 1 && !padding) || // By channels (backendId == DNN_BACKEND_WEBNN && !padding) || - (backendId == DNN_BACKEND_VKCOM && haveVulkan() && !padding); + (backendId == DNN_BACKEND_VKCOM && haveVulkan() && !padding) || + (backendId == DNN_BACKEND_CANN && !padding); } template @@ -364,13 +366,44 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + CV_Assert(inputsWrapper.size() == nodes.size()); + + // create operator + std::string op_name = cv::format("concat_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + int N = inputsWrapper.size(); + op->set_attr_concat_dim(axis); + op->set_attr_N(N); + + // set inputs : x (dynamic) + op->create_dynamic_input_x(N); + for (int i = 0; i < N; i++) + { + auto x_i = inputsWrapper[i].dynamicCast(); + auto x_i_desc = x_i->getTensorDesc(); + auto op_x_i = nodes[i].dynamicCast()->getOp(); + op->set_dynamic_input_x(i, *op_x_i, "y"); + op->update_dynamic_input_desc_x(i, *x_i_desc); + } + + // set outputs + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, const std::vector >& nodes) CV_OVERRIDE { - InferenceEngine::DataPtr data = ngraphDataNode(inputs[0]); - const int numDims = data->getDims().size(); + const int numDims = nodes[0].dynamicCast()->node->get_shape().size(); const int cAxis = normalize_axis(axis, numDims); std::vector maxDims(numDims, 0); @@ -378,16 +411,17 @@ public: ngraph::OutputVector inp_nodes; for (int i = 0; i < nodes.size(); ++i) { - inp_nodes.push_back(nodes[i].dynamicCast()->node); + auto inp = nodes[i].dynamicCast()->node; + inp_nodes.push_back(inp); - std::vector inpShape = ngraphDataNode(inputs[i])->getDims(); + std::vector inpShape = inp->get_shape(); for (int i = 0; i < numDims; ++i) maxDims[i] = std::max(maxDims[i], inpShape[i]); } for (int i = 0; i < inp_nodes.size(); ++i) { bool needPadding = false; - std::vector inpShape = ngraphDataNode(inputs[i])->getDims(); + std::vector inpShape = inp_nodes[i].get_shape(); std::vector begins(inpShape.size(), 0), ends(inpShape.size(), 0); for (int j = 0; j < inpShape.size(); ++j) { diff --git a/modules/dnn/src/layers/const_layer.cpp b/modules/dnn/src/layers/const_layer.cpp index 4392763be7..2141ad987a 100644 --- a/modules/dnn/src/layers/const_layer.cpp +++ b/modules/dnn/src/layers/const_layer.cpp @@ -11,6 +11,9 @@ #include "layers_common.hpp" #include "../ie_ngraph.hpp" #include "../op_webnn.hpp" +#include "../op_cann.hpp" + +#include #ifdef HAVE_OPENCL #include "opencl_kernels_dnn.hpp" @@ -40,7 +43,8 @@ public: #endif return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_WEBNN || - backendId == DNN_BACKEND_CUDA; + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_CANN; } virtual bool getMemoryShapes(const std::vector &inputs, @@ -79,6 +83,40 @@ public: blobs[0].copyTo(outputs[0]); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto mat_shape = shape(blobs[0]); + std::vector mat_shape_{mat_shape.begin(), mat_shape.end()}; + + auto ge_shape = ge::Shape(mat_shape_); + auto ge_dtype = ge::DT_FLOAT; + switch (blobs[0].type()) + { + case CV_32F: break; + case CV_32S: ge_dtype = ge::DT_INT32; break; + default: CV_Error(Error::StsNotImplemented, "Unsuppported data type"); + } + auto size_of_type = sizeof(float); + switch (blobs[0].type()) + { + case CV_32F: break; + case CV_32S: size_of_type = sizeof(int); break; + default: CV_Error(Error::StsNotImplemented, "Unsuppported data type"); + } + + auto desc = std::make_shared(ge_shape, ge::FORMAT_NCHW, ge_dtype); + auto ge_tensor = std::make_shared(); + ge_tensor->SetTensorDesc(*desc); + ge_tensor->SetData(blobs[0].data, ge_shape.GetShapeSize() * size_of_type); + + std::string op_name = cv::format("const_%d", index); + auto op = std::make_shared(op_name); + op->set_attr_value(*ge_tensor); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, diff --git a/modules/dnn/src/layers/convolution_layer.cpp b/modules/dnn/src/layers/convolution_layer.cpp index c2960d5aeb..b4829c72a6 100644 --- a/modules/dnn/src/layers/convolution_layer.cpp +++ b/modules/dnn/src/layers/convolution_layer.cpp @@ -48,6 +48,7 @@ #include "../ie_ngraph.hpp" #include "../op_vkcom.hpp" #include "../op_webnn.hpp" +#include "../op_cann.hpp" #include #include @@ -89,7 +90,8 @@ public: BaseConvolutionLayerImpl(const LayerParams ¶ms) { setParamsFrom(params); - getConvolutionKernelParams(params, kernel_size, pads_begin, pads_end, strides, dilations, padMode, adjust_pads); + getConvolutionKernelParams(params, kernel_size, pads_begin, pads_end, strides, dilations, + padMode, adjust_pads, useWinograd); numOutput = params.get("num_output"); int ngroups = params.get("group", 1); @@ -101,10 +103,6 @@ public: if (kernel_size.size() == 2) { kernel = Size(kernel_size[1], kernel_size[0]); stride = Size(strides[1], strides[0]); - for (int i = 0; i < pads_begin.size(); i++) { - if (pads_begin[i] != pads_end[i]) - CV_Error(Error::StsNotImplemented, "Unsupported asymmetric padding in convolution layer"); - } pad = Size(pads_begin[1], pads_begin[0]); dilation = Size(dilations[1], dilations[0]); @@ -118,6 +116,9 @@ public: fusedWeights = false; fusedBias = false; + + if (kernel_size.size() == 2) + isConv2D = true; } virtual void finalize(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr) CV_OVERRIDE @@ -163,10 +164,6 @@ public: } getConvPoolPaddings(inpShape, kernel_size, strides, padMode, pads_begin, pads_end); if (pads_begin.size() == 2) { - for (int i = 0; i < pads_begin.size(); i++) { - if (pads_begin[i] != pads_end[i]) - CV_Error(Error::StsNotImplemented, "Unsupported asymmetric padding in convolution layer"); - } pad = Size(pads_begin[1], pads_begin[0]); } fusedWeights = false; @@ -188,6 +185,9 @@ public: virtual bool tryFuse(Ptr& top) CV_OVERRIDE { + if (fusedAdd) // If the Conv layer has fused Add layer, it cannot fuse other layers. + return false; + Ptr blank_layer = top.dynamicCast(); if (blank_layer) return true; @@ -260,8 +260,7 @@ public: std::vector reluslope; Ptr activ; - Mat fastWeights; // Used to store weight params. It will be used for layer fusion and without memory alignment. - Ptr fastConv2dImpl; + Ptr fastConvImpl; #ifdef HAVE_OPENCL Ptr > convolutionOp; @@ -371,6 +370,17 @@ public: return true; } #endif +#ifdef HAVE_CANN + if (backendId == DNN_BACKEND_CANN) + { + if (ksize != 2) + { + CV_LOG_WARNING(NULL, "CANN supports Conv2D for now"); + return false; + } + return true; + } +#endif // HAVE_CANN return false; } @@ -438,7 +448,6 @@ public: wm.copyTo(wm_aligned); wm = wm_aligned; } - fastWeights = blobs[0].reshape(1, numOutput); weightsMat = wm; } else @@ -584,11 +593,15 @@ public: } } #endif - return !activ.empty(); + fusedActivation = !activ.empty(); + return fusedActivation; } virtual bool tryFuse(Ptr& top) CV_OVERRIDE { + if (fusedAdd) // If the Conv layer has fused Add layer, it cannot fuse other layers. + return false; + #ifdef HAVE_CUDA if(IS_DNN_CUDA_TARGET(preferableTarget)) { @@ -634,26 +647,14 @@ public: if (weightsMat.data == blobs[0].data) weightsMat = weightsMat.clone(); - // If fastWeights is the same as weightsMat, we don't need to allocate more space for fastWeights. - bool sameFastWeights = false; - if (fastWeights.step1() == weightsMat.step1()) // If weightsMat is realigned, it is not the same as fastWeights. - sameFastWeights = true; - - if (!sameFastWeights && fastWeights.data == blobs[0].data) - fastWeights = fastWeights.clone(); - Mat originWeights = blobs[0].reshape(1, outCn); for (int i = 0; i < outCn; ++i) { double wi = w.at(i); weightsMultipliers[i] *= wi; cv::multiply(originWeights.row(i), weightsMultipliers[i], weightsMat.row(i)); - if (!sameFastWeights) - cv::multiply(originWeights.row(i), weightsMultipliers[i], fastWeights.row(i)); biasvec[i] *= wi; } - if (sameFastWeights) - fastWeights = weightsMat; } if (!b.empty()) @@ -779,6 +780,68 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + CV_Assert(!blobs.empty()); + CV_Assert(inputsWrapper.size() == 1); + CV_Assert(nodes.size() == 1); + + bool has_bias = hasBias() || fusedBias; + + auto x = inputsWrapper[0].dynamicCast(); + const int x_in_channel = x->host->size[1]; + const int filter_out_channel = blobs[0].size[1]; + const int groups = x_in_channel / filter_out_channel; + + // create operator + std::string op_name = cv::format("conv2d_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_strides(ge::Operator::OpListInt( + {1, 1, (int64_t)strides[0], (int64_t)strides[1]} + )); + op->set_attr_pads(ge::Operator::OpListInt( + {(int64_t)pads_begin[1], (int64_t)pads_end[1], (int64_t)pads_begin[0], (int64_t)pads_end[0]} + )); + op->set_attr_dilations(ge::Operator::OpListInt( + {1, 1, (int64_t)dilations[0], (int64_t)dilations[1]} + )); + op->set_attr_groups(groups); + op->set_attr_data_format("NCHW"); + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + // set inputs : weight + const Mat& w_mat = blobs[0]; + auto op_const_weight = std::make_shared(w_mat.data, w_mat.type(), shape(w_mat), cv::format("%s_w", op_name.c_str())); + op->set_input_filter(*(op_const_weight->getOp())); + op->update_input_desc_filter(*(op_const_weight->getTensorDesc())); + // set inputs : bias + if (has_bias) + { + int out_channel = blobs[0].size[0]; + Mat b_mat({out_channel}, CV_32F, &biasvec[0]); + + std::vector bias_shape{out_channel}; + auto op_const_bias = std::make_shared(b_mat.data, b_mat.type(), bias_shape, cv::format("%s_b", op_name.c_str())); + op->set_input_bias(*(op_const_bias->getOp())); + op->update_input_desc_bias(*(op_const_bias->getTensorDesc())); + } + + // set outputs + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif + #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector > &inputs, @@ -978,793 +1041,6 @@ public: } #endif // HAVE_WEBNN - class ParallelConv : public cv::ParallelLoopBody - { - public: - enum { BLK_SIZE = 32, BLK_SIZE_CN = 64 }; - - const Mat* input_; - const Mat* weights_; - Mat* output_; - int outShape[4]; // used only for conv2d - std::vector kernel_size, pads_begin, pads_end, strides, dilations; - int ngroups_, nstripes_; - std::vector ofstab_; - const std::vector* biasvec_; - const std::vector* reluslope_; - const ActivationLayer* activ_; - bool is1x1_; - bool useAVX; - bool useAVX2; - bool useAVX512; - bool useRVV; - int blk_size_cn; - - ParallelConv() - : input_(0), weights_(0), output_(0), ngroups_(0), nstripes_(0), - biasvec_(0), reluslope_(0), activ_(0), is1x1_(false), useAVX(false), useAVX2(false), useAVX512(false), useRVV(false) - , blk_size_cn(0) - {} - - static void run( const Mat& input, Mat& output, const Mat& weights, - const std::vector& biasvec, - const std::vector& reluslope, - const std::vector& kernel_size, const std::vector& strides, - const std::vector& pads_begin, const std::vector& pads_end, - const std::vector& dilations, - const ActivationLayer* activ, int ngroups, int nstripes ) - { - size_t karea = std::accumulate(kernel_size.begin(), kernel_size.end(), - 1, std::multiplies()); - bool isConv1D = input.dims == 3; - bool isConv2D = input.dims == 4; - bool isConv3D = input.dims == 5; - CV_CheckEQ(static_cast(kernel_size.size()), input.dims - 2, ""); - CV_Assert_N(input.dims == output.dims, - input.size[0] == output.size[0], - weights.rows == output.size[1], - weights.cols == (input.size[1]/ngroups)*karea, - input.type() == output.type(), - input.type() == weights.type(), - input.type() == CV_32FC1, - input.isContinuous(), - output.isContinuous(), - biasvec.size() == (size_t)output.size[1]+2); - CV_Check(weights.step1(), weights.step1() % VEC_ALIGN == 0, ""); - CV_CheckType(weights.type(), CV_32FC1, ""); - ParallelConv p; - - p.input_ = &input; - p.weights_ = &weights; - p.output_ = &output; - int max_ind = isConv1D? 3: 4; - for( int i = 0; i < max_ind; i++ ) p.outShape[i] = output.size[i]; - p.outShape[1] /= ngroups; - - p.kernel_size = kernel_size; p.strides = strides; p.dilations = dilations; - p.pads_begin = pads_begin; p.pads_end = pads_end; - - p.ngroups_ = ngroups; - p.nstripes_ = nstripes; - - int inpCnAll = input.size[1]; - int depth = (input.dims == 5) ? input.size[2] : 1; - int width = input.size[input.dims - 1]; - int height = isConv1D? 1 : input.size[input.dims - 2]; - int inpCn = inpCnAll / ngroups; - - p.is1x1_ = (isConv2D && kernel_size[0] == 1 && kernel_size[1] == 1 && - pads_begin[0] == 0 && pads_begin[1] == 0) || - (isConv1D && pads_begin[0] == 0 && kernel_size[0] == 1); - - p.useAVX = checkHardwareSupport(CPU_AVX) && isConv2D; - p.useAVX2 = checkHardwareSupport(CPU_AVX2) && isConv2D; - p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX && isConv2D; - p.useRVV = checkHardwareSupport(CPU_RVV) && isConv2D; - - int kernel_d = isConv3D? kernel_size[0] : 1; - int kernel_h = isConv1D? 1 : kernel_size[kernel_size.size() - 2]; - int kernel_w = kernel_size.back(); - - int blk_size_cn0 = cvCeil(800./(kernel_w*kernel_h)); - int ncn = 16; - while (ncn*2 < blk_size_cn0 && ncn < inpCn) - ncn *= 2; - ncn = std::min(ncn, inpCn); - p.blk_size_cn = ncn; - - int dil_d = isConv3D? dilations[0] : 1; - int dil_h = isConv1D? 1 : dilations[dilations.size() - 2]; - int dil_w = dilations.back(); - - p.ofstab_.resize(karea * ncn); - int* ofstab = &p.ofstab_[0]; - - if (isConv1D) - { - for( int k = 0; k < ncn; k++ ) - for( int k_c = 0; k_c < kernel_w; k_c++ ) - ofstab[k*kernel_w + k_c] = k*width + k_c*dil_w; - } - else if (isConv2D) - { - for( int k = 0; k < ncn; k++ ) - for( int k_r = 0; k_r < kernel_h; k_r++ ) - for( int k_c = 0; k_c < kernel_w; k_c++ ) - ofstab[(k*kernel_h + k_r)*kernel_w + k_c] = - (k*height + k_r*dil_h)*width + k_c*dil_w; - } - else - { - for( int k = 0; k < ncn; k++ ) - for (int k_d = 0; k_d < kernel_d; k_d++) - for( int k_r = 0; k_r < kernel_h; k_r++ ) - for( int k_c = 0; k_c < kernel_w; k_c++ ) - ofstab[(k*kernel_d*kernel_h + k_d*kernel_h + k_r)*kernel_w + k_c] = - (k*depth*height + k_d*dil_d*height + k_r*dil_h)*width + k_c*dil_w; - } - - p.biasvec_ = &biasvec; - p.reluslope_ = &reluslope; - p.activ_ = p.reluslope_->empty() ? activ : 0; - - parallel_for_(Range(0, nstripes), p, nstripes); - } - - virtual void operator ()(const Range &r0) const CV_OVERRIDE - { - const int valign = ConvolutionLayerImpl::VEC_ALIGN; - int ngroups = ngroups_, batchSize = input_->size[0]*ngroups; - bool isConv1D = input_->dims == 3; - bool isConv2D = input_->dims == 4; - bool isConv3D = input_->dims == 5; - - int outW = output_->size[output_->dims - 1]; - int outH = isConv1D? 1 : output_->size[output_->dims - 2]; - int outCn = output_->size[1]/ngroups; - - int depth = isConv3D? input_->size[2] : 1; - int height = isConv1D? 1 : input_->size[input_->dims - 2]; - int width = input_->size[input_->dims - 1]; - int inpCn = input_->size[1]/ngroups; - - const int nstripes = nstripes_; - - int kernel_d = isConv3D? kernel_size[0] : 1; - int kernel_h = isConv1D? 1 : kernel_size[kernel_size.size() - 2]; - int kernel_w = kernel_size.back(); - int karea = kernel_w*kernel_h*kernel_d; - - int pad_d = isConv3D? pads_begin[0] : 0; - int pad_t = isConv1D? 0 : pads_begin[pads_begin.size() - 2]; - int pad_l = pads_begin.back(); - - int stride_d = isConv3D? strides[0] : 0; - int stride_h = isConv1D? 0 : strides[strides.size() - 2]; - int stride_w = strides.back(); - - int dilation_d = isConv3D? dilations[0] : 1; - int dilation_h = isConv1D? 1 : dilations[dilations.size() - 2]; - int dilation_w = dilations.back(); - - int i, j, k, d; - int inpPlaneSize = (int)input_->total(2); - int outPlaneSize = (int)output_->total(2); - bool is1x1 = is1x1_; - - int stripesPerSample; - int stripeSize; - Range r = r0; - bool depthWiseConvolution = !is1x1 && isConv2D && ngroups > 1 && inpCn == 1 && - outCn == 1 && kernel_d == 1 && dilation_d == 1 && stride_d == 0 && pad_d == 0 && - width >= 16 + dilation_w*(kernel_w - 1); - // for now only 3x3 depth-wise convolutions are supported - depthWiseConvolution = depthWiseConvolution && kernel_w == 3 && kernel_h == 3 && - // computing at most 1 pixel from each side can involve padding - max(stride_w, dilation_w) >= pad_l && max(stride_h, dilation_h) >= pad_t && - pad_l <= 1 && pad_t <= 1; - - if( !depthWiseConvolution && nstripes >= batchSize*2 ) - { - stripesPerSample = nstripes/batchSize; - stripeSize = (int)alignSize((outPlaneSize + stripesPerSample - 1)/stripesPerSample, valign); - stripeSize = std::min(stripeSize, outPlaneSize); - } - else - { - stripesPerSample = 1; - int samplesPerStripe = std::max((batchSize + nstripes - 1)/nstripes, 1); - r.start *= samplesPerStripe; - r.end *= samplesPerStripe; - stripeSize = outPlaneSize; - } - - const float* data_inp0_ = input_->ptr(); - const int* ofstab = &ofstab_[0]; - const float* wptr_orig_ = weights_->ptr(); - size_t wstep = weights_->step1(); - const float* biasptr_ = &biasvec_->at(0); - const float* reluptr_ = reluslope_->empty() ? 0 : &reluslope_->at(0); - float* data_out0_ = output_->ptr(); - AutoBuffer rowbuf0_; - float* rowbuf0 = 0; - bool use_rowbuf = !depthWiseConvolution; - int blk_size = depthWiseConvolution ? outPlaneSize : min((int)BLK_SIZE, stripeSize); - - // im2row buffer is not used for depth-wise convolution - if(use_rowbuf) - { - size_t rowbufsz = alignSize(karea*blk_size_cn, valign)*min((int)BLK_SIZE, blk_size); - //printf("karea=%d, blk_size_cn=%d, rowbufsz=%d, stripeSize=%d\n", karea, blk_size_cn, (int)rowbufsz, stripeSize); - rowbuf0_.allocate(rowbufsz + valign); - rowbuf0 = alignPtr(rowbuf0_.data(), (int)(valign*sizeof(float))); - // we clear the buffer once; ultimately, it lets us to avoid - // tail processing after running the unrolled/vectorized loop. - // the main idea is to make sure that the tail (a.k.a. padding) of each row - // (i.e. the elements with indices between vsz=karea*ncn and vsz_a) - // does not contain NaNs or Infs. Because the padding in the weights - // matrix is explicitly initialized with 0's, we handle all other - // cases nicely, i.e. we can skip expliciting re-initialization - // of the padding - we just retain elements from the previous iteration - // of the loop over channels (cn0). - memset(rowbuf0, 0, rowbufsz*sizeof(rowbuf0[0]) ); - } - - for( int stripe = r.start; stripe < r.end; stripe++ ) - { - int subsampleIdx = stripe/stripesPerSample; - if( subsampleIdx >= batchSize ) - break; - int stripeStart = (int)((stripe - subsampleIdx*stripesPerSample)*stripeSize); - int stripeEnd = (int)std::min(stripeStart + stripeSize, outPlaneSize); - const float* data_inp0 = data_inp0_ + subsampleIdx*inpPlaneSize*inpCn; - float* data_out0 = data_out0_ + subsampleIdx*outPlaneSize*outCn; - int startOutCn = (subsampleIdx % ngroups)*outCn; - const float* wptr_orig = wptr_orig_ + wstep*startOutCn; - const float* biasptr = biasptr_ + startOutCn; - - for( int cn0 = 0; cn0 < inpCn; cn0 += blk_size_cn ) - { - int cn1 = std::min(cn0 + blk_size_cn, inpCn); - int ncn = cn1 - cn0, vsz = karea*ncn; - int vsz_a = (int)alignSize(vsz, valign); - const float* wptr = wptr_orig + cn0*karea; - // we apply [Channels][P]ReLU (if any) during the final pass only. - const float* relu = cn1 == inpCn && reluptr_ ? reluptr_ + startOutCn : 0; - - for( int ofs0 = stripeStart; ofs0 < stripeEnd; ofs0 += blk_size ) - { - int ofs, ofs1 = std::min(ofs0 + blk_size, stripeEnd); - int bsz = ofs1 - ofs0; - - int out_d = ofs0 / (outH * outW); - int out_i = (ofs0 - out_d * outH * outW) / outW; - int out_j = ofs0 % outW; - - if (depthWiseConvolution) - { - CV_Assert(out_i == 0 && out_j == 0); - int in_d = out_d * stride_d - pad_d; - const float* inptr_ = data_inp0 + (cn0*depth*height + in_d*height)*width; - float* outptr_ = data_out0 + ofs0; - - #if CV_TRY_AVX2 - if(useAVX2) - opt_AVX2::fastDepthwiseConv(wptr, kernel_h, kernel_w, - stride_h, stride_w, dilation_h, dilation_w, pad_t, pad_l, - biasptr, relu, inptr_, height, width, outptr_, out_d, outH, outW); - else - #endif - #if CV_TRY_AVX - if(useAVX) - opt_AVX::fastDepthwiseConv(wptr, kernel_h, kernel_w, - stride_h, stride_w, dilation_h, dilation_w, pad_t, pad_l, - biasptr, relu, inptr_, height, width, outptr_, out_d, outH, outW); - else - #endif - #if CV_TRY_RVV - if(useRVV) - opt_RVV::fastDepthwiseConv(wptr, kernel_h, kernel_w, - stride_h, stride_w, dilation_h, dilation_w, pad_t, pad_l, - biasptr, relu, inptr_, height, width, outptr_, out_d, outH, outW); - else - #endif - { - const float w00_ = wptr[0], w01_ = wptr[1], w02_ = wptr[2], - w10 = wptr[3], w11 = wptr[4], w12 = wptr[5], - w20_ = wptr[6], w21_ = wptr[7], w22_ = wptr[8]; - int outW1 = min(outW, (width - dilation_w*(kernel_w - 1) + pad_l)/stride_w); - float relu_coeff = relu ? relu[out_d] : 1.f, bias = biasptr[out_d]; - - for (int out_i = 0; out_i < outH; out_i++) - { - int in_i = out_i * stride_h - pad_t, out_j = 0; - const float* imgptr0 = inptr_ + in_i*width; - const float* imgptr1 = imgptr0 + dilation_h*width; - const float* imgptr2 = imgptr0 + (dilation_h*2)*width; - float out, w00 = w00_, w01 = w01_, w02 = w02_; - float w20 = w20_, w21 = w21_, w22 = w22_; - if (in_i < 0) - { - w00 = w01 = w02 = 0.f; - imgptr0 = imgptr1; - } - else if (in_i + dilation_h*(kernel_h-1) >= height) - { - w20 = w21 = w22 = 0.f; - imgptr2 = imgptr1; - } - float* outptr = outptr_ + out_i*outW; - if (pad_l > 0) - { - out = imgptr0[0]*w01 + imgptr0[dilation_w]*w02 + - imgptr1[0]*w11 + imgptr1[dilation_w]*w12 + - imgptr2[0]*w21 + imgptr2[dilation_w]*w22 + bias; - if (relu) - out = out > 0.f ? out : out*relu_coeff; - outptr[0] = out; - out_j = 1; - } - - #if CV_SIMD - // maybe with AVX or AVX512 strided depthwise convolution - // can be accelerated with vector code, but with 4xfloat vectors - // it's hardly the case - if( stride_w == 1 ) - { - const int VECSZ = v_float32::nlanes; - const int out_delta = VECSZ/stride_w; - v_float32 vw00 = vx_setall_f32(w00), vw01 = vx_setall_f32(w01), vw02 = vx_setall_f32(w02), - vw10 = vx_setall_f32(w10), vw11 = vx_setall_f32(w11), vw12 = vx_setall_f32(w12), - vw20 = vx_setall_f32(w20), vw21 = vx_setall_f32(w21), vw22 = vx_setall_f32(w22); - v_float32 z = vx_setzero_f32(), vbias = vx_setall_f32(bias), vrc = vx_setall_f32(relu_coeff); - for( ; out_j < outW1; out_j += out_delta ) - { - if (out_j + out_delta > outW1) - { - if (out_j <= pad_l) - break; - out_j = outW1 - out_delta; - } - int in_j = out_j * stride_w - pad_l; - v_float32 v00 = vx_load(imgptr0 + in_j), - v01 = vx_load(imgptr0 + in_j + dilation_w), - v02 = vx_load(imgptr0 + in_j + dilation_w*2), - v10 = vx_load(imgptr1 + in_j), - v11 = vx_load(imgptr1 + in_j + dilation_w), - v12 = vx_load(imgptr1 + in_j + dilation_w*2), - v20 = vx_load(imgptr2 + in_j), - v21 = vx_load(imgptr2 + in_j + dilation_w), - v22 = vx_load(imgptr2 + in_j + dilation_w*2); - - v_float32 vout = v00*vw00 + v01*vw01 + v02*vw02 + - v10*vw10 + v11*vw11 + v12*vw12 + - v20*vw20 + v21*vw21 + v22*vw22 + vbias; - if (relu) - vout = v_select(vout > z, vout, vout*vrc); - v_store(outptr + out_j, vout); - } - } - #endif - for (; out_j < outW1; out_j++) - { - int in_j = out_j * stride_w - pad_l; - out = imgptr0[in_j]*w00 + imgptr0[in_j + dilation_w]*w01 + imgptr0[in_j + dilation_w*2]*w02 + - imgptr1[in_j]*w10 + imgptr1[in_j + dilation_w]*w11 + imgptr1[in_j + dilation_w*2]*w12 + - imgptr2[in_j]*w20 + imgptr2[in_j + dilation_w]*w21 + imgptr2[in_j + dilation_w*2]*w22 + bias; - if (relu) - out = out > 0.f ? out : out*relu_coeff; - outptr[out_j] = out; - } - - for (; out_j < outW; out_j++ ) - { - int in_j0 = out_j * stride_w - pad_l, in_j1 = in_j0 + dilation_w, in_j2 = in_j0 + dilation_w*2; - float s0 = 1.f, s1 = 1.f, s2 = 1.f; - if (in_j0 >= width) - { - in_j0 = 0; - s0 = 0.f; - } - if (in_j1 >= width) - { - in_j1 = 0; - s1 = 0.f; - } - if (in_j2 >= width) - { - in_j2 = 0; - s2 = 0.f; - } - out = imgptr0[in_j0]*w00*s0 + imgptr0[in_j1]*w01*s1 + imgptr0[in_j2]*w02*s2 + - imgptr1[in_j0]*w10*s0 + imgptr1[in_j1]*w11*s1 + imgptr1[in_j2]*w12*s2 + - imgptr2[in_j0]*w20*s0 + imgptr2[in_j1]*w21*s1 + imgptr2[in_j2]*w22*s2 + bias; - if (relu) - out = out > 0.f ? out : out*relu_coeff; - outptr[out_j] = out; - } - } - } - continue; - } - - // do im2row for a part of input tensor - float* rowbuf = rowbuf0; - - if (isConv1D) - { - for( ofs = ofs0; ofs < ofs1; out_j = 0, ++out_i ) - { - int delta = std::min(ofs1 - ofs, outW - out_j); - int out_j1 = out_j + delta; - - int in_j = out_j * stride_w - pad_l; - const float* imgptr = data_inp0 + cn0*width + in_j; - ofs += delta; - - // do im2row for a part of input tensor - if( is1x1 ) - { - for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w ) - { - for( k = 0; k < vsz; k++ ) - rowbuf[k] = imgptr[k*inpPlaneSize]; - } - } - else - { - for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w, in_j += stride_w ) - { - // this condition should be true for most of the tensor elements, i.e. - // most of the time the kernel aperture is inside the tensor X-Y plane. - if( out_j + 2 <= out_j1 && 0 <= in_j && in_j + stride_w*2 <= width - (kernel_w-1)*dilation_w ) - { - for( k = 0; k < vsz; k++ ) - { - int k1 = ofstab[k]; - float v0 = imgptr[k1]; - float v1 = imgptr[k1 + stride_w]; - rowbuf[k] = v0; - rowbuf[k+vsz_a] = v1; - } - out_j++; - rowbuf += vsz_a; - imgptr += stride_w; - in_j += stride_w; - } - else - { - int i0 = std::max(0, (-in_j + dilation_w-1)/dilation_w); - int i1 = std::min(kernel_w, (width - in_j + dilation_w-1)/dilation_w); - - // here some non-continuous sub-row of the row will not be - // filled from the tensor; we need to make sure that the uncovered - // elements are explicitly set to 0's. the easiest way is to - // set all the elements to 0's before the loop. - memset(rowbuf, 0, vsz*sizeof(rowbuf[0])); - for( k = 0; k < ncn; k++ ) - { - for( i = i0; i < i1; i++ ) - { - int imgofs = k*width + i*dilation_w; - rowbuf[k*kernel_w + i] = imgptr[imgofs]; - } - } - } - } - } - } - } - else if (isConv2D) - { - if( is1x1 && stride_w == 1 && stride_h == 1 ) - { - const float* imgptr = data_inp0 + (cn0*height + out_i)*width + out_j; - for( int j = 0; j < bsz; j++, rowbuf += vsz_a ) - { - if( j + 4 <= bsz ) - { - k = 0; - #if CV_SIMD128 - for( ; k <= vsz - 4; k += 4 ) - { - const float* inp = imgptr + j + k*inpPlaneSize; - v_float32x4 p0 = v_load(inp), p1 = v_load(inp + inpPlaneSize); - v_float32x4 p2 = v_load(inp + inpPlaneSize*2), p3 = v_load(inp + inpPlaneSize*3); - v_float32x4 r0, r1, r2, r3; - v_transpose4x4(p0, p1, p2, p3, r0, r1, r2, r3); - v_store(rowbuf + k, r0); - v_store(rowbuf + k + vsz_a, r1); - v_store(rowbuf + k + vsz_a*2, r2); - v_store(rowbuf + k + vsz_a*3, r3); - } - #endif - for( ; k < vsz; k++ ) - { - const float* inp = imgptr + j + k*inpPlaneSize; - float v0 = inp[0], v1 = inp[1], v2 = inp[2], v3 = inp[3]; - rowbuf[k] = v0; - rowbuf[k + vsz_a] = v1; - rowbuf[k + vsz_a*2] = v2; - rowbuf[k + vsz_a*3] = v3; - } - j += 3; - rowbuf += vsz_a*3; - } - else - { - for( k = 0; k < vsz; k++ ) - { - rowbuf[k] = imgptr[j + k*inpPlaneSize]; - } - } - } - } - else - for( ofs = ofs0; ofs < ofs1; out_j = 0, ++out_i ) - { - int delta = std::min(ofs1 - ofs, outW - out_j); - int out_j1 = out_j + delta; - - int in_i = out_i * stride_h - pad_t; - int in_j = out_j * stride_w - pad_l; - const float* imgptr = data_inp0 + (cn0*height + in_i)*width + in_j; - ofs += delta; - - // do im2row for a part of input tensor - if( is1x1 ) - { - for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w ) - { - for( k = 0; k < vsz; k++ ) - rowbuf[k] = imgptr[k*inpPlaneSize]; - } - } - else - { - bool ok_i = 0 <= in_i && in_i < height - (kernel_h-1)*dilation_h; - int i0 = std::max(0, (-in_i + dilation_h-1)/dilation_h); - int i1 = std::min(kernel_h, (height - in_i + dilation_h-1)/dilation_h); - - for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w, in_j += stride_w ) - { - // this condition should be true for most of the tensor elements, i.e. - // most of the time the kernel aperture is inside the tensor X-Y plane. - if( ok_i && out_j + 2 <= out_j1 && 0 <= in_j && in_j + stride_w*2 <= width - (kernel_w-1)*dilation_w ) - { - for( k = 0; k < vsz; k++ ) - { - int k1 = ofstab[k]; - float v0 = imgptr[k1]; - float v1 = imgptr[k1 + stride_w]; - rowbuf[k] = v0; - rowbuf[k+vsz_a] = v1; - } - out_j++; - rowbuf += vsz_a; - imgptr += stride_w; - in_j += stride_w; - } - else - { - int j0 = std::max(0, (-in_j + dilation_w-1)/dilation_w); - int j1 = std::min(kernel_w, (width - in_j + dilation_w-1)/dilation_w); - - // here some non-continuous sub-row of the row will not be - // filled from the tensor; we need to make sure that the uncovered - // elements are explicitly set to 0's. the easiest way is to - // set all the elements to 0's before the loop. - memset(rowbuf, 0, vsz*sizeof(rowbuf[0])); - for( k = 0; k < ncn; k++ ) - { - for( i = i0; i < i1; i++ ) - { - for( j = j0; j < j1; j++ ) - { - int imgofs = k*(width*height) + i*(dilation_h*width) + j*dilation_w; - rowbuf[(k*kernel_h + i)*kernel_w + j] = imgptr[imgofs]; - } - } - } - } - } - } - } - } - else - { - for( ofs = ofs0; ofs < ofs1; out_d += (out_i + 1) / outH, out_i = (out_i + 1) % outH, out_j = 0 ) - { - int delta = std::min(ofs1 - ofs, outW - out_j); - int out_j1 = out_j + delta; - - int in_d = out_d * stride_d - pad_d; - int in_i = out_i * stride_h - pad_t; - int in_j = out_j * stride_w - pad_l; - const float* imgptr = data_inp0 + (cn0*depth*height + in_d*height + in_i)*width + in_j; - ofs += delta; - - int d0 = std::max(0, (-in_d + dilation_d - 1) / dilation_d); - int d1 = std::min(kernel_d, (depth - in_d + dilation_d - 1) / dilation_d); - - int i0 = std::max(0, (-in_i + dilation_h-1)/dilation_h); - int i1 = std::min(kernel_h, (height - in_i + dilation_h-1)/dilation_h); - - for( ; out_j < out_j1; out_j++, rowbuf += vsz_a, imgptr += stride_w, in_j += stride_w ) - { - int j0 = std::max(0, (-in_j + dilation_w-1)/dilation_w); - int j1 = std::min(kernel_w, (width - in_j + dilation_w-1)/dilation_w); - - // here some non-continuous sub-row of the row will not be - // filled from the tensor; we need to make sure that the uncovered - // elements are explicitly set to 0's. the easiest way is to - // set all the elements to 0's before the loop. - memset(rowbuf, 0, vsz*sizeof(rowbuf[0])); - for( k = 0; k < ncn; k++ ) - { - for ( d = d0; d < d1; d++) - { - for( i = i0; i < i1; i++ ) - { - for( j = j0; j < j1; j++ ) - { - int imgofs = k*(depth*width*height) + d*dilation_d*width*height + i*(dilation_h*width) + j*dilation_w; - rowbuf[(k*kernel_d*kernel_h + d*kernel_h + i)*kernel_w + j] = imgptr[imgofs]; - } - } - } - } - } - } - } - // now compute dot product of the weights - // and im2row-transformed part of the tensor - #if CV_TRY_AVX512_SKX - /* AVX512 convolution requires an alignment of 16, and ROI is only there for larger vector sizes */ - if(useAVX512) - opt_AVX512_SKX::fastConv(wptr, wstep, biasptr, rowbuf0, data_out0 + ofs0, - outShape, bsz, vsz, vsz_a, relu, cn0 == 0); - else - #endif - #if CV_TRY_AVX2 - if(useAVX2) - opt_AVX2::fastConv(wptr, wstep, biasptr, rowbuf0, data_out0 + ofs0, - outShape, bsz, vsz, vsz_a, relu, cn0 == 0); - else - #endif - #if CV_TRY_AVX - if(useAVX) - opt_AVX::fastConv(wptr, wstep, biasptr, rowbuf0, data_out0 + ofs0, - outShape, bsz, vsz, vsz_a, relu, cn0 == 0); - else - #endif - #if CV_TRY_RVV - if(useRVV) - opt_RVV::fastConv(wptr, wstep, biasptr, rowbuf0, data_out0 + ofs0, - outShape, bsz, vsz, vsz_a, relu, cn0 == 0); - else - #endif - for( int i = 0; i < outCn; i += 2 ) - { - const float* wptr0 = wptr + i*wstep; - const float* wptr1 = wptr0 + wstep; - float* outptr0 = data_out0 + ofs0 + i*outPlaneSize; - float* outptr1 = outptr0 + outPlaneSize; - float bias0 = biasptr[i], bias1 = biasptr[i+1]; - float r0 = 1.f, r1 = 1.f; - - if( i+1 >= outCn ) - { - wptr1 = wptr0; - outptr1 = outptr0; - bias1 = bias0; - } - - if( relu ) - { - r0 = relu[i]; r1 = relu[i+1]; - if( i+1 >= outCn ) - r1 = r0; - } - - int j = 0; - #if CV_SIMD128 - v_float32x4 vr0 = v_setall_f32(r0), vr1 = v_setall_f32(r1), z = v_setzero_f32(); - - for( ; j <= bsz - 4; j += 4 ) - { - const float* rptr = rowbuf0 + j*vsz_a; - v_float32x4 s0, s1; - - if( cn0 == 0 ) - { - s0 = v_setall_f32(bias0); - s1 = v_setall_f32(bias1); - } - else - { - s0 = v_load(outptr0 + j); - s1 = v_load(outptr1 + j); - } - - v_float32x4 vs00 = v_setzero_f32(), vs01 = v_setzero_f32(), - vs02 = v_setzero_f32(), vs03 = v_setzero_f32(), - vs10 = v_setzero_f32(), vs11 = v_setzero_f32(), - vs12 = v_setzero_f32(), vs13 = v_setzero_f32(); - for( k = 0; k < vsz; k += 4, rptr += 4 ) - { - v_float32x4 w0 = v_load_aligned(wptr0 + k); - v_float32x4 w1 = v_load_aligned(wptr1 + k); - v_float32x4 r0 = v_load_aligned(rptr); - v_float32x4 r1 = v_load_aligned(rptr + vsz_a); - v_float32x4 r2 = v_load_aligned(rptr + vsz_a*2); - v_float32x4 r3 = v_load_aligned(rptr + vsz_a*3); - - vs00 = v_fma(w0, r0, vs00); - vs01 = v_fma(w0, r1, vs01); - vs02 = v_fma(w0, r2, vs02); - vs03 = v_fma(w0, r3, vs03); - - vs10 = v_fma(w1, r0, vs10); - vs11 = v_fma(w1, r1, vs11); - vs12 = v_fma(w1, r2, vs12); - vs13 = v_fma(w1, r3, vs13); - } - s0 += v_reduce_sum4(vs00, vs01, vs02, vs03); - s1 += v_reduce_sum4(vs10, vs11, vs12, vs13); - if( relu ) - { - s0 = v_select(s0 > z, s0, s0*vr0); - s1 = v_select(s1 > z, s1, s1*vr1); - } - - v_store(outptr0 + j, s0); - v_store(outptr1 + j, s1); - } - #endif - for( ; j < bsz; j++ ) - { - const float* rptr = rowbuf0 + j*vsz_a; - float s00, s10; - - if( cn0 == 0 ) - { - s00 = bias0; - s10 = bias1; - } - else - { - s00 = outptr0[j]; - s10 = outptr1[j]; - } - - for( k = 0; k < vsz; k++ ) - { - float r0 = rptr[k]; - s00 += wptr0[k]*r0; - s10 += wptr1[k]*r0; - } - if( relu ) - { - s00 = s00 > 0.f ? s00 : s00*r0; - s10 = s10 > 0.f ? s10 : s10*r1; - } - - outptr0[j] = s00; - outptr1[j] = s10; - } - } - } - } - - if( activ_ ) - activ_->forwardSlice(data_out0 + stripeStart, data_out0 + stripeStart, - (int)(stripeEnd - stripeStart), - outPlaneSize, startOutCn, startOutCn + outCn); - } - } - }; - #ifdef HAVE_OPENCL bool forward_ocl(InputArrayOfArrays inps, OutputArrayOfArrays outs, OutputArrayOfArrays internals) { @@ -1815,7 +1091,10 @@ public: config.in_shape = shape(inputs[0]); config.out_shape = shape(outputs[0]); config.kernel = kernel; - config.pad = pad; + // pads_begin: 0 - pad_top, 1 - pad_left + // pads_end: 0 - pad_bottom, 1 - pad_right + std::vector pads = {int(pads_begin[0]), int(pads_end[0]), int(pads_begin[1]), int(pads_end[1])}; + config.pads = pads; config.stride = stride; config.dilation = dilation; if (inputs[0].dims != 4 && inputs[0].dims != umat_blobs[0].dims) @@ -1970,9 +1249,6 @@ public: if (blobs.empty()) { variableWeight = true; - if (fastWeights.data != inputs[1].data) - fastWeights = inputs[1].clone(); - Mat wm = inputs[1].reshape(1, outCn); if (wm.data != weightsMat.data) { @@ -2032,7 +1308,7 @@ public: } #ifdef HAVE_TENGINE - bool tengine_ret = false; ; + bool tengine_ret = false; std::vector teng_in, teng_out; inputs_arr.getMatVector(teng_in); @@ -2057,20 +1333,24 @@ public: /* tengine_init will run when first time. */ if(NULL == tengine_graph) { + // pads_begin: 0 - pad_top, 1 - pad_left + // pads_end: 0 - pad_bottom, 1 - pad_right + // pad_h0: pad_top, pad_h1: pad_bottom + // pad_w0: pad_left, pad_w1: pad_right tengine_graph = tengine_init(name.c_str(), input_, inch, ngroups, in_h, in_w, output_, out_b, outch, out_h, out_w, kernel_, kernel_size.size(), kernel.height, kernel.width, teg_bias, stride.height, stride.width, - pad.height, pad.width, dilation.height, dilation.width, + pads_begin[0], pads_end[0], pads_begin[1], pads_end[1], dilation.height, dilation.width, weightsMat.step1(), padMode, tengine_graph, nstripes); - /*printf("Init(%s): input=%p(%d %d %d %d ),output=%p(%d %d %d %d ),kernel=%p(%ld %d %d ), bias=%p ," - "stride(%d %d), pad(%d %d), dilation(%d %d) ,weightsMat=%ld, padMode=%s ,tengine_graph = %p \n", - name.c_str(),input_, inch, ngroups, in_h, in_w, - output_, out_b, outch, out_h, out_w, - kernel_, kernel_size.size(), kernel.height, kernel.width, - teg_bias, stride.height, stride.width, - pad.height, pad.width, dilation.height, dilation.width, - weightsMat.step1(), padMode.c_str() ,tengine_graph);*/ + // printf("Init(%s): input=%p(%d %d %d %d ),output=%p(%d %d %d %d ),kernel=%p(%ld %d %d ), bias=%p ," + // "stride(%d %d), pad(%d %d %d %d), dilation(%d %d) ,weightsMat=%ld, padMode=%s ,tengine_graph = %p \n", + // name.c_str(),input_, inch, ngroups, in_h, in_w, + // output_, out_b, outch, out_h, out_w, + // kernel_, kernel_size.size(), kernel.height, kernel.width, + // teg_bias, stride.height, stride.width, + // pads_begin[0], pads_end[0], pads_begin[1], pads_end[1], dilation.height, dilation.width, + // weightsMat.step1(), padMode.c_str() ,tengine_graph); } if(NULL != tengine_graph) { @@ -2088,38 +1368,27 @@ public: #endif { int nstripes = std::max(getNumThreads(), 1); + int conv_dim = CONV_2D; + if (inputs[0].dims == 3) + conv_dim = CONV_1D; + if (inputs[0].dims == 5) + conv_dim = CONV_3D; - // Initialization of FastCovn2d - if ((!fastConv2dImpl || variableWeight) && inputs[0].dims == 4) + // Initialization of FastCovn2d, pack weight. + if (!fastConvImpl || variableWeight) { int K = outputs[0].size[1]; int C = inputs[0].size[1]; - int Hk = kernel_size[kernel_size.size() - 2]; - int Wk = kernel_size.back(); + + // Winograd only works when input h and w >= 12. + bool canUseWinograd = useWinograd && conv_dim == CONV_2D && inputs[0].size[2] >= 12 && inputs[0].size[3] >= 12; CV_Assert(outputs[0].size[1] % ngroups == 0); - int stride_h = strides[strides.size() - 2]; - int stride_w = strides.back(); - - int dilation_h = dilations[dilations.size() - 2]; - int dilation_w = dilations.back(); - float* weightsPtr = fastWeights.ptr(); - CV_Assert(weightsPtr); - - fastConv2dImpl = initFastConv2d(ngroups, K, C, Hk, Wk, stride_w, stride_h, - dilation_w, dilation_h, pads_begin, pads_end, weightsPtr, &biasvec[0]); + fastConvImpl = initFastConv(weightsMat, &biasvec[0], ngroups, K, C, kernel_size, strides, + dilations, pads_begin, pads_end, conv_dim, canUseWinograd); } - if (fastConv2dImpl) - { - runFastConv2d(inputs[0], outputs[0], fastConv2dImpl, nstripes, activ); - return; - } - - // Use only for Conv1D and Conv3D. - ParallelConv::run(inputs[0], outputs[0], weightsMat, biasvec, reluslope, - kernel_size, strides, pads_begin, pads_end, dilations, activ.get(), ngroups, nstripes); - + runFastConv(inputs[0], outputs[0], fastConvImpl, nstripes, activ, reluslope, fusedAdd); } } @@ -2446,6 +1715,7 @@ public: useAVX2 = checkHardwareSupport(CPU_AVX2); useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX; useRVV = checkHardwareSupport(CPU_RVV); + useLASX = checkHardwareSupport(CPU_LASX); } void operator()(const Range& range_) const CV_OVERRIDE @@ -2483,6 +1753,11 @@ public: opt_RVV::fastGEMM( aptr, astep, bptr, bstep, cptr, cstep, mmax, kmax, nmax ); } else + #endif + #if CV_TRY_LASX + if( useLASX ) + opt_LASX::fastGEMM( aptr, astep, bptr, bstep, cptr, cstep, mmax, kmax, nmax ); + else #endif for( m = 0; m < mmax; m += 2 ) { @@ -2583,6 +1858,7 @@ public: bool useAVX2; bool useAVX512; bool useRVV; + bool useLASX; }; class Col2ImInvoker : public cv::ParallelLoopBody diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index 353ce8c0b4..a4b71ddddf 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -48,6 +48,7 @@ #include "../ie_ngraph.hpp" #include "../op_vkcom.hpp" #include "../op_webnn.hpp" +#include "../op_cann.hpp" #include #include @@ -186,6 +187,12 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + return func.initCannOp(inputsWrapper, index, nodes); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, const std::vector >& nodes) CV_OVERRIDE @@ -350,7 +357,8 @@ struct ReLUFunctor : public BaseFunctor return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE || - backendId == DNN_BACKEND_VKCOM; + backendId == DNN_BACKEND_VKCOM || + backendId == DNN_BACKEND_CANN; } void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const @@ -450,6 +458,42 @@ struct ReLUFunctor : public BaseFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + auto op_x = nodes[0].dynamicCast()->getOp(); + auto x_desc = x->getTensorDesc(); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + if (slope) + { + std::string op_name = cv::format("leakyrelu_%d", index); + auto op = std::make_shared(op_name); + + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + + op->set_attr_negative_slope(slope); + + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } + + std::string op_name = cv::format("relu_%d", index); + auto op = std::make_shared(op_name); // FIXIT: Relu6? + + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif + #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) { @@ -525,7 +569,8 @@ struct ReLU6Functor : public BaseFunctor return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE || - backendId == DNN_BACKEND_WEBNN; + backendId == DNN_BACKEND_WEBNN || + backendId == DNN_BACKEND_CANN; } void apply(const float* srcptr, float* dstptr, int len, size_t planeSize, int cn0, int cn1) const @@ -607,6 +652,37 @@ struct ReLU6Functor : public BaseFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("clip_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + Mat min_value_mat(1, 1, CV_32F, Scalar(minValue)); + std::vector shape_{1}; + auto op_const_minv = std::make_shared(min_value_mat.data, min_value_mat.type(), shape_, cv::format("%s_min_value", op_name.c_str())); + op->set_input_clip_value_min(*(op_const_minv->getOp())); + op->update_input_desc_clip_value_min(*(op_const_minv->getTensorDesc())); + + Mat max_value_mat(1, 1, CV_32F, Scalar(maxValue)); + auto op_const_maxv = std::make_shared(max_value_mat.data, max_value_mat.type(), shape_, cv::format("%s_max_value", op_name.c_str())); + op->set_input_clip_value_min(*(op_const_maxv->getOp())); + op->update_input_desc_clip_value_min(*(op_const_maxv->getTensorDesc())); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif + #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) @@ -728,6 +804,12 @@ struct BaseDefaultFunctor : public BaseFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + CV_Error(Error::StsNotImplemented, ""); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) @@ -767,7 +849,8 @@ struct TanHFunctor : public BaseDefaultFunctor #endif return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_HALIDE; + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -790,6 +873,26 @@ struct TanHFunctor : public BaseDefaultFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("tanh_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) { @@ -811,7 +914,9 @@ struct SwishFunctor : public BaseDefaultFunctor { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_HALIDE || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -834,6 +939,28 @@ struct SwishFunctor : public BaseDefaultFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("swish_%d", index); + auto op = std::make_shared(op_name); + + op->set_attr_scale(1.0f); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) { @@ -856,7 +983,9 @@ struct MishFunctor : public BaseDefaultFunctor { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_HALIDE || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -887,6 +1016,26 @@ struct MishFunctor : public BaseDefaultFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("mish_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) { @@ -918,7 +1067,8 @@ struct SigmoidFunctor : public BaseDefaultFunctor #endif return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_HALIDE; + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -941,6 +1091,25 @@ struct SigmoidFunctor : public BaseDefaultFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("sigmoid_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) @@ -970,7 +1139,8 @@ struct ELUFunctor : public BaseDefaultFunctor #endif return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_HALIDE; + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -998,6 +1168,28 @@ struct ELUFunctor : public BaseDefaultFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("elu_%d", index); + auto op = std::make_shared(op_name); + + op->set_attr_alpha(alpha); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) { @@ -1023,7 +1215,8 @@ struct AbsValFunctor : public BaseDefaultFunctor #endif return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_HALIDE; + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -1046,6 +1239,25 @@ struct AbsValFunctor : public BaseDefaultFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("abs_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) @@ -1071,7 +1283,8 @@ struct BNLLFunctor : public BaseDefaultFunctor { return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_HALIDE; + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -1087,6 +1300,26 @@ struct BNLLFunctor : public BaseDefaultFunctor } #endif +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("bnll_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_HALIDE void attachHalide(const Halide::Expr& input, Halide::Func& top) { @@ -1108,7 +1341,7 @@ struct CeilFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE; + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE; } inline float calculate(float x) const @@ -1123,6 +1356,26 @@ struct CeilFunctor : public BaseDefaultFunctor } #endif +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("bnll_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_HALIDE void attachHalide(const Halide::Expr& input, Halide::Func& top) { @@ -1143,7 +1396,10 @@ struct FloorFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE; + return backendId == DNN_BACKEND_OPENCV || + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_HALIDE || + backendId == DNN_BACKEND_CANN; } inline float calculate(float x) const @@ -1158,6 +1414,26 @@ struct FloorFunctor : public BaseDefaultFunctor } #endif +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + + std::string op_name = cv::format("floor_%d", index); + auto op = std::make_shared(op_name); + + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + #ifdef HAVE_HALIDE void attachHalide(const Halide::Expr& input, Halide::Func& top) { @@ -1178,7 +1454,7 @@ struct LogFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE; + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE; } inline float calculate(float x) const @@ -1213,7 +1489,7 @@ struct RoundFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE; + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE; } inline float calculate(float x) const @@ -1253,7 +1529,7 @@ struct SqrtFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE; + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE; } inline float calculate(float x) const @@ -1295,7 +1571,7 @@ struct NotFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_HALIDE; + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE; } inline float calculate(float x) const @@ -1992,6 +2268,12 @@ struct PowerFunctor : public BaseFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + CV_Error(Error::StsNotImplemented, ""); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) @@ -2240,6 +2522,31 @@ struct ChannelsPReLUFunctor : public BaseFunctor } #endif // HAVE_HALIDE +#ifdef HAVE_CANN + Ptr initCannOp(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) + { + auto x = inputsWrapper[0].dynamicCast(); + auto op_x = nodes[0].dynamicCast()->getOp(); + auto x_desc = x->getTensorDesc(); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::string op_name = cv::format("prelu_%d", index); + auto op = std::make_shared(op_name); + + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + + std::vector shape_{scale.size[0]}; // scale should be a 1d of shape [n] tensor, and it is a 2d mat of shape [n, 1] in opencv + auto op_const_slope = std::make_shared(scale.data, scale.type(), shape_, cv::format("%s_weight", op_name.c_str())); + op->set_input_weight(*(op_const_slope->getOp())); + op->update_input_desc_weight(*(op_const_slope->getTensorDesc())); + + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH std::shared_ptr initNgraphAPI(const std::shared_ptr& node) diff --git a/modules/dnn/src/layers/eltwise_layer.cpp b/modules/dnn/src/layers/eltwise_layer.cpp index a67b0c4bb5..24a87bcc17 100644 --- a/modules/dnn/src/layers/eltwise_layer.cpp +++ b/modules/dnn/src/layers/eltwise_layer.cpp @@ -46,6 +46,8 @@ #include "../op_halide.hpp" #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" +#include "../op_cann.hpp" + #include #ifdef HAVE_OPENCL @@ -169,6 +171,11 @@ public: return channelsMode == ELTWISE_CHANNNELS_SAME; #endif +#ifdef HAVE_CANN + if (backendId == DNN_BACKEND_CANN) + return channelsMode == ELTWISE_CHANNNELS_SAME && coeffs.empty(); +#endif + if (backendId == DNN_BACKEND_CUDA) { if(channelsModeInput == ELTWISE_CHANNNELS_INPUT_0 || channelsModeInput == ELTWISE_CHANNNELS_INPUT_0_TRUNCATE) @@ -841,6 +848,47 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + CV_Assert(inputsWrapper.size() == 2); + CV_Assert(nodes.size() == 2); + + auto op_x1 = nodes[0].dynamicCast()->getOp(); + auto x1 = inputsWrapper[0].dynamicCast(); + auto x1_desc = x1->getTensorDesc(); + auto op_x2 = nodes[1].dynamicCast()->getOp(); + auto x2 = inputsWrapper[1].dynamicCast(); + auto x2_desc = x2->getTensorDesc(); + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::shared_ptr eltwise_operator = nullptr; + // add, mul, div, max, min + switch (op) + { +#define BUILD_CANN_ELTWISE_OP(op_type, class_name, op_name) \ + case op_type: { \ + auto eltwise_op = \ + std::make_shared(op_name); \ + eltwise_op->set_input_x1_by_name(*op_x1, "y"); \ + eltwise_op->set_input_x2_by_name(*op_x2, "y"); \ + eltwise_op->update_input_desc_x1(*x1_desc); \ + eltwise_op->update_input_desc_x2(*x2_desc); \ + eltwise_op->update_output_desc_y(*output_desc); \ + eltwise_operator = eltwise_op; \ + } break; + BUILD_CANN_ELTWISE_OP(SUM, Add, cv::format("add_%d", index)); + BUILD_CANN_ELTWISE_OP(PROD, Mul, cv::format("mul_%d", index)); + BUILD_CANN_ELTWISE_OP(DIV, Xdivy, cv::format("div_%d", index)); + BUILD_CANN_ELTWISE_OP(MAX, Maximum, cv::format("max_%d", index)); + BUILD_CANN_ELTWISE_OP(MIN, Minimum, cv::format("min_%d", index)); +#undef BUILD_CANN_ELTWISE_OP + default: CV_Error(Error::StsNotImplemented, "Unsupported eltwise operation"); + } + + return Ptr(new CannBackendNode(eltwise_operator)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, diff --git a/modules/dnn/src/layers/fast_convolution/depthwise_convolution.cpp b/modules/dnn/src/layers/fast_convolution/depthwise_convolution.cpp index 4eb47c46b2..0c471e8920 100644 --- a/modules/dnn/src/layers/fast_convolution/depthwise_convolution.cpp +++ b/modules/dnn/src/layers/fast_convolution/depthwise_convolution.cpp @@ -11,375 +11,404 @@ #include "../../precomp.hpp" #include "fast_convolution.hpp" +#include "../layers_common.hpp" namespace cv { namespace dnn { -static void depthWiseBlock(const float *inptr, float *outptr, const float *weights, float biasval, int *ofstab, int *yxtab, - float minval, float maxval, int Hi, int Wi, int H0, int W0, int ksize, int pad_top, int pad_left, - int dilation_y, int stride_x, int stride_y, int inner_xleft, int inner_xright, int inner_ytop, - int inner_ybottom, bool ifMinMaxAct, bool useSIMD, bool is3x3) +static void depthWiseBlockConv2D(const float* wptr, + int kernel_h, int kernel_w, + int stride_h, int stride_w, + int dilation_h, int dilation_w, + int pad_t, int pad_l, + const float* biasptr, const float* relu, + const float* inptr_, + int height, int width, + float* outptr_, + int out_d, int outH, int outW) { -#if CV_SIMD128 - v_float32x4 vminval = v_setall_f32(minval), vmaxval = v_setall_f32(maxval); + const float w00_ = wptr[0], w01_ = wptr[1], w02_ = wptr[2], + w10 = wptr[3], w11 = wptr[4], w12 = wptr[5], + w20_ = wptr[6], w21_ = wptr[7], w22_ = wptr[8]; + int outW1 = min(outW, (width - dilation_w*(kernel_w - 1) + pad_l)/stride_w); + float relu_coeff = relu ? relu[out_d] : 1.f, bias = biasptr[out_d]; - v_float32x4 w0 = v_setall_f32( - 0.f), w1 = w0, w2 = w0, w3 = w0, w4 = w0, w5 = w0, w6 = w0, w7 = w0, w8 = w0, vbias = w0; - if (useSIMD) + for (int out_i = 0; out_i < outH; out_i++) { - vbias = v_setall_f32(biasval); - if (is3x3) + int in_i = out_i * stride_h - pad_t, out_j = 0; + const float* imgptr0 = inptr_ + in_i*width; + const float* imgptr1 = imgptr0 + dilation_h*width; + const float* imgptr2 = imgptr0 + (dilation_h*2)*width; + float out, w00 = w00_, w01 = w01_, w02 = w02_; + float w20 = w20_, w21 = w21_, w22 = w22_; + if (in_i < 0) { - w0 = v_setall_f32(weights[0]); - w1 = v_setall_f32(weights[1]); - w2 = v_setall_f32(weights[2]); - w3 = v_setall_f32(weights[3]); - w4 = v_setall_f32(weights[4]); - w5 = v_setall_f32(weights[5]); - w6 = v_setall_f32(weights[6]); - w7 = v_setall_f32(weights[7]); - w8 = v_setall_f32(weights[8]); + w00 = w01 = w02 = 0.f; + imgptr0 = imgptr1; } - } -#endif - int dy0 = 1; - for (int y0 = 0; y0 < H0; y0 += dy0, outptr += W0 * dy0) - { -#if CV_SIMD128 - dy0 = inner_ytop <= y0 && y0 + 3 < inner_ybottom && is3x3 && stride_y == 1 && dilation_y == 1 - ? 3 : 1; -#endif - int x0 = 0, x1 = y0 >= inner_ytop && y0 < inner_ybottom ? inner_xleft : W0; - int yi_ = y0 * stride_y - pad_top; - - for (;;) + else if (in_i + dilation_h*(kernel_h-1) >= height) { - float s_0, s_1, s_2; - if (dy0 == 3) - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - s_0 = s_1 = s_2 = biasval; - for (int k = 0; k < ksize; k++) - { - int dy = yxtab[k * 2]; - int yi = yi_ + dy; - int xi = xi_ + yxtab[k * 2 + 1]; - float w = weights[k]; + w20 = w21 = w22 = 0.f; + imgptr2 = imgptr1; + } + + float* outptr = outptr_ + out_i*outW; + if (pad_l > 0) + { + out = imgptr0[0]*w01 + imgptr0[dilation_w]*w02 + + imgptr1[0]*w11 + imgptr1[dilation_w]*w12 + + imgptr2[0]*w21 + imgptr2[dilation_w]*w22 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[0] = out; + out_j = 1; + } - if ((unsigned) xi < (unsigned) Wi) - { - s_0 += inptr[yi * Wi + xi] * w; - s_1 += inptr[(yi + 1) * Wi + xi] * w; - s_2 += inptr[(yi + 2) * Wi + xi] * w; - } - } - s_0 = std::min(std::max(s_0, minval), maxval); - s_1 = std::min(std::max(s_1, minval), maxval); - s_2 = std::min(std::max(s_2, minval), maxval); - outptr[x0] = s_0; - outptr[x0 + W0] = s_1; - outptr[x0 + W0 * 2] = s_2; - } - } - else - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - s_0 = biasval; - for (int k = 0; k < ksize; k++) { - int dy = yxtab[k * 2]; - int yi = yi_ + dy; - int xi = xi_ + yxtab[k * 2 + 1]; - float w = weights[k]; - if (((unsigned) yi < (unsigned) Hi) & ((unsigned) xi < (unsigned) Wi)) - s_0 += inptr[yi * Wi + xi] * w; - } - s_0 = std::min(std::max(s_0, minval), maxval); - outptr[x0] = s_0; - } - } - if (x0 == W0) - break; - x1 = inner_xright; #if CV_SIMD128 - if (useSIMD) + const int VEC_NLANES = 4; + v_float32x4 vw00 = v_setall_f32(w00); + v_float32x4 vw01 = v_setall_f32(w01); + v_float32x4 vw02 = v_setall_f32(w02); + v_float32x4 vw10 = v_setall_f32(w10); + v_float32x4 vw11 = v_setall_f32(w11); + v_float32x4 vw12 = v_setall_f32(w12); + v_float32x4 vw20 = v_setall_f32(w20); + v_float32x4 vw21 = v_setall_f32(w21); + v_float32x4 vw22 = v_setall_f32(w22); + v_float32x4 z = v_setzero_f32(); + v_float32x4 vbias = v_setall_f32(bias); + v_float32x4 vrc = v_setall_f32(relu_coeff); + + if (stride_w == 1 || (stride_w == 2 && dilation_w == 1)) + { + if( stride_w == 1 ) { - if (is3x3) + for( ; out_j < outW1; out_j += VEC_NLANES ) { - if (dy0 == 3) + if (out_j + VEC_NLANES > outW1) { - for (; x0 <= x1 - FAST_VEC_NLANES; x0 += FAST_VEC_NLANES) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - - v_float32x4 s0, s1, s2; - v_float32x4 x00 = v_load(inptr_xi); - v_float32x4 x01 = v_load(inptr_xi + 1); - v_float32x4 x02 = v_load(inptr_xi + 2); - - v_float32x4 x10 = v_load(inptr_xi + Wi); - v_float32x4 x11 = v_load(inptr_xi + Wi + 1); - v_float32x4 x12 = v_load(inptr_xi + Wi + 2); - - v_float32x4 x20 = v_load(inptr_xi + Wi * 2); - v_float32x4 x21 = v_load(inptr_xi + Wi * 2 + 1); - v_float32x4 x22 = v_load(inptr_xi + Wi * 2 + 2); - - v_float32x4 x30 = v_load(inptr_xi + Wi * 3); - v_float32x4 x31 = v_load(inptr_xi + Wi * 3 + 1); - v_float32x4 x32 = v_load(inptr_xi + Wi * 3 + 2); - - v_float32x4 x40 = v_load(inptr_xi + Wi * 4); - v_float32x4 x41 = v_load(inptr_xi + Wi * 4 + 1); - v_float32x4 x42 = v_load(inptr_xi + Wi * 4 + 2); - - s0 = v_fma(x00, w0, vbias); - s1 = v_fma(x10, w0, vbias); - s2 = v_fma(x20, w0, vbias); - - s0 = v_fma(x01, w1, s0); - s1 = v_fma(x11, w1, s1); - s2 = v_fma(x21, w1, s2); - - s0 = v_fma(x02, w2, s0); - s1 = v_fma(x12, w2, s1); - s2 = v_fma(x22, w2, s2); - - s0 = v_fma(x10, w3, s0); - s1 = v_fma(x20, w3, s1); - s2 = v_fma(x30, w3, s2); - - s0 = v_fma(x11, w4, s0); - s1 = v_fma(x21, w4, s1); - s2 = v_fma(x31, w4, s2); - - s0 = v_fma(x12, w5, s0); - s1 = v_fma(x22, w5, s1); - s2 = v_fma(x32, w5, s2); - - s0 = v_fma(x20, w6, s0); - s1 = v_fma(x30, w6, s1); - s2 = v_fma(x40, w6, s2); - - s0 = v_fma(x21, w7, s0); - s1 = v_fma(x31, w7, s1); - s2 = v_fma(x41, w7, s2); - - s0 = v_fma(x22, w8, s0); - s1 = v_fma(x32, w8, s1); - s2 = v_fma(x42, w8, s2); - - if (ifMinMaxAct) - { - s0 = v_min(v_max(s0, vminval), vmaxval); - s1 = v_min(v_max(s1, vminval), vmaxval); - s2 = v_min(v_max(s2, vminval), vmaxval); - } - - v_store(outptr + x0, s0); - v_store(outptr + W0 + x0, s1); - v_store(outptr + W0 * 2 + x0, s2); - } + if (out_j <= pad_l || outW1 - VEC_NLANES < 0) + break; + out_j = outW1 - VEC_NLANES; } - else - { - for (; x0 <= x1 - FAST_VEC_NLANES; x0 += FAST_VEC_NLANES) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - v_float32x4 s0 = v_fma(v_load(inptr_xi + ofstab[0]), w0, vbias); - v_float32x4 s1 = v_load(inptr_xi + ofstab[1]) * w1; - v_float32x4 s2 = v_load(inptr_xi + ofstab[2]) * w2; + int in_j = out_j * stride_w - pad_l; + v_float32x4 v00 = v_load(imgptr0 + in_j), + v01 = v_load(imgptr0 + in_j + dilation_w), + v02 = v_load(imgptr0 + in_j + dilation_w*2), + v10 = v_load(imgptr1 + in_j), + v11 = v_load(imgptr1 + in_j + dilation_w), + v12 = v_load(imgptr1 + in_j + dilation_w*2), + v20 = v_load(imgptr2 + in_j), + v21 = v_load(imgptr2 + in_j + dilation_w), + v22 = v_load(imgptr2 + in_j + dilation_w*2); - s0 = v_fma(v_load(inptr_xi + ofstab[3]), w3, s0); - s1 = v_fma(v_load(inptr_xi + ofstab[4]), w4, s1); - s2 = v_fma(v_load(inptr_xi + ofstab[5]), w5, s2); - - s0 = v_fma(v_load(inptr_xi + ofstab[6]), w6, s0); - s1 = v_fma(v_load(inptr_xi + ofstab[7]), w7, s1); - s2 = v_fma(v_load(inptr_xi + ofstab[8]), w8, s2); - - s0 = s0 + s1 + s2; - if (ifMinMaxAct) - s0 = v_min(v_max(s0, vminval), vmaxval); - v_store(outptr + x0, s0); - } - } - } - else - { - for (; x0 <= x1 - FAST_VEC_NLANES; x0 += FAST_VEC_NLANES) - { - int xi_ = x0 * stride_x - pad_left, k = 0; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - v_float32x4 s0 = vbias; - for (; k <= ksize - 4; k += 4) - { - v_float32x4 v0 = v_load(inptr_xi + ofstab[k]); - v_float32x4 v1 = v_load(inptr_xi + ofstab[k + 1]); - v_float32x4 v2 = v_load(inptr_xi + ofstab[k + 2]); - v_float32x4 v3 = v_load(inptr_xi + ofstab[k + 3]); - - v_float32x4 ww0 = v_setall_f32(weights[k]); - v_float32x4 ww1 = v_setall_f32(weights[k+1]); - v_float32x4 ww2 = v_setall_f32(weights[k+2]); - v_float32x4 ww3 = v_setall_f32(weights[k+3]); - - s0 = v_fma(v0, ww0, s0); - s0 = v_fma(v1, ww1, s0); - s0 = v_fma(v2, ww2, s0); - s0 = v_fma(v3, ww3, s0); - } - for (; k < ksize; k++) - s0 = v_fma(v_load(inptr_xi + ofstab[k]), - v_setall_f32(weights[k]), s0); - if (ifMinMaxAct) - s0 = v_min(v_max(s0, vminval), vmaxval); - v_store(outptr + x0, s0); - } + v_float32x4 vout = v00*vw00 + v01*vw01 + v02*vw02 + + v10*vw10 + v11*vw11 + v12*vw12 + + v20*vw20 + v21*vw21 + v22*vw22 + vbias; + if (relu) + vout = v_select(vout > z, vout, vout*vrc); + v_store(outptr + out_j, vout); } } + else // (stride_w == 2 && dilation_w == 1) + { + for( ; out_j < outW1; out_j += VEC_NLANES ) + { + if (out_j + VEC_NLANES > outW1 && out_j > pad_l) + { + if (outW1 - VEC_NLANES < 0) + break; + out_j = outW1 - VEC_NLANES; + } + + int in_j = out_j * stride_w - pad_l; + + v_float32x4 v00, v01, v02, v10, v11, v12, v20, v21, v22, unused; + v_load_deinterleave(imgptr0 + in_j, v00, v01); + v_load_deinterleave(imgptr0 + in_j + 2, v02, unused); + v_load_deinterleave(imgptr1 + in_j, v10, v11); + v_load_deinterleave(imgptr1 + in_j + 2, v12, unused); + v_load_deinterleave(imgptr2 + in_j, v20, v21); + v_load_deinterleave(imgptr2 + in_j + 2, v22, unused); + + v_float32x4 vout = v00 * vw00 + v01 * vw01 + v02 * vw02 + + v10 * vw10 + v11 * vw11 + v12 * vw12 + + v20 * vw20 + v21 * vw21 + v22 * vw22 + vbias; + + if (relu) + vout = v_select(vout > z, vout, vout*vrc); + v_store(outptr + out_j, vout); + } + } + } #endif - if (dy0 == 3) - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + W0 * yi_ + xi_; - s_0 = s_1 = s_2 = biasval; - for (int k = 0; k < ksize; k++) - { - int inp_ofs = ofstab[k]; - float w = weights[k]; - s_0 += inptr_xi[inp_ofs] * w; - s_1 += inptr_xi[inp_ofs + Wi] * w; - s_2 += inptr_xi[inp_ofs + Wi * 2] * w; - } - if (ifMinMaxAct) - { - s_0 = std::min(std::max(s_0, minval), maxval); - s_1 = std::min(std::max(s_1, minval), maxval); - s_2 = std::min(std::max(s_2, minval), maxval); - } - outptr[x0] = s_0; - outptr[x0 + W0] = s_1; - outptr[x0 + W0 * 2] = s_2; - } - } - else - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - s_0 = biasval; - for (int k = 0; k < ksize; k++) - { - s_0 += inptr_xi[ofstab[k]] * weights[k]; - } + for (; out_j < outW1; out_j++) + { + int in_j = out_j * stride_w - pad_l; + out = imgptr0[in_j]*w00 + imgptr0[in_j + dilation_w]*w01 + imgptr0[in_j + dilation_w*2]*w02 + + imgptr1[in_j]*w10 + imgptr1[in_j + dilation_w]*w11 + imgptr1[in_j + dilation_w*2]*w12 + + imgptr2[in_j]*w20 + imgptr2[in_j + dilation_w]*w21 + imgptr2[in_j + dilation_w*2]*w22 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[out_j] = out; + } - if (ifMinMaxAct) - s_0 = std::min(std::max(s_0, minval), maxval); - outptr[x0] = s_0; - } + for (; out_j < outW; out_j++ ) + { + int in_j0 = out_j * stride_w - pad_l, in_j1 = in_j0 + dilation_w, in_j2 = in_j0 + dilation_w*2; + float s0 = 1.f, s1 = 1.f, s2 = 1.f; + if (in_j0 >= width) + { + in_j0 = 0; + s0 = 0.f; } - x1 = W0; + if (in_j1 >= width) + { + in_j1 = 0; + s1 = 0.f; + } + if (in_j2 >= width) + { + in_j2 = 0; + s2 = 0.f; + } + out = imgptr0[in_j0]*w00*s0 + imgptr0[in_j1]*w01*s1 + imgptr0[in_j2]*w02*s2 + + imgptr1[in_j0]*w10*s0 + imgptr1[in_j1]*w11*s1 + imgptr1[in_j2]*w12*s2 + + imgptr2[in_j0]*w20*s0 + imgptr2[in_j1]*w21*s1 + imgptr2[in_j2]*w22*s2 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[out_j] = out; } } } -void runDepthwise(InputArray _input, OutputArray _output, const Ptr& conv, float minval, float maxval, ActivationLayer* activ, bool ifMinMaxAct) { +static void depthWiseBlockConv1D(const float* wptr, + int kernel_w, int stride_w, int dilation_w, int pad_l, + const float* biasptr, const float* relu, + const float* inptr_, int width, + float* outptr_, + int out_d, int outW) +{ + const float w00_ = wptr[0], w01_ = wptr[1], w02_ = wptr[2]; + int outW1 = min(outW, (width - dilation_w * (kernel_w - 1) + pad_l)/stride_w); + float relu_coeff = relu ? relu[out_d] : 1.f, bias = biasptr[out_d]; + + int out_j = 0; + const float* imgptr0 = inptr_; + float out, w00 = w00_, w01 = w01_, w02 = w02_; + float* outptr = outptr_; + + if (pad_l > 0) + { + out = imgptr0[0]*w01 + imgptr0[dilation_w]*w02 + bias; + + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[0] = out; + out_j = 1; + } + +#if CV_SIMD128 + const int VEC_NLANES = 4; + v_float32x4 vw00 = v_setall_f32(w00); + v_float32x4 vw01 = v_setall_f32(w01); + v_float32x4 vw02 = v_setall_f32(w02); + v_float32x4 z = v_setzero_f32(); + v_float32x4 vbias = v_setall_f32(bias); + v_float32x4 vrc = v_setall_f32(relu_coeff); + + if (stride_w == 1 || (stride_w == 2 && dilation_w == 1)) + { + if( stride_w == 1 ) + { + for( ; out_j < outW1; out_j += VEC_NLANES ) + { + if (out_j + VEC_NLANES > outW1) + { + if (out_j <= pad_l || outW1 - VEC_NLANES < 0) + break; + out_j = outW1 - VEC_NLANES; + } + int in_j = out_j * stride_w - pad_l; + v_float32x4 v00 = v_load(imgptr0 + in_j), + v01 = v_load(imgptr0 + in_j + dilation_w), + v02 = v_load(imgptr0 + in_j + dilation_w*2); + + v_float32x4 vout = v00*vw00 + v01*vw01 + v02*vw02 + vbias; + if (relu) + vout = v_select(vout > z, vout, vout*vrc); + v_store(outptr + out_j, vout); + } + } + else // (stride_w == 2 && dilation_w == 1) + { + for( ; out_j < outW1; out_j += VEC_NLANES ) + { + if (out_j + VEC_NLANES > outW1) + { + if (out_j <= pad_l || outW1 - VEC_NLANES < 0) + break; + out_j = outW1 - VEC_NLANES; + } + int in_j = out_j * stride_w - pad_l; + + v_float32x4 v00, v01, v02, unused; + v_load_deinterleave(imgptr0 + in_j, v00, v01); + v_load_deinterleave(imgptr0 + in_j + 2, v02, unused); + + v_float32x4 vout = v00 * vw00 + v01 * vw01 + v02 * vw02 + vbias; + + if (relu) + vout = v_select(vout > z, vout, vout*vrc); + v_store(outptr + out_j, vout); + } + } + } +#endif + + for (; out_j < outW1; out_j++) + { + int in_j = out_j * stride_w - pad_l; + out = imgptr0[in_j]*w00 + imgptr0[in_j + dilation_w]*w01 + imgptr0[in_j + dilation_w*2]*w02 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[out_j] = out; + } + + for (; out_j < outW; out_j++ ) + { + int in_j0 = out_j * stride_w - pad_l, in_j1 = in_j0 + dilation_w, in_j2 = in_j0 + dilation_w*2; + float s0 = 1.f, s1 = 1.f, s2 = 1.f; + if (in_j0 >= width) + { + in_j0 = 0; + s0 = 0.f; + } + if (in_j1 >= width) + { + in_j1 = 0; + s1 = 0.f; + } + if (in_j2 >= width) + { + in_j2 = 0; + s2 = 0.f; + } + out = imgptr0[in_j0]*w00*s0 + imgptr0[in_j1]*w01*s1 + imgptr0[in_j2]*w02*s2 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[out_j] = out; + } +} + +void runDepthwise(InputArray _input, OutputArray _output, const Ptr& conv, ActivationLayer* activ_, + const std::vector& reluslope) +{ Mat input = _input.getMat(); Mat output = _output.getMat(); MatShape inputShape = shape(input); MatShape outputShape = shape(output); - CV_Assert(inputShape.size() == 4 && outputShape.size() == 4); - int N = inputShape[0], C = inputShape[1], Hi = inputShape[2], Wi = inputShape[3]; // [N, C, H, W] + CV_Assert(inputShape.size() == 3 || inputShape.size() == 4); + CV_Assert(inputShape.size() == outputShape.size()); + + int conv_dim = conv->conv_dim; + CV_Assert((conv_dim == CONV_2D || conv_dim == CONV_1D) && + "DNN: Currently we do not support depth-wise for Convolution 3D!"); + + ActivationLayer* activ = reluslope.empty() ? activ_ : nullptr; + int N = inputShape[0], C = inputShape[1]; + + int Hi = conv_dim == CONV_1D ? 1 : inputShape[inputShape.size() - 2]; + int Wi = inputShape[inputShape.size() - 1]; + int K = conv->K, Hk = conv->Hk, Wk = conv->Wk; - int H0 = outputShape[2], W0 = outputShape[3], ngroups = conv->ngroups; + + int H0 = conv_dim == CONV_1D ? 1 : outputShape[outputShape.size() - 2]; + int W0 = outputShape[outputShape.size() - 1]; + int ngroups = conv->ngroups; const size_t inp_planesize = (size_t) Hi * Wi; const size_t out_planesize = (size_t) H0 * W0; CV_Assert(ngroups > 1 && ngroups == K && ngroups == C); - int stride_y = conv->stride_y, stride_x = conv->stride_x; - int dilation_y = conv->dilation_y, dilation_x = conv->dilation_x; + int stride_h = conv->stride_h, stride_w = conv->stride_w; + int dilation_h = conv->dilation_h, dilation_w = conv->dilation_w; int pad_top = conv->pad_top, pad_bottom = conv->pad_bottom; int pad_left = conv->pad_left, pad_right = conv->pad_right; - int ksize = Hk * Wk, padded_ksize = ((ksize + FAST_VEC_NLANES - 1) / FAST_VEC_NLANES) * FAST_VEC_NLANES; + int ksize = Hk * Wk; + + const int VEC_NLANES = 32; + int padded_ksize = ((ksize + VEC_NLANES-1) / VEC_NLANES) * VEC_NLANES; const float *inp = input.ptr(); float *out = output.ptr(); - std::vector ofstab_(3 * padded_ksize, 0); +#if CV_TRY_AVX2 || CV_TRY_AVX || CV_TRY_RVV + // TODO: remove the following limitation, need change code in layers_common.simd.hpp. + bool canRunOpt = Wi >= 16 + dilation_w*(Wk - 1); +#endif + std::vector ofstab_(3 * ksize, 0); int *ofstab = ofstab_.data(); - int *yxtab = ofstab + padded_ksize; + int *yxtab = ofstab + ksize; - for (int k = 0; k < padded_ksize; k++) + for (int k = 0; k < ksize; k++) { int y = k < ksize ? k / Wk : 0; int x = k < ksize ? k % Wk : 0; - int dy = y * dilation_y, dx = x * dilation_x; + int dy = y * dilation_h, dx = x * dilation_w; yxtab[k * 2] = dy; yxtab[k * 2 + 1] = dx; ofstab[k] = dy * Wi + dx; } - const float *weights0 = conv->weightsBuf.data(), *bias = conv->biasBuf.data(); - int inner_ytop = (pad_bottom + stride_y - 1) / stride_y, inner_ybottom = 3; - int inner_xleft = (pad_left + stride_x - 1) / stride_x, inner_xright = 4; - + const float *weights0 = conv->weightsBufPtr, *bias = conv->biasBuf.data(); + const float* relu = reluslope.data(); CV_Assert(ksize > 1 || (pad_left == 0 && pad_right == 0 && pad_top == 0 && pad_bottom == 0)); - inner_xright = (Wi - (Wk - 1) * dilation_x + pad_left) / stride_x; - inner_xright += inner_xright * stride_x - pad_left + (Wk - 1) * dilation_x < Wi; - inner_ybottom = (Hi - (Hk - 1) * dilation_y + pad_top) / stride_y; - inner_ybottom += inner_ybottom * stride_y - pad_top + (Hk - 1) * dilation_y < Hi; - - if (inner_xleft >= inner_xright || inner_ytop >= inner_ybottom) - { - inner_xleft = W0; - inner_ytop = H0; - } - - inner_ybottom = inner_ybottom < H0 ? inner_ybottom : H0; - - bool useSIMD = stride_x == 1 && inner_xleft < W0; - bool is3x3 = Hk == 3 && Wk == 3; - parallel_for_(Range(0, N * C), [&](const Range &r0) { - for (int nc = r0.start; nc < r0.end; nc++) + for (int nc = r0.start; nc < r0.end; nc++) + { + int c = nc % C; + const float *inptr0 = inp + inp_planesize * nc; + float *outptr0 = out + out_planesize * nc; + + const float *weights = weights0 + c * padded_ksize; + + if (conv_dim == CONV_2D) { - int c = nc % C; - const float *inptr = inp + inp_planesize * nc; - float *outptr0 = out + out_planesize * nc; - - float biasval = bias[c]; - const float *weights = weights0 + c * padded_ksize; - #if CV_TRY_AVX2 - if (conv->useAVX2) - opt_AVX2::depthWiseBlock_AVX2(inptr, outptr0, weights, biasval, ofstab, yxtab, minval, maxval, Hi, Wi, H0, W0, ksize, - pad_top, pad_left, dilation_y, stride_x, stride_y, inner_xleft, inner_xright, inner_ytop, - inner_ybottom, ifMinMaxAct, useSIMD, is3x3); + if(canRunOpt && conv->useAVX2) + opt_AVX2::fastDepthwiseConv(weights, Hk, Wk, stride_h, stride_w, dilation_h, dilation_w, + pad_top, pad_left, bias, relu, inptr0, Hi, Wi, outptr0, c, H0, W0); else #endif - depthWiseBlock(inptr, outptr0, weights, biasval, ofstab, yxtab, minval, maxval, Hi, Wi, H0, W0, ksize, - pad_top, pad_left, dilation_y, stride_x, stride_y, inner_xleft, inner_xright, inner_ytop, - inner_ybottom, ifMinMaxAct, useSIMD, is3x3); - - if (activ) - activ->forwardSlice(outptr0, outptr0, (int) out_planesize, out_planesize, c, c+1); +#if CV_TRY_AVX + if(canRunOpt && conv->useAVX) + opt_AVX::fastDepthwiseConv(weights, Hk, Wk, stride_h, stride_w, dilation_h, dilation_w, + pad_top, pad_left, bias, relu, inptr0, Hi, Wi, outptr0, c, H0, W0); + else +#endif +#if CV_TRY_RVV + if(canRunOpt && conv->useRVV) + opt_RVV::fastDepthwiseConv(weights, Hk, Wk, stride_h, stride_w, dilation_h, dilation_w, + pad_top, pad_left, bias, relu, inptr0, Hi, Wi, outptr0, c, H0, W0); + else +#endif + depthWiseBlockConv2D(weights, Hk, Wk, stride_h, stride_w, dilation_h, dilation_w, + pad_top, pad_left, bias, relu, inptr0, Hi, Wi, outptr0, c, H0, W0); } - }); + else // conv_dim == CONV_1D, spatial branch for depth-wise Conv1D. + { + depthWiseBlockConv1D(weights, Wk, stride_w, dilation_w, pad_left, bias, relu, inptr0, Wi, outptr0, c, W0); + } + + if (activ) + activ->forwardSlice(outptr0, outptr0, (int) out_planesize, out_planesize, c, c+1); + }}); } -}} // namespace cv::dnn \ No newline at end of file +}} // namespace cv::dnn diff --git a/modules/dnn/src/layers/fast_convolution/fast_convolution.avx2.cpp b/modules/dnn/src/layers/fast_convolution/fast_convolution.avx2.cpp index 22580c580c..0d3c144762 100644 --- a/modules/dnn/src/layers/fast_convolution/fast_convolution.avx2.cpp +++ b/modules/dnn/src/layers/fast_convolution/fast_convolution.avx2.cpp @@ -6,356 +6,494 @@ #include "fast_convolution.hpp" namespace cv { +namespace dnn { namespace opt_AVX2 { #if CV_TRY_AVX2 -void convBlock_AVX2(int k, const float *a, const float *b, - float *c, int ldc, const float *bias, - float minval, float maxval, bool ifActiv) +void convBlockMR1(int np, const float* a, const float* b, float *c, const float bias, bool init_c, + const float minval, const float maxval, bool ifMinMaxAct) { -#if FAST_CONV_MR == 4 && FAST_CONV_NR == 24 - __m256 vminval = _mm256_set1_ps(minval), vmaxval = _mm256_set1_ps(maxval); - __m256 c0 = _mm256_set1_ps(bias[0]), c1 = c0, c2 = c0; - __m256 c3 = _mm256_set1_ps(bias[1]), c4 = c3, c5 = c3; - __m256 c6 = _mm256_set1_ps(bias[2]), c7 = c6, c8 = c6; - __m256 c9 = _mm256_set1_ps(bias[3]), c10 = c9, c11 = c9; +#if CONV_NR == 24 + __m256 c0 = _mm256_set1_ps(bias), c1 = c0, c2 = c0; - __m256 a0 = _mm256_setzero_ps(), a1 = _mm256_setzero_ps(); - __m256 b0 = _mm256_setzero_ps(), b1 = _mm256_setzero_ps(), b2 = _mm256_setzero_ps(); - - for (int p = 0; p < k; p++, a += FAST_CONV_MR, b += FAST_CONV_NR) + for (int p = 0; p < np; p++, a++, b += CONV_NR) { - a0 = _mm256_set1_ps(a[0]), a1 = _mm256_set1_ps(a[1]); - b0 = _mm256_load_ps(b), b1 = _mm256_load_ps(b + 8), b2 = _mm256_load_ps(b + 16); + __m256 a0 = _mm256_set1_ps(a[0]); + __m256 b0 = _mm256_loadu_ps(b), b1 = _mm256_loadu_ps(b + 8), b2 = _mm256_loadu_ps(b + 16); c0 = _mm256_fmadd_ps(b0, a0, c0); c1 = _mm256_fmadd_ps(b1, a0, c1); c2 = _mm256_fmadd_ps(b2, a0, c2); - - c3 = _mm256_fmadd_ps(b0, a1, c3); - a0 = _mm256_set1_ps(a[2]); - c4 = _mm256_fmadd_ps(b1, a1, c4); - c5 = _mm256_fmadd_ps(b2, a1, c5); - - c6 = _mm256_fmadd_ps(b0, a0, c6); - a1 = _mm256_set1_ps(a[3]); - c7 = _mm256_fmadd_ps(b1, a0, c7); - c8 = _mm256_fmadd_ps(b2, a0, c8); - - c9 = _mm256_fmadd_ps(b0, a1, c9); - c10 = _mm256_fmadd_ps(b1, a1, c10); - c11 = _mm256_fmadd_ps(b2, a1, c11); } - if (ifActiv) + if (init_c) { - c0 = _mm256_min_ps(_mm256_max_ps(c0, vminval), vmaxval); - c1 = _mm256_min_ps(_mm256_max_ps(c1, vminval), vmaxval); - c2 = _mm256_min_ps(_mm256_max_ps(c2, vminval), vmaxval); - c3 = _mm256_min_ps(_mm256_max_ps(c3, vminval), vmaxval); - c4 = _mm256_min_ps(_mm256_max_ps(c4, vminval), vmaxval); - c5 = _mm256_min_ps(_mm256_max_ps(c5, vminval), vmaxval); - c6 = _mm256_min_ps(_mm256_max_ps(c6, vminval), vmaxval); - c7 = _mm256_min_ps(_mm256_max_ps(c7, vminval), vmaxval); - c8 = _mm256_min_ps(_mm256_max_ps(c8, vminval), vmaxval); - c9 = _mm256_min_ps(_mm256_max_ps(c9, vminval), vmaxval); - c10 = _mm256_min_ps(_mm256_max_ps(c10, vminval), vmaxval); - c11 = _mm256_min_ps(_mm256_max_ps(c11, vminval), vmaxval); + c0 = _mm256_add_ps(_mm256_loadu_ps(c), c0); + c1 = _mm256_add_ps(_mm256_loadu_ps(c + 8), c1); + c2 = _mm256_add_ps(_mm256_loadu_ps(c + 16), c2); } - _mm256_storeu_ps(c, c0); _mm256_storeu_ps(c+8, c1); _mm256_storeu_ps(c+16, c2); - _mm256_storeu_ps(c + ldc, c3); _mm256_storeu_ps(c + ldc + 8, c4); _mm256_storeu_ps(c + ldc + 16, c5); - _mm256_storeu_ps(c + ldc*2, c6); _mm256_storeu_ps(c + ldc*2 + 8, c7); _mm256_storeu_ps(c + ldc*2 + 16, c8); - _mm256_storeu_ps(c + ldc*3, c9); _mm256_storeu_ps(c + ldc*3 + 8, c10); _mm256_storeu_ps(c + ldc*3 + 16, c11); + if (ifMinMaxAct) + { + __m256 vmax = _mm256_set1_ps(maxval); + __m256 vmin = _mm256_set1_ps(minval); + + c0 = _mm256_min_ps(_mm256_max_ps(c0, vmin), vmax); + c1 = _mm256_min_ps(_mm256_max_ps(c1, vmin), vmax); + c2 = _mm256_min_ps(_mm256_max_ps(c2, vmin), vmax); + } + + _mm256_storeu_ps(c, c0); + _mm256_storeu_ps(c + 8, c1); + _mm256_storeu_ps(c + 16, c2); _mm256_zeroupper(); #else -#error "unsupported FAST_CONV_MR and/or FAST_CONV_NR in convBlock_AVX2." +#error "unsupported CONV_NR in convBlockMR1." #endif } -void depthWiseBlock_AVX2(const float *inptr, float *outptr, const float *weights, float biasval, int *ofstab, int *yxtab, - float minval, float maxval, int Hi, int Wi, int H0, int W0, int ksize, int pad_top, int pad_left, - int dilation_y, int stride_x, int stride_y, int inner_xleft, int inner_xright, int inner_ytop, - int inner_ybottom, bool ifMinMaxAct, bool useSIMD, bool is3x3) +void convBlock_AVX2(int np, const float* a, const float* b, float* c, int ldc, bool init_c) { - const int VECSZ = 8; - __m256 vminval = _mm256_set1_ps(minval); - __m256 vmaxval = _mm256_set1_ps(maxval); +#if CONV_MR == 4 && CONV_NR == 24 + __m256 c00 = _mm256_set1_ps(0.f), c01 = c00, c02 = c00; + __m256 c10 = c00, c11 = c00, c12 = c00; + __m256 c20 = c00, c21 = c00, c22 = c00; + __m256 c30 = c00, c31 = c00, c32 = c00; - __m256 w0 = _mm256_setzero_ps(), - w1 = w0, w2 = w0, w3 = w0, w4 = w0, w5 = w0, w6 = w0, w7 = w0, w8 = w0, vbias = w0; + __m256 a0 = _mm256_setzero_ps(), a1 = _mm256_setzero_ps(); + __m256 b0 = _mm256_setzero_ps(), b1 = _mm256_setzero_ps(), b2 = _mm256_setzero_ps(); - if (useSIMD) + for (int p = 0; p < np; p++, a += CONV_MR, b += CONV_NR) { - vbias = _mm256_set1_ps(biasval); - if (is3x3) - { - w0 = _mm256_set1_ps(weights[0]); - w1 = _mm256_set1_ps(weights[1]); - w2 = _mm256_set1_ps(weights[2]); - w3 = _mm256_set1_ps(weights[3]); - w4 = _mm256_set1_ps(weights[4]); - w5 = _mm256_set1_ps(weights[5]); - w6 = _mm256_set1_ps(weights[6]); - w7 = _mm256_set1_ps(weights[7]); - w8 = _mm256_set1_ps(weights[8]); - } + a0 = _mm256_set1_ps(a[0]), a1 = _mm256_set1_ps(a[1]); + b0 = _mm256_load_ps(b), b1 = _mm256_load_ps(b + 8), b2 = _mm256_load_ps(b + 16); + + c00 = _mm256_fmadd_ps(b0, a0, c00); + c01 = _mm256_fmadd_ps(b1, a0, c01); + c02 = _mm256_fmadd_ps(b2, a0, c02); + + c10 = _mm256_fmadd_ps(b0, a1, c10); + c11 = _mm256_fmadd_ps(b1, a1, c11); + c12 = _mm256_fmadd_ps(b2, a1, c12); + + a0 = _mm256_set1_ps(a[2]), a1 = _mm256_set1_ps(a[3]); + + c20 = _mm256_fmadd_ps(b0, a0, c20); + c21 = _mm256_fmadd_ps(b1, a0, c21); + c22 = _mm256_fmadd_ps(b2, a0, c22); + + c30 = _mm256_fmadd_ps(b0, a1, c30); + c31 = _mm256_fmadd_ps(b1, a1, c31); + c32 = _mm256_fmadd_ps(b2, a1, c32); } - int dy0 = 1; - for (int y0 = 0; y0 < H0; y0 += dy0, outptr += W0 * dy0) + if (!init_c) { - dy0 = inner_ytop <= y0 && y0 + 3 < inner_ybottom && is3x3 && stride_y == 1 && dilation_y == 1 - ? 3 : 1; + c00 = _mm256_add_ps(c00, _mm256_load_ps(c)); + c01 = _mm256_add_ps(c01, _mm256_load_ps(c + 8)); + c02 = _mm256_add_ps(c02, _mm256_load_ps(c + 16)); - int x0 = 0, x1 = y0 >= inner_ytop && y0 < inner_ybottom ? inner_xleft : W0; - int yi_ = y0 * stride_y - pad_top; + c10 = _mm256_add_ps(c10, _mm256_load_ps(c + ldc)); + c11 = _mm256_add_ps(c11, _mm256_load_ps(c + ldc + 8)); + c12 = _mm256_add_ps(c12, _mm256_load_ps(c + ldc + 16)); - for (;;) - { - float s_0, s_1, s_2; - if (dy0 == 3) - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - s_0 = s_1 = s_2 = biasval; - for (int k = 0; k < ksize; k++) - { - int dy = yxtab[k * 2]; - int yi = yi_ + dy; - int xi = xi_ + yxtab[k * 2 + 1]; - float w = weights[k]; + c20 = _mm256_add_ps(c20, _mm256_load_ps(c + ldc*2)); + c21 = _mm256_add_ps(c21, _mm256_load_ps(c + ldc*2 + 8)); + c22 = _mm256_add_ps(c22, _mm256_load_ps(c + ldc*2 + 16)); - if ((unsigned) xi < (unsigned) Wi) - { - s_0 += inptr[yi * Wi + xi] * w; - s_1 += inptr[(yi + 1) * Wi + xi] * w; - s_2 += inptr[(yi + 2) * Wi + xi] * w; - } - } - if (ifMinMaxAct) - { - s_0 = std::min(std::max(s_0, minval), maxval); - s_1 = std::min(std::max(s_1, minval), maxval); - s_2 = std::min(std::max(s_2, minval), maxval); - } - - outptr[x0] = s_0; - outptr[x0 + W0] = s_1; - outptr[x0 + W0 * 2] = s_2; - } - } - else - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - s_0 = biasval; - for (int k = 0; k < ksize; k++) { - int dy = yxtab[k * 2]; - int yi = yi_ + dy; - int xi = xi_ + yxtab[k * 2 + 1]; - float w = weights[k]; - if (((unsigned) yi < (unsigned) Hi) & ((unsigned) xi < (unsigned) Wi)) - s_0 += inptr[yi * Wi + xi] * w; - } - if (ifMinMaxAct) - s_0 = std::min(std::max(s_0, minval), maxval); - outptr[x0] = s_0; - } - } - if (x0 == W0) - break; - x1 = inner_xright; - - if (useSIMD) - { - if (is3x3) - { - if (dy0 == 3) - { - for (; x0 <= x1 - VECSZ; x0 += VECSZ) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - - __m256 s0, s1, s2; - __m256 x00 = _mm256_loadu_ps(inptr_xi); - __m256 x01 = _mm256_loadu_ps(inptr_xi + 1); - __m256 x02 = _mm256_loadu_ps(inptr_xi + 2); - - __m256 x10 = _mm256_loadu_ps(inptr_xi + Wi); - __m256 x11 = _mm256_loadu_ps(inptr_xi + Wi + 1); - __m256 x12 = _mm256_loadu_ps(inptr_xi + Wi + 2); - - __m256 x20 = _mm256_loadu_ps(inptr_xi + Wi * 2); - __m256 x21 = _mm256_loadu_ps(inptr_xi + Wi * 2 + 1); - __m256 x22 = _mm256_loadu_ps(inptr_xi + Wi * 2 + 2); - - __m256 x30 = _mm256_loadu_ps(inptr_xi + Wi * 3); - __m256 x31 = _mm256_loadu_ps(inptr_xi + Wi * 3 + 1); - __m256 x32 = _mm256_loadu_ps(inptr_xi + Wi * 3 + 2); - - __m256 x40 = _mm256_loadu_ps(inptr_xi + Wi * 4); - __m256 x41 = _mm256_loadu_ps(inptr_xi + Wi * 4 + 1); - __m256 x42 = _mm256_loadu_ps(inptr_xi + Wi * 4 + 2); - - s0 = _mm256_fmadd_ps(x00, w0, vbias); - s1 = _mm256_fmadd_ps(x10, w0, vbias); - s2 = _mm256_fmadd_ps(x20, w0, vbias); - - s0 = _mm256_fmadd_ps(x01, w1, s0); - s1 = _mm256_fmadd_ps(x11, w1, s1); - s2 = _mm256_fmadd_ps(x21, w1, s2); - - s0 = _mm256_fmadd_ps(x02, w2, s0); - s1 = _mm256_fmadd_ps(x12, w2, s1); - s2 = _mm256_fmadd_ps(x22, w2, s2); - - s0 = _mm256_fmadd_ps(x10, w3, s0); - s1 = _mm256_fmadd_ps(x20, w3, s1); - s2 = _mm256_fmadd_ps(x30, w3, s2); - - s0 = _mm256_fmadd_ps(x11, w4, s0); - s1 = _mm256_fmadd_ps(x21, w4, s1); - s2 = _mm256_fmadd_ps(x31, w4, s2); - - s0 = _mm256_fmadd_ps(x12, w5, s0); - s1 = _mm256_fmadd_ps(x22, w5, s1); - s2 = _mm256_fmadd_ps(x32, w5, s2); - - s0 = _mm256_fmadd_ps(x20, w6, s0); - s1 = _mm256_fmadd_ps(x30, w6, s1); - s2 = _mm256_fmadd_ps(x40, w6, s2); - - s0 = _mm256_fmadd_ps(x21, w7, s0); - s1 = _mm256_fmadd_ps(x31, w7, s1); - s2 = _mm256_fmadd_ps(x41, w7, s2); - - s0 = _mm256_fmadd_ps(x22, w8, s0); - s1 = _mm256_fmadd_ps(x32, w8, s1); - s2 = _mm256_fmadd_ps(x42, w8, s2); - - if (ifMinMaxAct) - { - s0 = _mm256_min_ps(_mm256_max_ps(s0, vminval), vmaxval); - s1 = _mm256_min_ps(_mm256_max_ps(s1, vminval), vmaxval); - s2 = _mm256_min_ps(_mm256_max_ps(s2, vminval), vmaxval); - } - - _mm256_storeu_ps(outptr + x0, s0); - _mm256_storeu_ps(outptr + W0 + x0, s1); - _mm256_storeu_ps(outptr + W0 * 2 + x0, s2); - } - } - else - { - for (; x0 <= x1 - VECSZ; x0 += VECSZ) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - __m256 s0 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[0]), w0, vbias); - __m256 s1 = _mm256_mul_ps(_mm256_loadu_ps(inptr_xi + ofstab[1]), w1); - __m256 s2 = _mm256_mul_ps(_mm256_loadu_ps(inptr_xi + ofstab[2]), w2); - - s0 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[3]), w3, s0); - s1 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[4]), w4, s1); - s2 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[5]), w5, s2); - - s0 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[6]), w6, s0); - s1 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[7]), w7, s1); - s2 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[8]), w8, s2); - - s0 = _mm256_add_ps(_mm256_add_ps(s0, s1), s2); - - if (ifMinMaxAct) - s0 = _mm256_min_ps(_mm256_max_ps(s0, vminval), vmaxval); - _mm256_storeu_ps(outptr + x0, s0); - } - } - } - else - { - for (; x0 <= x1 - VECSZ; x0 += VECSZ) - { - int xi_ = x0 * stride_x - pad_left, k = 0; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - __m256 s0 = vbias; - for (; k <= ksize - 4; k += 4) - { - __m256 v0 = _mm256_loadu_ps(inptr_xi + ofstab[k]); - __m256 v1 = _mm256_loadu_ps(inptr_xi + ofstab[k + 1]); - __m256 v2 = _mm256_loadu_ps(inptr_xi + ofstab[k + 2]); - __m256 v3 = _mm256_loadu_ps(inptr_xi + ofstab[k + 3]); - - __m256 ww0 = _mm256_set1_ps(weights[k]); - __m256 ww1 = _mm256_set1_ps(weights[k+1]); - __m256 ww2 = _mm256_set1_ps(weights[k+2]); - __m256 ww3 = _mm256_set1_ps(weights[k+3]); - - s0 = _mm256_fmadd_ps(v0, ww0, s0); - s0 = _mm256_fmadd_ps(v1, ww1, s0); - s0 = _mm256_fmadd_ps(v2, ww2, s0); - s0 = _mm256_fmadd_ps(v3, ww3, s0); - } - for (; k < ksize; k++) - s0 = _mm256_fmadd_ps(_mm256_loadu_ps(inptr_xi + ofstab[k]), - _mm256_set1_ps(weights[k]), s0); - - if (ifMinMaxAct) - s0 = _mm256_min_ps(_mm256_max_ps(s0, vminval), vmaxval); - _mm256_storeu_ps(outptr + x0, s0); - } - } - } - - if (dy0 == 3) - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + W0 * yi_ + xi_; - s_0 = s_1 = s_2 = biasval; - for (int k = 0; k < ksize; k++) { - int inp_ofs = ofstab[k]; - float w = weights[k]; - s_0 += inptr_xi[inp_ofs] * w; - s_1 += inptr_xi[inp_ofs + Wi] * w; - s_2 += inptr_xi[inp_ofs + Wi * 2] * w; - } - if (ifMinMaxAct) - { - s_0 = std::min(std::max(s_0, minval), maxval); - s_1 = std::min(std::max(s_1, minval), maxval); - s_2 = std::min(std::max(s_2, minval), maxval); - } - - outptr[x0] = s_0; - outptr[x0 + W0] = s_1; - outptr[x0 + W0 * 2] = s_2; - } - } - else - { - for (; x0 < x1; x0++) - { - int xi_ = x0 * stride_x - pad_left; - const float *inptr_xi = inptr + Wi * yi_ + xi_; - s_0 = biasval; - for (int k = 0; k < ksize; k++) - { - s_0 += inptr_xi[ofstab[k]] * weights[k]; - } - if (ifMinMaxAct) - s_0 = std::min(std::max(s_0, minval), maxval); - outptr[x0] = s_0; - } - } - x1 = W0; - } + c30 = _mm256_add_ps(c30, _mm256_load_ps(c + ldc*3)); + c31 = _mm256_add_ps(c31, _mm256_load_ps(c + ldc*3 + 8)); + c32 = _mm256_add_ps(c32, _mm256_load_ps(c + ldc*3 + 16)); } + + _mm256_storeu_ps(c, c00), _mm256_storeu_ps(c+8, c01), _mm256_storeu_ps(c+16, c02); + _mm256_storeu_ps(c + ldc, c10), _mm256_storeu_ps(c + ldc + 8, c11), _mm256_storeu_ps(c + ldc + 16, c12); + _mm256_storeu_ps(c + ldc*2, c20), _mm256_storeu_ps(c + ldc*2 + 8, c21), _mm256_storeu_ps(c + ldc*2 + 16, c22); + _mm256_storeu_ps(c + ldc*3, c30), _mm256_storeu_ps(c + ldc*3 + 8, c31), _mm256_storeu_ps(c + ldc*3 + 16, c32); + _mm256_zeroupper(); +#else +#error "unsupported CONV_MR and/or CONV_NR in convBlock_AVX2." +#endif } + +void _fx_winograd_accum_f32(const float* inwptr, const float* wptr, + float* outbuf, int Cg, int iblock) +{ + CV_Assert(_FX_WINO_IBLOCK == 6 && _FX_WINO_KBLOCK == 4);// && _FX_WINO_ATOM_F32 == 8); + if (iblock > 3) + { + for (int atom_id = 0; atom_id < _FX_WINO_NATOMS_F32; atom_id++, + outbuf += _FX_WINO_ATOM_F32) + { + __m256 s00 = _mm256_set1_ps(0.f), s01 = s00, s02 = s00, s03 = s00, s04 = s00, s05 = s00; + __m256 s10 = _mm256_set1_ps(0.f), s11 = s00, s12 = s00, s13 = s00, s14 = s00, s15 = s00; + __m256 s20 = _mm256_set1_ps(0.f), s21 = s00, s22 = s00, s23 = s00, s24 = s00, s25 = s00; + __m256 s30 = _mm256_set1_ps(0.f), s31 = s00, s32 = s00, s33 = s00, s34 = s00, s35 = s00; + for (int c = 0; c < Cg; c++, inwptr += _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32, + wptr += _FX_WINO_KBLOCK*_FX_WINO_ATOM_F32) + { + __m256 w0 = _mm256_load_ps(wptr), w1 = _mm256_load_ps(wptr + 8); + __m256 w2 = _mm256_load_ps(wptr + 16), w3 = _mm256_load_ps(wptr + 24); + __m256 x0, x1; + x0 = _mm256_load_ps(inwptr); + x1 = _mm256_load_ps(inwptr + 8); + s00 = _mm256_fmadd_ps(w0, x0, s00); + s01 = _mm256_fmadd_ps(w0, x1, s01); + s10 = _mm256_fmadd_ps(w1, x0, s10); + s11 = _mm256_fmadd_ps(w1, x1, s11); + s20 = _mm256_fmadd_ps(w2, x0, s20); + s21 = _mm256_fmadd_ps(w2, x1, s21); + s30 = _mm256_fmadd_ps(w3, x0, s30); + s31 = _mm256_fmadd_ps(w3, x1, s31); + x0 = _mm256_load_ps(inwptr + 16); + x1 = _mm256_load_ps(inwptr + 24); + s02 = _mm256_fmadd_ps(w0, x0, s02); + s03 = _mm256_fmadd_ps(w0, x1, s03); + s12 = _mm256_fmadd_ps(w1, x0, s12); + s13 = _mm256_fmadd_ps(w1, x1, s13); + s22 = _mm256_fmadd_ps(w2, x0, s22); + s23 = _mm256_fmadd_ps(w2, x1, s23); + s32 = _mm256_fmadd_ps(w3, x0, s32); + s33 = _mm256_fmadd_ps(w3, x1, s33); + x0 = _mm256_load_ps(inwptr + 32); + x1 = _mm256_load_ps(inwptr + 40); + s04 = _mm256_fmadd_ps(w0, x0, s04); + s05 = _mm256_fmadd_ps(w0, x1, s05); + s14 = _mm256_fmadd_ps(w1, x0, s14); + s15 = _mm256_fmadd_ps(w1, x1, s15); + s24 = _mm256_fmadd_ps(w2, x0, s24); + s25 = _mm256_fmadd_ps(w2, x1, s25); + s34 = _mm256_fmadd_ps(w3, x0, s34); + s35 = _mm256_fmadd_ps(w3, x1, s35); + } + + _mm256_store_ps(outbuf, s00); + _mm256_store_ps(outbuf + 1*64, s01); + _mm256_store_ps(outbuf + 2*64, s02); + _mm256_store_ps(outbuf + 3*64, s03); + _mm256_store_ps(outbuf + 4*64, s04); + _mm256_store_ps(outbuf + 5*64, s05); + + _mm256_store_ps(outbuf + 6*64, s10); + _mm256_store_ps(outbuf + 7*64, s11); + _mm256_store_ps(outbuf + 8*64, s12); + _mm256_store_ps(outbuf + 9*64, s13); + _mm256_store_ps(outbuf + 10*64, s14); + _mm256_store_ps(outbuf + 11*64, s15); + + _mm256_store_ps(outbuf + 12*64, s20); + _mm256_store_ps(outbuf + 13*64, s21); + _mm256_store_ps(outbuf + 14*64, s22); + _mm256_store_ps(outbuf + 15*64, s23); + _mm256_store_ps(outbuf + 16*64, s24); + _mm256_store_ps(outbuf + 17*64, s25); + + _mm256_store_ps(outbuf + 18*64, s30); + _mm256_store_ps(outbuf + 19*64, s31); + _mm256_store_ps(outbuf + 20*64, s32); + _mm256_store_ps(outbuf + 21*64, s33); + _mm256_store_ps(outbuf + 22*64, s34); + _mm256_store_ps(outbuf + 23*64, s35); + } + } + else + { + for (int atom_id = 0; atom_id < _FX_WINO_NATOMS_F32; atom_id++, + outbuf += _FX_WINO_ATOM_F32) + { + __m256 s00 = _mm256_set1_ps(0.f), s01 = s00, s02 = s00; + __m256 s10 = _mm256_set1_ps(0.f), s11 = s00, s12 = s00; + __m256 s20 = _mm256_set1_ps(0.f), s21 = s00, s22 = s00; + __m256 s30 = _mm256_set1_ps(0.f), s31 = s00, s32 = s00; + for (int c = 0; c < Cg; c++, inwptr += _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32, + wptr += _FX_WINO_KBLOCK*_FX_WINO_ATOM_F32) { + __m256 w0 = _mm256_load_ps(wptr), w1 = _mm256_load_ps(wptr + 8); + __m256 w2 = _mm256_load_ps(wptr + 16), w3 = _mm256_load_ps(wptr + 24); + __m256 x0, x1, x2; + x0 = _mm256_load_ps(inwptr); + x1 = _mm256_load_ps(inwptr + 8); + x2 = _mm256_load_ps(inwptr + 16); + s00 = _mm256_fmadd_ps(w0, x0, s00); + s01 = _mm256_fmadd_ps(w0, x1, s01); + s02 = _mm256_fmadd_ps(w0, x2, s02); + s10 = _mm256_fmadd_ps(w1, x0, s10); + s11 = _mm256_fmadd_ps(w1, x1, s11); + s12 = _mm256_fmadd_ps(w1, x2, s12); + s20 = _mm256_fmadd_ps(w2, x0, s20); + s21 = _mm256_fmadd_ps(w2, x1, s21); + s22 = _mm256_fmadd_ps(w2, x2, s22); + s30 = _mm256_fmadd_ps(w3, x0, s30); + s31 = _mm256_fmadd_ps(w3, x1, s31); + s32 = _mm256_fmadd_ps(w3, x2, s32); + } + + _mm256_store_ps(outbuf, s00); + _mm256_store_ps(outbuf + 1*64, s01); + _mm256_store_ps(outbuf + 2*64, s02); + _mm256_store_ps(outbuf + 6*64, s10); + _mm256_store_ps(outbuf + 7*64, s11); + _mm256_store_ps(outbuf + 8*64, s12); + _mm256_store_ps(outbuf + 12*64, s20); + _mm256_store_ps(outbuf + 13*64, s21); + _mm256_store_ps(outbuf + 14*64, s22); + _mm256_store_ps(outbuf + 18*64, s30); + _mm256_store_ps(outbuf + 19*64, s31); + _mm256_store_ps(outbuf + 20*64, s32); + } + } + _mm256_zeroupper(); +} +static inline +void transpose8_ps(__m256 &row0, __m256 &row1, __m256 &row2, __m256 &row3, __m256 &row4, __m256 &row5, __m256 &row6, __m256 &row7) +{ + __m256 __t0, __t1, __t2, __t3, __t4, __t5, __t6, __t7; + __m256 __tt0, __tt1, __tt2, __tt3, __tt4, __tt5, __tt6, __tt7; + __t0 = _mm256_unpacklo_ps(row0, row1); + __t1 = _mm256_unpackhi_ps(row0, row1); + __t2 = _mm256_unpacklo_ps(row2, row3); + __t3 = _mm256_unpackhi_ps(row2, row3); + __t4 = _mm256_unpacklo_ps(row4, row5); + __t5 = _mm256_unpackhi_ps(row4, row5); + __t6 = _mm256_unpacklo_ps(row6, row7); + __t7 = _mm256_unpackhi_ps(row6, row7); + __tt0 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(1,0,1,0)); + __tt1 = _mm256_shuffle_ps(__t0,__t2,_MM_SHUFFLE(3,2,3,2)); + __tt2 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(1,0,1,0)); + __tt3 = _mm256_shuffle_ps(__t1,__t3,_MM_SHUFFLE(3,2,3,2)); + __tt4 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(1,0,1,0)); + __tt5 = _mm256_shuffle_ps(__t4,__t6,_MM_SHUFFLE(3,2,3,2)); + __tt6 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(1,0,1,0)); + __tt7 = _mm256_shuffle_ps(__t5,__t7,_MM_SHUFFLE(3,2,3,2)); + row0 = _mm256_permute2f128_ps(__tt0, __tt4, 0x20); + row1 = _mm256_permute2f128_ps(__tt1, __tt5, 0x20); + row2 = _mm256_permute2f128_ps(__tt2, __tt6, 0x20); + row3 = _mm256_permute2f128_ps(__tt3, __tt7, 0x20); + row4 = _mm256_permute2f128_ps(__tt0, __tt4, 0x31); + row5 = _mm256_permute2f128_ps(__tt1, __tt5, 0x31); + row6 = _mm256_permute2f128_ps(__tt2, __tt6, 0x31); + row7 = _mm256_permute2f128_ps(__tt3, __tt7, 0x31); +} + +/*Input transform*/ +void _fx_winograd_BtXB_8x8_f32(const float* inptr, int inpstep, float* outptr, int Cg) +{ + __m256 x00 = _mm256_loadu_ps(inptr); + __m256 x10 = _mm256_loadu_ps(inptr + inpstep); + __m256 x20 = _mm256_loadu_ps(inptr + inpstep*2); + __m256 x30 = _mm256_loadu_ps(inptr + inpstep*3); + __m256 x40 = _mm256_loadu_ps(inptr + inpstep*4); + __m256 x50 = _mm256_loadu_ps(inptr + inpstep*5); + __m256 x60 = _mm256_loadu_ps(inptr + inpstep*6); + __m256 x70 = _mm256_loadu_ps(inptr + inpstep*7); + + __m256 z00, z10, z20, z30, z40, z50, z60, z70; + + { + /* Y[0] = [1.f, 0.f, -5.25f, 0.f, 5.25f, 0.f, -1.f, 0.f]*X */ + /* Y[7] = [0.f, -1.f, 0.f, 5.25f, 0.f, -5.25f, 0.f, 1.f]*X */ + __m256 q5_25 = _mm256_set1_ps(5.25f), t00, t10; + t00 = _mm256_sub_ps(x40, x20); + t10 = _mm256_sub_ps(x30, x50); + + __m256 y00 = _mm256_fmadd_ps(t00, q5_25, _mm256_sub_ps(x00, x60)); + __m256 y70 = _mm256_fmadd_ps(t10, q5_25, _mm256_sub_ps(x70, x10)); + + /* Y[1] = [0.f, 1.f, 1.f, -4.25f, -4.25f, 1.f, 1.f, 0.f]*X */ + /* Y[2] = [0.f, -1.f, 1.f, 4.25f, -4.25f, -1.f, 1.f, 0.f]*X */ + __m256 qm4_25 = _mm256_set1_ps(-4.25f); + t00 = _mm256_fmadd_ps(x30, qm4_25, _mm256_add_ps(x10, x50)); + t10 = _mm256_fmadd_ps(x40, qm4_25, _mm256_add_ps(x20, x60)); + + __m256 y10 = _mm256_add_ps(t00, t10); + __m256 y20 = _mm256_sub_ps(t10, t00); + + /* Y[3] = [0.f, 0.5f, 0.25f, -2.5f, -1.25f, 2.f, 1.f, 0.f]*X */ + /* Y[4] = [0.f, -0.5f, 0.25f, 2.5f, -1.25f, -2.f, 1.f, 0.f]*X */ + __m256 q0_5 = _mm256_set1_ps(0.5f), q0_25 = _mm256_set1_ps(0.25f); + __m256 qm2_5 = _mm256_set1_ps(-2.5f), qm1_25 = _mm256_set1_ps(-1.25f); + t00 = _mm256_fmadd_ps(x10, q0_5, _mm256_add_ps(x50, x50)); + t10 = _mm256_fmadd_ps(x20, q0_25, x60); + t00 = _mm256_fmadd_ps(x30, qm2_5, t00); + t10 = _mm256_fmadd_ps(x40, qm1_25, t10); + + __m256 y30 = _mm256_add_ps(t00, t10); + __m256 y40 = _mm256_sub_ps(t10, t00); + + /* Y[5] = [0.f, 2.f, 4.f, -2.5f, -5.f, 0.5f, 1.f, 0.f]*X */ + /* Y[6] = [0.f, -2.f, 4.f, 2.5f, -5.f, -0.5f, 1.f, 0.f]*X */ + __m256 q4 = _mm256_set1_ps(4.f), qm5 = _mm256_set1_ps(-5.f); + t00 = _mm256_fmadd_ps(x50, q0_5, _mm256_add_ps(x10, x10)); + t10 = _mm256_fmadd_ps(x20, q4 , x60); + t00 = _mm256_fmadd_ps(x30, qm2_5, t00); + t10 = _mm256_fmadd_ps(x40, qm5 , t10); + + __m256 y50 = _mm256_add_ps(t00, t10); + __m256 y60 = _mm256_sub_ps(t10, t00); + + /* transpose 8x8 matrix in-place with some renumeration of the elements: */ + transpose8_ps(y00, y10, y20, y30, y40, y50, y60, y70); + + /* Z[0] = [1.f, 0.f, -5.25f, 0.f, 5.25f, 0.f, -1.f, 0.f]*Y */ + /* Z[7] = [0.f, -1.f, 0.f, 5.25f, 0.f, -5.25f, 0.f, 1.f]*Y */ + t00 = _mm256_sub_ps(y40, y20); + t10 = _mm256_sub_ps(y30, y50); + z00 = _mm256_fmadd_ps(t00, q5_25, _mm256_sub_ps(y00, y60)); + z70 = _mm256_fmadd_ps(t10, q5_25, _mm256_sub_ps(y70, y10)); + + /* Z[1] = [0.f, 1.f, 1.f, -4.25f, -4.25f, 1.f, 1.f, 0.f]*Y */ + /* Z[2] = [0.f, -1.f, 1.f, 4.25f, -4.25f, -1.f, 1.f, 0.f]*Y */ + t00 = _mm256_fmadd_ps(y30, qm4_25, _mm256_add_ps(y10, y50)); + t10 = _mm256_fmadd_ps(y40, qm4_25, _mm256_add_ps(y20, y60)); + z10 = _mm256_add_ps(t00, t10); + z20 = _mm256_sub_ps(t10, t00); + + /* Z[3] = [0.f, 0.5f, 0.25f, -2.5f, -1.25f, 2.f, 1.f, 0.f]*Y */ + /* Z[4] = [0.f, -0.5f, 0.25f, 2.5f, -1.25f, -2.f, 1.f, 0.f]*Y */ + t00 = _mm256_fmadd_ps(y10, q0_5, _mm256_add_ps(y50, y50)); + t10 = _mm256_fmadd_ps(y20, q0_25, y60); + t00 = _mm256_fmadd_ps(y30, qm2_5, t00); + t10 = _mm256_fmadd_ps(y40, qm1_25, t10); + + z30 = _mm256_add_ps(t00, t10); + z40 = _mm256_sub_ps(t10, t00); + + /* Z[5] = [0.f, 2.f, 4.f, -2.5f, -5.f, 0.5f, 1.f, 0.f]*Y */ + /* Z[6] = [0.f, -2.f, 4.f, 2.5f, -5.f, -0.5f, 1.f, 0.f]*Y */ + t00 = _mm256_fmadd_ps(y50, q0_5, _mm256_add_ps(y10, y10)); + t10 = _mm256_fmadd_ps(y20, q4, y60); + t00 = _mm256_fmadd_ps(y30, qm2_5, t00); + t10 = _mm256_fmadd_ps(y40, qm5, t10); + + z50 = _mm256_add_ps(t00, t10); + z60 = _mm256_sub_ps(t10, t00); + } + + const int outstep = _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32*Cg; + + _mm256_storeu_ps(outptr, z00); + _mm256_storeu_ps(outptr + outstep, z10); + _mm256_storeu_ps(outptr + outstep*2, z20); + _mm256_storeu_ps(outptr + outstep*3, z30); + _mm256_storeu_ps(outptr + outstep*4, z40); + _mm256_storeu_ps(outptr + outstep*5, z50); + _mm256_storeu_ps(outptr + outstep*6, z60); + _mm256_storeu_ps(outptr + outstep*7, z70); + _mm256_zeroupper(); +} + +#define STORE6_ELE_FROM_16(ptr, z00, lowM, highM) \ + lowM = _mm256_castps256_ps128(z00); \ + highM = _mm256_extractf128_ps(z00, 1); \ + _mm_storeu_ps(ptr, lowM); \ + _mm_storel_epi64((__m128i*)(ptr + 4), _mm_castps_si128(highM)) + +/* Inverse Winograd 8x8 transform: + out = (A'*inp*A)', where + inp is input 8x8 FP32 matrix, + A' is + [1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f, + 0.f, 1.f, -1.f, 2.f, -2.f, 0.5f, -0.5f, 0.f, + 0.f, 1.f, 1.f, 4.f, 4.f, 0.25f, 0.25f, 0.f, + 0.f, 1.f, -1.f, 8.f, -8.f, 0.125f, -0.125f, 0.f, + 0.f, 1.f, 1.f, 16.f, 16.f, 1.f/16, 1.f/16, 0.f, + 0.f, 1.f, -1.f, 32.f, -32.f, 1.f/32, -1.f/32, 1.f] +*/ +void _fx_winograd_AtXA_8x8_f32(const float* inptr, int inpstep, + float* bpptr, int bpstep, float* outptr, int outstep, + float bias, float minval, float maxval, bool ifMinMaxAct) +{ + + __m256 x00 = _mm256_load_ps(inptr); + __m256 x10 = _mm256_load_ps(inptr + inpstep); + __m256 x20 = _mm256_load_ps(inptr + inpstep*2); + __m256 x30 = _mm256_load_ps(inptr + inpstep*3); + __m256 x40 = _mm256_load_ps(inptr + inpstep*4); + __m256 x50 = _mm256_load_ps(inptr + inpstep*5); + __m256 x60 = _mm256_load_ps(inptr + inpstep*6); + __m256 x70 = _mm256_load_ps(inptr + inpstep*7); + __m256 z00, z10, z20, z30, z40, z50; + + { + __m256 s12_0, s34_0, s56_0; + s12_0 = _mm256_add_ps(x10, x20); + s34_0 = _mm256_add_ps(x30, x40); + s56_0 = _mm256_add_ps(x50, x60); + + __m256 y00 = _mm256_add_ps(x00, _mm256_add_ps(s12_0, _mm256_add_ps(s34_0, s56_0))); + __m256 y20 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(0.25f), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(4.0f), s12_0)); + __m256 y40 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(1.f/16), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(16.0f), s12_0)); + + s12_0 = _mm256_sub_ps(x10, x20); + s34_0 = _mm256_sub_ps(x30, x40); + s56_0 = _mm256_sub_ps(x50, x60); + __m256 y50 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(1.f/32), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(32.f), _mm256_add_ps(x70, s12_0))); + __m256 y10 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(0.5f), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(2.f), s12_0)); + __m256 y30 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(0.125f), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(8.f), s12_0)); + __m256 y60 = _mm256_set1_ps(0.f), y70 = y60; + + /* transpose 8x8 matrix in-place with some renumeration of the elements: */ + + transpose8_ps(y00, y10, y20, y30, y40, y50, y60, y70); + + s12_0 = _mm256_add_ps(y10, y20); + s34_0 = _mm256_add_ps(y30, y40); + s56_0 = _mm256_add_ps(y50, y60); + + z00 = _mm256_add_ps(y00, _mm256_add_ps(s12_0, _mm256_add_ps(s34_0, s56_0))); + z20 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(0.25f), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(4.0f), s12_0)); + z40 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(1.f/16), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(16.0f), s12_0)); + + s12_0 = _mm256_sub_ps(y10, y20); + s34_0 = _mm256_sub_ps(y30, y40); + s56_0 = _mm256_sub_ps(y50, y60); + + z50 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(1.f/32), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(32.0f), _mm256_add_ps(y70, s12_0))); + z10 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(0.5f), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(2.0f), s12_0)); + z30 = _mm256_fmadd_ps(s56_0, _mm256_set1_ps(0.125f), _mm256_fmadd_ps(s34_0, _mm256_set1_ps(8.0f), s12_0)); + + __m256 vbias = _mm256_set1_ps(bias); + z00 = _mm256_add_ps(vbias, z00); + z10 = _mm256_add_ps(vbias, z10); + z20 = _mm256_add_ps(vbias, z20); + z30 = _mm256_add_ps(vbias, z30); + z40 = _mm256_add_ps(vbias, z40); + z50 = _mm256_add_ps(vbias, z50); + } + + if (bpptr) + { + z00 = _mm256_add_ps(z00, _mm256_loadu_ps(bpptr)); + z10 = _mm256_add_ps(z10, _mm256_loadu_ps(bpptr + bpstep)); + z20 = _mm256_add_ps(z20, _mm256_loadu_ps(bpptr + bpstep*2)); + z30 = _mm256_add_ps(z30, _mm256_loadu_ps(bpptr + bpstep*3)); + z40 = _mm256_add_ps(z40, _mm256_loadu_ps(bpptr + bpstep*4)); + z50 = _mm256_add_ps(z50, _mm256_loadu_ps(bpptr + bpstep*5)); + } + + if (ifMinMaxAct) + { + __m256 vmax = _mm256_set1_ps(maxval); + __m256 vmin = _mm256_set1_ps(minval); + + z00 = _mm256_min_ps(_mm256_max_ps(z00, vmin), vmax); + z10 = _mm256_min_ps(_mm256_max_ps(z10, vmin), vmax); + z20 = _mm256_min_ps(_mm256_max_ps(z20, vmin), vmax); + z30 = _mm256_min_ps(_mm256_max_ps(z30, vmin), vmax); + z40 = _mm256_min_ps(_mm256_max_ps(z40, vmin), vmax); + z50 = _mm256_min_ps(_mm256_max_ps(z50, vmin), vmax); + } + + __m128 lowM, highM; + STORE6_ELE_FROM_16(outptr, z00, lowM, highM); + STORE6_ELE_FROM_16(outptr + outstep, z10, lowM, highM); + STORE6_ELE_FROM_16(outptr + outstep * 2, z20, lowM, highM); + STORE6_ELE_FROM_16(outptr + outstep * 3, z30, lowM, highM); + STORE6_ELE_FROM_16(outptr + outstep * 4, z40, lowM, highM); + STORE6_ELE_FROM_16(outptr + outstep * 5, z50, lowM, highM); + _mm256_zeroupper(); +} + #endif } // namespace opt_AVX2 +} // namespace dnn } // namespace cv \ No newline at end of file diff --git a/modules/dnn/src/layers/fast_convolution/fast_convolution.cpp b/modules/dnn/src/layers/fast_convolution/fast_convolution.cpp index 139ea7f6fc..1cde7b324f 100644 --- a/modules/dnn/src/layers/fast_convolution/fast_convolution.cpp +++ b/modules/dnn/src/layers/fast_convolution/fast_convolution.cpp @@ -14,40 +14,249 @@ #include "fast_convolution.simd.hpp" namespace cv { namespace dnn { - -Ptr initFastConv2d( +enum { VEC_ALIGN = 32, DFT_TYPE = CV_32F }; // Memory alignment. +Ptr initFastConv( + InputArray _weightsMat, + float* srcBias, int ngroups, - int K, int C, int Hk, int Wk, - int stride_x, int stride_y, - int dilation_x, int dilation_y, + int K, int C, + const std::vector& kernel_size, + const std::vector& strides, + const std::vector& dilations, const std::vector& pads_begin, const std::vector& pads_end, - float* srcWeights, - float* srcBias) + int conv_dim, + bool useWinograd) { - Ptr conv = makePtr(); + Ptr conv = makePtr(); CV_Assert(ngroups > 0 && K > 0 && C > 0 && K % ngroups == 0); - CV_Assert(Hk > 0 && Wk > 0); - CV_Assert(stride_y > 0 && stride_x > 0); - CV_Assert(dilation_y > 0 && dilation_x > 0); - conv->K = K; conv->C = C; conv->Hk = Hk; conv->Wk = Wk; // [K, iC, kH, kW] - conv->stride_y = stride_y; - conv->stride_x = stride_x; - conv->dilation_y = dilation_y; - conv->dilation_x = dilation_x; + // Weight shape, [K, C, Dk, Hk, Wk] for Conv3D, [K, C, Hk, Wk] for Conv2D, [K, C, Wk] for Conv1D. + int Dk = conv_dim == CONV_3D ? (int)kernel_size[0] : 1; + int Hk = conv_dim == CONV_1D ? 1 : (int)kernel_size[kernel_size.size() - 2]; + int Wk = (int)kernel_size.back(); + int karea = Wk*Hk*Dk; + conv->pad_front = conv_dim == CONV_3D ? (int)pads_begin[0] : 0; + conv->pad_top = conv_dim == CONV_1D ? 0 : (int)pads_begin[pads_begin.size() - 2]; + conv->pad_left = (int)pads_begin.back(); + + conv->pad_behind = conv_dim == CONV_3D ? (int)pads_end[0] : 0; + conv->pad_bottom = conv_dim == CONV_1D ? 0 : (int)pads_end[pads_end.size() - 2]; + conv->pad_right = (int)pads_end.back(); + + int stride_d = conv_dim == CONV_3D ? (int)strides[0] : 0; + int stride_h = conv_dim == CONV_1D ? 0 : (int)strides[strides.size() - 2]; + int stride_w = (int)strides.back(); + + int dilation_d = conv_dim == CONV_3D ? (int)dilations[0] : 1; + int dilation_h = conv_dim == CONV_1D ? 1 : (int)dilations[dilations.size() - 2]; + int dilation_w = (int)dilations.back(); + + CV_Assert(Dk > 0 && Hk > 0 && Wk > 0); + CV_Assert(stride_d >= 0 && stride_h >= 0 && stride_w > 0); + CV_Assert(dilation_d > 0 && dilation_h > 0 && dilation_w > 0); + + conv->K = K; conv->C = C; conv->Hk = Hk; conv->Wk = Wk, conv->Dk = Dk; + + conv->stride_d = stride_d; + conv->stride_h = stride_h; + conv->stride_w = stride_w; + + conv->dilation_d = dilation_d; + conv->dilation_h = dilation_h; + conv->dilation_w = dilation_w; + conv->conv_dim = conv_dim; conv->ngroups = ngroups; - conv->pad_top = pads_begin[0]; - conv->pad_bottom = pads_end[0]; - conv->pad_left = pads_begin[1]; - conv->pad_right = pads_end[1]; + + bool ifRunDepthWise = ngroups > 1 && ngroups == K && ngroups == C; + bool ifRunDepthWiseRemain = false; // It's for big padding or big kernel or Conv3D depth-wise convolution. + + if (ifRunDepthWise) + { + if (conv_dim == CONV_1D) + { + ifRunDepthWise &= Hk == 1 && Wk == 3 && (stride_w == 1 || (stride_w == 2 && dilation_w == 1)) + && max(stride_w, dilation_w) >= conv->pad_left && conv->pad_left <= 1; + } + else if (conv_dim == CONV_2D) + { + ifRunDepthWise &= Hk == 3 && Wk == 3 && ((stride_w == 1) || (stride_w == 2 && dilation_w == 1)) && + max(stride_w, dilation_w) >= conv->pad_left && max(stride_h, dilation_h) >= conv->pad_top + && conv->pad_left <= 1 && conv->pad_top <= 1; + } + + if (!ifRunDepthWise || conv_dim == CONV_3D) + { + ifRunDepthWise = false; + ifRunDepthWiseRemain = true; + } + } + + conv->conv_type = ifRunDepthWise && conv_dim != CONV_3D ? _FX_CONV_TYPE_DEPTHWISE : + useWinograd && (conv_dim == CONV_2D && (conv->useSIMD128 || conv->useAVX2 || conv->useNEON) && + Hk == 3 && Wk == 3 && dilation_h == 1 && dilation_w == 1 && stride_h == 1 && stride_w == 1) ? + _FX_CONV_TYPE_WINOGRAD3X3 : + (ifRunDepthWiseRemain ? _FX_CONV_TYPE_DEPTHWISE_REMAIN : _FX_CONV_TYPE_GENERIC); + +#if !(CV_NEON || CV_SIMD128 || CV_TRY_AVX2) + if (conv->conv_type == _FX_CONV_TYPE_WINOGRAD3X3) // Disabel Winograd when CV_NEON, CV_SIMD128 and CV_TRY_AVX2 are not available. + conv->conv_type = _FX_CONV_TYPE_GENERIC; +#endif + + Mat weightsMat = _weightsMat.getMat(); + auto wShape = shape(weightsMat); + const size_t wstep = weightsMat.step1(); + + float *srcWeights = (float *)weightsMat.data; + if (conv->conv_type == _FX_CONV_TYPE_DEPTHWISE || conv->conv_type == _FX_CONV_TYPE_DEPTHWISE_REMAIN) + { + // Handle the Conv1D, Conv2D and Conv3D depth-wise. + // for depth-wise convolutions on NCHW data we just preserve the weights in KCHW layout, + // but add some padding to make the weights array layout more SIMD-friendly + int ksize = karea; + + // TODO: simplify the following code with std::copy. + // this code aims to let memory fit with vector size. + int padded_ksize = ((ksize + VEC_ALIGN-1) / VEC_ALIGN) * VEC_ALIGN; + int nweights = C*padded_ksize; + conv->weightsBuf.reserve(nweights + VEC_ALIGN); + conv->weightsBufPtr = alignPtr(conv->weightsBuf.data(), VEC_ALIGN); + memset(conv->weightsBufPtr, 0, nweights*sizeof(conv->weightsBufPtr[0])); + auto weightsBufPtr = conv->weightsBufPtr; + parallel_for_(Range(0, C), [&](const Range& r0){ + for(int c = r0.start; c < r0.end; c++) + { + for (int k = 0; k < ksize; k++) + weightsBufPtr[c*padded_ksize + k] = srcWeights[c*wstep + k]; + }}); + } + else if(conv->conv_type == _FX_CONV_TYPE_WINOGRAD3X3) // winograd + { + static const float ktm[8][3] = { + {1.0f, 0.0f, 0.0f}, + {-2.0f / 9, -2.0f / 9, -2.0f / 9}, + {-2.0f / 9, 2.0f / 9, -2.0f / 9}, + {1.0f / 90, 1.0f / 45, 2.0f / 45}, + {1.0f / 90, -1.0f / 45, 2.0f / 45}, + {32.f/45, 16.f/45, 8.f/45}, + {32.f/45, -16.f/45, 8.f/45}, + {0.0f, 0.0f, 1.0f} + }; + + // the weights are packed as 6-dim tensor: + // ngroups * ceil((K/ngroups)/KBLOCK) * (W*W/ATOM_SIZE) * (C/ngroups) * KBLOCK * ATOM_SIZE, + // where W is the size of Winograd-transformed kernel (8x8), + // ATOM_SIZE is number of lanes in SIMD register (4 for NEON and FP32), + // KBLOCK is some platform-dependent constant dependent on the number of SIMD registers. + int ksize = _FX_WINO_KSIZE * _FX_WINO_KSIZE; + int Cg = C/ngroups; + int Kg = K/ngroups; + int Kg_nblocks = (Kg + _FX_WINO_KBLOCK - 1)/_FX_WINO_KBLOCK; + size_t nweights = ngroups*Kg_nblocks*Cg*_FX_WINO_KBLOCK*_FX_WINO_AREA; + conv->weightsWinoBuf.reserve(nweights + VEC_ALIGN); + conv->weightsWinoBufPtr = alignPtr(conv->weightsWinoBuf.data(), VEC_ALIGN); + float* wptrWino = conv->weightsWinoBufPtr; + memset(wptrWino, 0, nweights * sizeof(wptrWino[0])); + + parallel_for_(Range(0, K), [&](const Range& r0){ + float kernelTm[_FX_WINO_AREA]; + for (int k = r0.start; k < r0.end; k++) + { + int g = k / Kg; + int k_ = k - g*Kg; + int ki = k_ / _FX_WINO_KBLOCK; + int dk = k_ - ki*_FX_WINO_KBLOCK; + + for (int c = 0; c < Cg; c++) + { + // wstep = Hk*Wk*Cg + const float *kernel0 = srcWeights + k * wstep + c * ksize; + + // transform kernel, transposed + const float *k0 = kernel0; + const float *k1 = kernel0 + 3; + const float *k2 = kernel0 + 6; + + // h + float tmp[8][3]; + for (int i = 0; i < 8; i++) + { + tmp[i][0] = k0[0] * ktm[i][0] + k0[1] * ktm[i][1] + k0[2] * ktm[i][2]; + tmp[i][1] = k1[0] * ktm[i][0] + k1[1] * ktm[i][1] + k1[2] * ktm[i][2]; + tmp[i][2] = k2[0] * ktm[i][0] + k2[1] * ktm[i][1] + k2[2] * ktm[i][2]; + } + + // v + for (int j = 0; j < 8; j++) + { + float *tmpp = &tmp[j][0]; + + for (int i = 0; i < 8; i++) + kernelTm[j * 8 + i] = tmpp[0] * ktm[i][0] + tmpp[1] * ktm[i][1] + tmpp[2] * ktm[i][2]; + } + + // repack the data. + float* wptr = wptrWino + (g*Kg_nblocks + ki) * Cg *_FX_WINO_KBLOCK*_FX_WINO_AREA + + (c*_FX_WINO_KBLOCK + dk)*_FX_WINO_ATOM_F32; + for (int i = 0; i < _FX_WINO_NATOMS_F32; i++, + wptr += Cg * _FX_WINO_KBLOCK * _FX_WINO_ATOM_F32) + { + CV_Assert(conv->weightsWinoBufPtr <= wptr && wptr + _FX_WINO_ATOM_F32 <= conv->weightsWinoBufPtr + nweights); + memcpy(wptr, kernelTm + i * _FX_WINO_ATOM_F32, _FX_WINO_ATOM_F32*sizeof (wptr[0])); + } + } + }}); + } + else if (conv->conv_type == _FX_CONV_TYPE_GENERIC) + { + // The weights are packed as + // ngroups x (ceil((K/ngroups)/CONV_MR)*CONV_MR) x (Cg*Hk*Wk*Dk) x CONV_MR tensor + int Kg = K/ngroups, Cg = max(C/ngroups, 1); + int numStripsMR = (Kg + CONV_MR - 1) / CONV_MR; + int Kg_aligned = numStripsMR * CONV_MR; + int DkHkWkCg = Dk*Hk*Wk*Cg; + size_t nweights = ngroups*Kg_aligned*DkHkWkCg; + conv->weightsBuf.reserve(nweights + VEC_ALIGN); + conv->weightsBufPtr = alignPtr(conv->weightsBuf.data(), VEC_ALIGN); + float* weightsBufPtr = conv->weightsBufPtr; + memset(weightsBufPtr, 0, nweights*sizeof(weightsBufPtr[0])); + + // Pack the weight. + parallel_for_(Range(0, ngroups * numStripsMR), [&](const Range& r0){ + for (int gsi = r0.start; gsi < r0.end; gsi++) + { + int g = gsi / numStripsMR; + int si = gsi - g * numStripsMR; + + int startK = si * CONV_MR; + CV_Assert(startK < Kg_aligned); + + float* packed_wptr = weightsBufPtr + DkHkWkCg * (startK + g * Kg_aligned); + int dk = Kg - startK < CONV_MR ? Kg - startK : CONV_MR; // check if we need zero padding. + + int k_idx = g*Kg + startK; + for(int hwd = 0; hwd < Hk*Wk*Dk; hwd++) { + for(int c = 0; c < Cg; c++, packed_wptr += CONV_MR) + { + const float* wptr = srcWeights + wstep * k_idx + c*Hk*Wk*Dk + hwd; + int k = 0; + for(; k < dk; k++, wptr += wstep) + packed_wptr[k] = *wptr; + for(; k < CONV_MR; k++) + packed_wptr[k] = 0.f; + } + } + }}); + } + else + CV_Error(CV_StsUnsupportedFormat, "Unknown convolution type."); // store bias; append some zero's to make sure that - // we can always read FAST_CONV_MR elements starting from any valid index + // we can always read MR elements starting from any valid index { - int k = 0, nbias = K + FAST_CONV_MR-1; + int k = 0, nbias = K + VEC_ALIGN; conv->biasBuf.reserve(nbias); float* biasBufPtr = conv->biasBuf.data(); for(; k < K; k++) @@ -55,369 +264,124 @@ Ptr initFastConv2d( for(; k < nbias; k++) biasBufPtr[k] = 0.f; } - -#if CV_NEON // For now, winograd is ARM platform only. - if (ngroups == 1 && Hk ==3 && Wk == 3 && stride_x == 1 && stride_y == 1 && dilation_x == 1 && dilation_y ==1 - && K >= 16 && C >= 16 ) - conv->ifWinograd63 = true; -#else - conv->ifWinograd63 = false; -#endif - - if (ngroups > 1 && ngroups == K && ngroups == C) - { - // for depth-wise convolutions on NCHW data we just preserve the weights in KCHW layout, - // but add some padding to make the weights array layout more SIMD-friendly - int ksize = Hk*Wk; - int padded_ksize = ((ksize + FAST_VEC_NLANES-1)/FAST_VEC_NLANES)*FAST_VEC_NLANES; // this code aims to let memory fit with vector size. - int nweights = C*padded_ksize; - conv->weightsBuf.reserve(nweights); - float* weightsBufPtr = conv->weightsBuf.data(); - memset(weightsBufPtr, 0, nweights*sizeof(weightsBufPtr[0])); - for(int c = 0; c < C; c++) - { - for (int k = 0; k < ksize; k++) - weightsBufPtr[c*padded_ksize + k] = srcWeights[c*ksize + k]; - } - } - else - { - // The weights are packed as - // ngroups x (ceil((K/ngroups)/FAST_CONV_MR)*FAST_CONV_MR) x (Cg*Hk*Wk) x FAST_CONV_MR tensor - int Kg = K/ngroups, Cg = max(C/ngroups, 1); - int Kg_aligned = ((Kg + FAST_CONV_MR - 1)/FAST_CONV_MR)*FAST_CONV_MR; - size_t nweights = ngroups*Kg_aligned*Cg*Hk*Wk; - conv->weightsBuf.reserve(nweights); - float* weightsBufPtr = conv->weightsBuf.data(); - memset(weightsBufPtr, 0, nweights*sizeof(weightsBufPtr[0])); - float* packed_wptr = weightsBufPtr; - - // pack the weight. - for(int g = 0; g < ngroups; g++) - { - for(int k0 = 0; k0 < Kg_aligned; k0 += FAST_CONV_MR) - { - int dk = Kg - k0 < FAST_CONV_MR ? Kg - k0 : FAST_CONV_MR; - for(int c = 0; c < Cg; c++) - { - for(int yx = 0; yx < Hk*Wk; yx++, packed_wptr += FAST_CONV_MR) - { - const float* wptr = srcWeights + ((g*Kg + k0)*Cg + c)*Hk*Wk + yx; - int k = 0; - for(; k < dk; k++, wptr += Cg*Hk*Wk) - packed_wptr[k] = *wptr; - for(; k < FAST_CONV_MR; k++) - packed_wptr[k] = 0.f; - } - } - } - } - - // Prepare Weight for Winograd F(6x6, 3x3) - if (conv->ifWinograd63) - { - initWinograd63(conv, srcWeights, K, C); - } - } return conv; } -static void packInput(float* inpbuf, const float* inptr, int* yxtab, int ksize, int Cg, int Hi, int Wi, int W0, - int pad_top, int pad_left, int stride_x, int stride_y, int yx0, int slice_len, - bool fast_1x1, bool partial0, bool s1d1p0, bool s1d1) +static inline void packData8(float*& inpbuf, float*& inptrIn, int& in_w, int& x0, int& s0, const int* ofstab, + const int stride_w, const int ksize) { - const size_t inp_planesize = (size_t)Hi*Wi; + float* inpbufC = inpbuf + s0; + float* inptrInC = inptrIn; - if (fast_1x1) - { - /* - super-fast branch for 1x1 convolutions with sy=sx=1. - in this case each feature plane can be safely treated - as 1D array and we just extract next portion - of FAST_CONV_NR elements from each feature plane and - put it together. - */ - inptr += yx0; - if (!partial0) - { - // Make special branch where memcpy() is called with a constant buffer size. - // Compilers will likely unroll this loop properly. - for (int c = 0; c < Cg; c++, inptr += inp_planesize, inpbuf += FAST_CONV_NR) - memcpy(inpbuf, inptr, FAST_CONV_NR * sizeof(inpbuf[0])); - } - else - { - for (int c = 0; c < Cg; c++, inptr += inp_planesize, inpbuf += FAST_CONV_NR) - { - memcpy(inpbuf, inptr, slice_len * sizeof(inpbuf[0])); - memset(inpbuf + slice_len, 0, (FAST_CONV_NR - slice_len) * sizeof(inpbuf[0])); - } - } - } - else if (s1d1p0) - { - /* - slower, but still fast branch for sy=sx=1, dy=dx=1 and without padding, - in this case we copy data from input tensors by chunks. - */ - for (int c = 0; c < Cg; c++) - { - float *inpbuf_c = inpbuf + c * (FAST_CONV_NR * ksize); - const float *inptr_c = inptr + c * inp_planesize; - - for (int k = 0; k < ksize; k++) - { - int y0 = yx0 / W0, x0 = yx0 % W0; - int yi = y0 + yxtab[k * 2], xi = x0 + yxtab[k * 2 + 1]; - float *inpbuf_k = inpbuf_c + k * FAST_CONV_NR; - int xi_0 = yxtab[k * 2 + 1]; - - int i = 0; - for (; i < slice_len;) - { - const float *inptr_k = inptr_c + yi * Wi + xi; - int copy_len = std::min(slice_len - i, W0 - x0); - int di_z = (slice_len == i + copy_len) ? FAST_CONV_NR - slice_len : 0; - - memcpy(inpbuf_k + i, - inptr_k, - copy_len * sizeof(inpbuf_k[0])); - - memset(inpbuf_k + i + copy_len, - 0, di_z * sizeof(inpbuf_k[0])); - - i += copy_len; - x0 = 0; - xi = xi_0; - yi++; - } - } - } - } - else if (s1d1) - { - /* - slower, but still fast branch for sy=sx=1, dy=dx=1. - in this case we copy data from input tensors by chunks and - interleave the data in inpbuf with 0's - (that correspond to the padding elements) when necessary - */ - int y0 = yx0 / W0, x0 = yx0 % W0; - for (int c = 0; c < Cg; c++) - { - float *inpbuf_c = inpbuf + c * (FAST_CONV_NR * ksize); - const float *inptr_c = inptr + c * inp_planesize; - - for (int k = 0; k < ksize; k++) - { - int x0_tmp = x0; - - int xi_0 = yxtab[k * 2 + 1] - pad_left; - - int yi = y0 + yxtab[k * 2] - pad_top, xi = x0_tmp + xi_0; - float *inpbuf_k = inpbuf_c + k * FAST_CONV_NR; - - int i = 0; - for (; i < slice_len;) { - int copyLen = std::min(slice_len - i, W0 - x0_tmp); - - int di_z = (i + copyLen == slice_len) ? FAST_CONV_NR - slice_len - : 0; // The final padding. - // pad_top or pad bottom - if (yi < 0 || yi > Hi - 1) - { - memset(inpbuf_k + i, - 0, (copyLen + di_z) * sizeof(inpbuf_k[0])); - i += copyLen + di_z; - } - else - { - int x_pad_left = 0, x_pad_right = 0; - - // pad_left - if (xi < 0) - { - x_pad_left = std::min(-xi, copyLen); - xi = 0; - copyLen -= x_pad_left; - } - - memset(inpbuf_k + i, - 0, x_pad_left * sizeof(inpbuf_k[0])); - i += x_pad_left; - - // pad right - if (xi + copyLen > Wi) - { - if (xi > Wi) - { - x_pad_right = copyLen; - copyLen = 0; - } - else - { - x_pad_right = std::min(xi + copyLen - Wi, copyLen); - copyLen -= x_pad_right; - } - } - - CV_Assert(copyLen >= 0); - - const float *inptr_k = inptr_c + yi * Wi + xi; - memcpy(inpbuf_k + i, - inptr_k, - copyLen * sizeof(inpbuf_k[0])); - - i += copyLen; - - // pad_right and the final padding. - memset(inpbuf_k + i, - 0, (di_z + x_pad_right) * sizeof(inpbuf_k[0])); - i += x_pad_right + di_z; - } - - x0_tmp = 0; - xi = xi_0; - yi++; - } - } - } - } - else - { - int y0_ = yx0 / W0, x0_ = yx0 - y0_ * W0; + if (stride_w == 1) for (int k = 0; k < ksize; k++) { - int dy = yxtab[k * 2], dx = yxtab[k * 2 + 1]; - int i = 0, y0 = y0_, x0 = x0_; - for (; i < FAST_CONV_NR;) - { - float *inpbuf_ki = inpbuf + k * FAST_CONV_NR + i; - int yi = y0 * stride_y + dy - pad_top; - int xi = x0 * stride_x + dx - pad_left; + int k1 = ofstab[k]; + float v0 = inptrInC[k1]; + float v1 = inptrInC[k1 + 1]; + float v2 = inptrInC[k1 + 2]; + float v3 = inptrInC[k1 + 3]; + float v4 = inptrInC[k1 + 4]; + float v5 = inptrInC[k1 + 5]; + float v6 = inptrInC[k1 + 6]; + float v7 = inptrInC[k1 + 7]; - if ((unsigned) yi < (unsigned) Hi && - (unsigned) xi < (unsigned) Wi) - { - const float *inptr_ki = inptr + yi * Wi + xi; - if (i + 4 <= FAST_CONV_NR && x0 + 4 <= W0 && xi + stride_x * 4 <= Wi) - { - if (stride_x == 2) { - for (int c = 0; c < Cg; c++, inpbuf_ki += FAST_CONV_NR * - ksize, inptr_ki += inp_planesize) - { - float t0 = inptr_ki[0], t1 = inptr_ki[2]; - float t2 = inptr_ki[4], t3 = inptr_ki[6]; - inpbuf_ki[0] = t0; - inpbuf_ki[1] = t1; - inpbuf_ki[2] = t2; - inpbuf_ki[3] = t3; - } - } - else - { - for (int c = 0; c < Cg; c++, inpbuf_ki += FAST_CONV_NR * - ksize, inptr_ki += inp_planesize) - { - float t0 = inptr_ki[0], t1 = inptr_ki[stride_x]; - float t2 = inptr_ki[stride_x * 2], t3 = inptr_ki[stride_x * 3]; - inpbuf_ki[0] = t0; - inpbuf_ki[1] = t1; - inpbuf_ki[2] = t2; - inpbuf_ki[3] = t3; - } - } - i += 4; - x0 += 4; - } - else - { - for (int c = 0; c < Cg; c++, inpbuf_ki += FAST_CONV_NR * - ksize, inptr_ki += inp_planesize) - *inpbuf_ki = *inptr_ki; - i++; - x0++; - } - } - else - { - for (int c = 0; c < Cg; c++, inpbuf_ki += FAST_CONV_NR * ksize) - inpbuf_ki[0] = 0.f; - i++; - x0++; - } - int mask = x0 >= W0; - y0 += mask; - x0 &= mask - 1; - } + inpbufC[k*CONV_NR] = v0; + inpbufC[k*CONV_NR+1] = v1; + inpbufC[k*CONV_NR+2] = v2; + inpbufC[k*CONV_NR+3] = v3; + inpbufC[k*CONV_NR+4] = v4; + inpbufC[k*CONV_NR+5] = v5; + inpbufC[k*CONV_NR+6] = v6; + inpbufC[k*CONV_NR+7] = v7; } - } + else + for (int k = 0; k < ksize; k++) + { + int k1 = ofstab[k]; + float v0 = inptrInC[k1]; + float v1 = inptrInC[k1 + stride_w]; + float v2 = inptrInC[k1 + 2*stride_w]; + float v3 = inptrInC[k1 + 3*stride_w]; + float v4 = inptrInC[k1 + 4*stride_w]; + float v5 = inptrInC[k1 + 5*stride_w]; + float v6 = inptrInC[k1 + 6*stride_w]; + float v7 = inptrInC[k1 + 7*stride_w]; + + inpbufC[k*CONV_NR] = v0; + inpbufC[k*CONV_NR+1] = v1; + inpbufC[k*CONV_NR+2] = v2; + inpbufC[k*CONV_NR+3] = v3; + inpbufC[k*CONV_NR+4] = v4; + inpbufC[k*CONV_NR+5] = v5; + inpbufC[k*CONV_NR+6] = v6; + inpbufC[k*CONV_NR+7] = v7; + } + x0+=7; + s0+=7; + inptrIn += 7*stride_w; + in_w += 7*stride_w; } -static void matMulCompute(float* outptr0, float* inpbuf_task, float* cbuf, const Ptr& conv, int HkWkCg, - int k0, int k1, int yx0, int yx1, size_t out_planesize, int g, int Kg, int Kg_aligned, - bool partial0, ActivationLayer*& activ, float minval, float maxval, bool ifMinMaxAct) +static inline void packData2(float*& inpbuf, float*& inptrIn, int& in_w, int& x0, int& s0, const int* ofstab, + const int stride_w, const int ksize) { - int outstep0 = out_planesize; + float* inpbufC = inpbuf + s0; + float* inptrInC = inptrIn; - for (int k = k0; k < k1; k += FAST_CONV_MR, outptr0 += outstep0 * FAST_CONV_MR) + for (int k = 0; k < ksize; k++) { - int dk = Kg - k < FAST_CONV_MR ? Kg - k : FAST_CONV_MR; - bool partial = partial0 || dk < FAST_CONV_MR; - float *outptr = outptr0; - - int outstep = outstep0; - if (partial) - { - outptr = cbuf; - outstep = FAST_CONV_NR; - } - - -#if CV_TRY_AVX2 - if (conv->useAVX2) - opt_AVX2::convBlock_AVX2( HkWkCg, conv->weightsBuf.data() + (g * Kg_aligned + k) * HkWkCg, - inpbuf_task, outptr, outstep, conv->biasBuf.data() + Kg * g + k, - minval, maxval, ifMinMaxAct); - else -#endif -#if CV_TRY_NEON - if (conv->useNEON) - opt_NEON::convBlock_NEON(HkWkCg, conv->weightsBuf.data() + (g * Kg_aligned + k) * HkWkCg, - inpbuf_task, outptr, outstep, conv->biasBuf.data() + Kg * g + k, - minval, maxval, ifMinMaxAct); - else -#endif - convBlock(HkWkCg, conv->weightsBuf.data() + (g * Kg_aligned + k) * HkWkCg, - inpbuf_task, outptr, outstep, conv->biasBuf.data() + Kg * g + k, - minval, maxval, ifMinMaxAct); - - // activation - if (activ) - activ->forwardSlice(outptr, outptr, yx1 - yx0, outstep, Kg * g + k, - Kg * g + k + dk); - - if (partial) - { - for (int i = 0; i < dk; i++) - memcpy(outptr0 + i * outstep0, cbuf + i * FAST_CONV_NR, - (yx1 - yx0) * sizeof(cbuf[0])); - } + int k1 = ofstab[k]; + float v0 = inptrInC[k1]; + float v1 = inptrInC[k1 + stride_w]; + inpbufC[k*CONV_NR] = v0; + inpbufC[k*CONV_NR+1] = v1; } + + x0++; + s0++; + inptrIn += stride_w; + in_w += stride_w; } -void runFastConv2d(InputArray _input, OutputArray _output, - const Ptr& conv, int ntasks, const Ptr& actLayer) +void runFastConv(InputArray _input, OutputArray _output, const Ptr& conv, int ntasks, + const Ptr& actLayer, const std::vector& reluslope, bool fusedAdd) { Mat input = _input.getMat(); Mat output = _output.getMat(); + int conv_dim = conv->conv_dim; + + CV_Assert_N(input.dims == output.dims, + input.size[0] == output.size[0], + conv->C == input.size[1], + conv->K == output.size[1], + input.type() == output.type(), + input.isContinuous(), + output.isContinuous()); + + Mat fusedAddMat; + if (fusedAdd) + { + CV_Assert(conv->conv_dim != CONV_3D && "Conv3D does not support Conv+Add fusion optimization!"); + fusedAddMat = _output.getMat(); + } + + if (conv->conv_type == _FX_CONV_TYPE_DEPTHWISE) + { + // Depthwise-Convolution layer should not be followed by Add layer. + CV_Assert(fusedAddMat.empty() && (conv_dim == CONV_1D || conv_dim == CONV_2D)); + return runDepthwise(input, output, conv,actLayer.get(), reluslope); + } + MatShape inputShape = shape(input); MatShape outputShape = shape(output); - CV_Assert(inputShape.size() == 4 && outputShape.size() == 4); - ActivationLayer* activ = 0; + CV_Assert(inputShape.size() == outputShape.size()); + + ActivationLayer* activ = nullptr; float minval = -FLT_MAX, maxval = FLT_MAX; bool ifMinMaxAct = false; + if (actLayer) { Ptr activ_relu = actLayer.dynamicCast(); @@ -450,245 +414,671 @@ void runFastConv2d(InputArray _input, OutputArray _output, else activ = nullptr; - if (conv->ngroups > 1 && conv->ngroups == conv->K && conv->ngroups == conv->C) + if (conv->conv_type == _FX_CONV_TYPE_WINOGRAD3X3) // winograd { - return runDepthwise(input, output, conv, minval, maxval, activ, ifMinMaxAct); - } - -#if CV_NEON - if ( conv->ifWinograd63 - && inputShape[2] > 12 && inputShape[3] > 12 - && inputShape[2] < 120 && inputShape[3] < 120 ) - { - // In general, for winograd branch, more cores will give better performance. - int maxNumThread = std::max(getNumThreads(), 1); - if (runWinograd63(input, output, conv, maxNumThread, minval, maxval, activ, ifMinMaxAct)) + CV_Assert(conv->weightsWinoBufPtr && input.dims == 4 && conv_dim == CONV_2D); + if (runWinograd63(input, fusedAddMat, output, conv, ntasks, minval, maxval, activ, ifMinMaxAct)) return; } -#endif - float* inp = input.ptr(); - float* out = output.ptr(); + int N = inputShape[0], C = inputShape[1]; + + // input shape: [N, C, D, H, W] for Conv3D, [N, C, H, W] for Conv2D, [N, C, W] for Conv1D. + int Di = conv_dim == CONV_3D ? inputShape[2] : 1; + int Hi = conv_dim == CONV_1D ? 1 : inputShape[inputShape.size() - 2]; + int Wi = inputShape[inputShape.size() - 1]; + + int ngroups = conv->ngroups; + int K = conv->K, Dk = conv->Dk, Hk = conv->Hk, Wk = conv->Wk; + + int D0 = conv_dim == CONV_3D ? outputShape[2] : 1; + int H0 = conv_dim == CONV_1D ? 1 : outputShape[outputShape.size() - 2]; + int W0 = outputShape[outputShape.size() - 1]; - int N = inputShape[0], C = inputShape[1], Hi = inputShape[2], Wi = inputShape[3]; // [N, C, H, W] - int K = conv->K, Hk = conv->Hk, Wk = conv->Wk; - int H0 = outputShape[2], W0 = outputShape[3], ngroups = conv->ngroups; // ngroups int Cg = C/ngroups, Kg = K/ngroups; - int Kg_nblocks = (Kg + FAST_CONV_MR-1)/FAST_CONV_MR, Kg_aligned = Kg_nblocks*FAST_CONV_MR; // align to MR - const size_t inp_planesize = (size_t)Hi*Wi; - const size_t out_planesize = (size_t)H0*W0; + const size_t inp_planesize = (size_t)Di*Hi*Wi; + const size_t out_planesize = (size_t)D0*H0*W0; - int pad_top = conv->pad_top, pad_bottom = conv->pad_bottom; + int pad_front = conv->pad_front; + int pad_top = conv->pad_top; int pad_left = conv->pad_left; - int pad_right = conv->pad_right; - int stride_y = conv->stride_y, stride_x = conv->stride_x; - int dilation_y = conv->dilation_y, dilation_x = conv->dilation_x; + int stride_d = conv->stride_d, stride_h = conv->stride_h, stride_w = conv->stride_w; + int dilation_d = conv->dilation_d, dilation_h = conv->dilation_h, dilation_w = conv->dilation_w; - int ksize = Hk * Wk; - bool s1d1 = stride_x == 1 && stride_y == 1 && dilation_x == 1 && dilation_y == 1; - bool s1d1p0 = s1d1 && pad_top == 0 && pad_left ==0 && pad_bottom == 0 && pad_right == 0; - bool fast_1x1 = stride_x == 1 && stride_y == 1 && ksize == 1; - int HkWkCg = Hk*Wk*Cg; + int ksize = Dk*Hk*Wk; + bool fast_1x1 = ksize == 1 && stride_d == 1 && stride_w == 1 && stride_h == 1 && + pad_front == 0 && pad_top == 0 && pad_left == 0; + int DkHkWkCg = Dk*Hk*Wk*Cg; - enum { VEC_ALIGN = 8, DFT_TYPE = CV_32F }; - size_t taskbufsize = FAST_CONV_NR*HkWkCg; // input buffer - size_t taskbufsizeOutput = FAST_CONV_NR * FAST_CONV_MR; - size_t inputbufsize = 0; - size_t outbufsize = ntasks * taskbufsizeOutput; + std::vector ofstab_(Hk*Wk*Dk*4, 0); + int* ofstab = ofstab_.data(); + int* dhwTab = ofstab + Hk*Wk*Dk; + int padded_ksize = ((ksize + VEC_ALIGN-1) / VEC_ALIGN) * VEC_ALIGN; - int stripes_per_sample = (out_planesize + FAST_CONV_NR - 1)/FAST_CONV_NR; // align to NR - size_t hw_task = stripes_per_sample; - size_t hw_aligned = stripes_per_sample * FAST_CONV_NR; - - bool separatedLoop = false; - - if (stripes_per_sample < 4 * ntasks) + if (conv_dim == CONV_1D) { - // If stripes_per_sample is small, we parallelize on K (output channel). - stripes_per_sample = 1; - - // Separated Parallelloop could save much time in packing input data. But it may cost more memory, we use it when batch size is 1. - if (N == 1) + for( int w = 0; w < Wk; w++) { - separatedLoop = true; - inputbufsize = ngroups * hw_aligned * HkWkCg; - } - - if (!separatedLoop) - { - inputbufsize = taskbufsize * ntasks; + int dw = w*dilation_w; + dhwTab[w*3+2] = dw; + ofstab[w] = dw; } } + else if (conv_dim == CONV_2D) + { + for (int h = 0; h < Hk; h++) + for( int w = 0; w < Wk; w++) + { + int k = h*Wk + w; + int dh = h*dilation_h, dw = w*dilation_w; + dhwTab[k*3+1] = dh; + dhwTab[k*3+2] = dw; + ofstab[k] = dh*Wi + dw; + } + } else { - // If stripes_per_sample is big, we parallelize on H0*W0. - Kg_nblocks = 1; - inputbufsize = taskbufsize * ntasks; + for (int d = 0; d < Dk; d++) + for (int h = 0; h < Hk; h++) + { + for (int w = 0; w < Wk; w++) + { + int k = d*Hk*Wk + h*Wk + w; + int dd = d*dilation_d, dh = h*dilation_h, dw = w*dilation_w; + dhwTab[k*3] = dd; + dhwTab[k*3+1] = dh; + dhwTab[k*3+2] = dw; + ofstab[k] = dd*Hi*Wi + dh*Wi + dw; + } + } } + int MAX_STRIPES = (56 + CONV_NR - 1)/CONV_NR; + + // Friendly to L1 cache + const int K_BLOCK_SIZE = conv->conv_type == _FX_CONV_TYPE_DEPTHWISE_REMAIN ? 1 : 32; + const int C_BLOCK_SIZE = 256; + + int Kg_nblocks = (Kg + CONV_MR-1)/CONV_MR, Kg_aligned = Kg_nblocks * CONV_MR; + + int stripes_per_sample = ((int)out_planesize + CONV_NR - 1) / CONV_NR; + + if (stripes_per_sample < ntasks * 4 && conv->conv_type != _FX_CONV_TYPE_DEPTHWISE_REMAIN) + { + MAX_STRIPES = 1; + stripes_per_sample = 1; + } + else + Kg_nblocks = 1; + int Kstripes = Kg_nblocks*stripes_per_sample; int nsubtasks = N*ngroups*Kstripes; - AutoBuffer inpbuf_all_, outputbuf_; - inputbufsize = alignSize(inputbufsize, VEC_ALIGN); - inpbuf_all_.allocate(inputbufsize + VEC_ALIGN); - float* inpbuf_all = alignPtr(inpbuf_all_.data(), (int)(VEC_ALIGN*sizeof(float))); + size_t stripesize = CONV_NR * ksize * Cg; + size_t taskbufsize = (stripesize + CONV_NR * K_BLOCK_SIZE) * MAX_STRIPES; + size_t totalbufsize = taskbufsize * ntasks; - outbufsize = alignSize(outbufsize, VEC_ALIGN); - outputbuf_.allocate(outbufsize + VEC_ALIGN); - float* output_buf = alignPtr(outputbuf_.data(), (int)(VEC_ALIGN*sizeof(float))); + AutoBuffer inpbuf_all_; + totalbufsize = alignSize(totalbufsize, VEC_ALIGN); + inpbuf_all_.allocate(totalbufsize + VEC_ALIGN); + float* inpbuf_all = alignPtr(inpbuf_all_.data(), (int)(VEC_ALIGN*sizeof(inpbuf_all_[0]))); - std::vector ofstab_(Hk*Wk*3, 0); - int* ofstab = ofstab_.data(); - int* yxtab = ofstab + Hk*Wk; + float* inp = input.ptr(); + float* out = output.ptr(); + float* fusedAddPtr0 = fusedAddMat.empty() ? 0 : fusedAddMat.ptr(); - for (int y = 0; y < Hk; y++) - for( int x = 0; x < Wk; x++) - { - int k = y*Wk + x; - int dy = y*dilation_y, dx = x*dilation_x; - yxtab[k*2] = dy; - yxtab[k*2+1] = dx; - ofstab[k] = dy*Wi + dx; - } - - if (ksize == 1) + parallel_for_(Range(0, ntasks), [&](const Range& r0) { + for (int task_id = r0.start; task_id < r0.end; task_id++) { - CV_Assert(pad_left == 0 && pad_right == 0 && pad_top == 0 && pad_bottom == 0); - CV_Assert(stride_x != 1 || stride_y != 1 || (H0 == Hi && W0 == Wi)); - } + float* inpbuf_task = &inpbuf_all[taskbufsize * task_id]; + float* cbuf_task = inpbuf_task + stripesize * MAX_STRIPES; - if (separatedLoop) - { - // For now this branch only handles batch size = 1. Maybe we could support batch size < 10 in the future. - // Pack Input data - parallel_for_(Range(0, ngroups * hw_task), [&](const Range& r0) + int ngs0 = (int)((size_t)nsubtasks * task_id / ntasks); + int ngs1 = (int)((size_t)nsubtasks * (task_id+1) / ntasks); + for (int subtask = ngs0; subtask < ngs1; ) { - for (int nhwi = r0.start; nhwi < r0.end; nhwi++) + int ng = subtask / Kstripes; + int kzyx0 = subtask - ng * Kstripes; + int kzyx1 = kzyx0 + (ngs1 - subtask); + int n = ng / ngroups, g = ng % ngroups; // ng - n * ngroups; + size_t inp_plane_ofs = (size_t)(n * ngroups + g) * Cg * inp_planesize; + kzyx1 = kzyx1 <= Kstripes ? kzyx1 : Kstripes; + subtask += kzyx1 - kzyx0; + int k0, k1; + int zyx0, zyx_limit, zyx_block_limit = 0; + + if (stripes_per_sample == 1 && conv->conv_type != _FX_CONV_TYPE_DEPTHWISE_REMAIN) { - int g = nhwi/hw_task; - int hw_i = nhwi % hw_task; - int hw0 = hw_i * FAST_CONV_NR; - float* inpbuf = inpbuf_all + g * hw_aligned * HkWkCg + hw0 * HkWkCg; - const float* inptr = inp + g * Cg * inp_planesize; - bool partial0 = hw0 + FAST_CONV_NR > out_planesize? true: false; - int slice_len = FAST_CONV_NR; - - if (partial0) - slice_len = out_planesize - hw0; - - packInput(inpbuf, inptr, yxtab, ksize, Cg, Hi, Wi, W0, pad_top, pad_left, stride_x, stride_y, - hw0, slice_len, fast_1x1, partial0, s1d1p0, s1d1); + k0 = kzyx0 * CONV_MR; + k1 = kzyx1 * CONV_MR; + k1 = k1 <= Kg ? k1 : Kg; + zyx0 = 0; + zyx_limit = (int)out_planesize; } - }); - - // Compute - parallel_for_(Range(0, ntasks), [&](const Range& r0) - { - for (int task_id = r0.start; task_id < r0.end; task_id++) + else { - float *cbuf = output_buf + task_id * taskbufsizeOutput; - int ngs0 = (int) ((size_t) nsubtasks * task_id / ntasks); - int ngs1 = (int) ((size_t) nsubtasks * (task_id + 1) / ntasks); - for (int subtask = ngs0; subtask < ngs1;) + k0 = 0; + k1 = Kg; + zyx0 = kzyx0 * CONV_NR; + zyx_limit = kzyx1 * CONV_NR; + zyx_limit = zyx_limit < out_planesize ? zyx_limit : (int)out_planesize; + } + + for (; zyx0 < zyx_limit; zyx0 = zyx_block_limit) + { + // step 1. extract part of input tensor and represent it in zigzag form + zyx_block_limit = zyx0 + CONV_NR * MAX_STRIPES; + zyx_block_limit = zyx_block_limit < zyx_limit ? zyx_block_limit : zyx_limit; + + int nstripes = (zyx_block_limit - zyx0 + CONV_NR - 1) / CONV_NR; + int zyx0_saved = zyx0; + + CV_Assert(nstripes <= MAX_STRIPES); + + for (int stripe = 0; zyx0 < zyx_block_limit; stripe++, zyx0 += CONV_NR) { - int ng = subtask / Kstripes; - int kyx0 = subtask - ng * Kstripes; - int kyx1 = kyx0 + (ngs1 - subtask); - int n = ng / ngroups, g = ng - n * ngroups; + float *inpbuf = inpbuf_task + stripe * stripesize; + float *inptr = inp + inp_plane_ofs; - CV_Assert(n <= 1); - - kyx1 = kyx1 <= Kstripes ? kyx1 : Kstripes; // Guarantee that maximum kyx1 is Kstripes. - subtask += kyx1 - kyx0; - - int k0 = kyx0 * FAST_CONV_MR; - int k1 = kyx1 * FAST_CONV_MR; - k1 = k1 <= Kg ? k1 : Kg; - - - for (int yx0 = 0; yx0 < out_planesize; yx0 += FAST_CONV_NR) + /* + 1. pack the data. Copy the HkxWk CONV_NR-wide slices from + each feature plane of the input tensor to the input buffer. + */ + if (fast_1x1) { - float* inpbuf_task = inpbuf_all + g * hw_aligned * HkWkCg + yx0 * HkWkCg; - int yx1 = yx0 + FAST_CONV_NR; - yx1 = yx1 <= out_planesize ? yx1 : out_planesize; - int slice_len = yx1 - yx0; - bool partial0 = slice_len < FAST_CONV_NR; - - int outstep0 = out_planesize; - size_t outofs = ((n * ngroups + g) * Kg + k0) * outstep0 + yx0; - float *outptr0 = out + outofs; - - matMulCompute(outptr0, inpbuf_task, cbuf, conv, HkWkCg, k0, k1, yx0, yx1, out_planesize, g, - Kg, Kg_aligned, partial0, activ, minval, maxval, ifMinMaxAct); + int slice_len = zyx_block_limit - zyx0; + bool partial = slice_len < CONV_NR; + // Superfast branch for 1x1 convolutions with sy=sx=1. + // in this case each feature plane can be safely treated + // as 1D array, and we just extract next portion + // of CONV_NR elements from each feature plane and + // put it together. + inptr += zyx0; + if (!partial) + { + // Make special branch where memcpy() is called with a constant buffer size. + // Compilers will likely unroll this loop properly. + for (int c = 0; c < Cg; c++, inptr += inp_planesize, inpbuf += CONV_NR) + memcpy(inpbuf, inptr, CONV_NR * sizeof(inpbuf[0])); + } + else + { + for (int c = 0; c < Cg; c++, inptr += inp_planesize, inpbuf += CONV_NR) + { + memcpy(inpbuf, inptr, slice_len * sizeof(inpbuf[0])); + memset(inpbuf + slice_len, 0, (CONV_NR - slice_len) * sizeof(inpbuf[0])); + } + } } - } - } - }); - } - else - { - parallel_for_(Range(0, ntasks), [&](const Range &r0) { - for (int task_id = r0.start; task_id < r0.end; task_id++) { - float *inpbuf_task = &inpbuf_all[taskbufsize * task_id]; - float *cbuf = output_buf + task_id * taskbufsizeOutput; - int ngs0 = (int) ((size_t) nsubtasks * task_id / ntasks); - int ngs1 = (int) ((size_t) nsubtasks * (task_id + 1) / ntasks); - - for (int subtask = ngs0; subtask < ngs1;) - { - int ng = subtask / Kstripes; - int kyx0 = subtask - ng * Kstripes; - int kyx1 = kyx0 + (ngs1 - subtask); - int n = ng / ngroups, g = ng - n * ngroups; - size_t inp_plane_ofs = (size_t) (n * ngroups + g) * Cg * inp_planesize; - kyx1 = kyx1 <= Kstripes ? kyx1 : Kstripes; // Guarantee that maximum kyx1 is Kstripes. - subtask += kyx1 - kyx0; - int k0, k1; - int yx0, yx_limit; - - if (stripes_per_sample == 1) + else if (conv->conv_type == _FX_CONV_TYPE_DEPTHWISE_REMAIN) { - k0 = kyx0 * FAST_CONV_MR; - k1 = kyx1 * FAST_CONV_MR; - k1 = k1 <= Kg ? k1 : Kg; - yx0 = 0; - yx_limit = out_planesize; + CV_Assert(Cg == 1); + const int HW0 = H0 * W0; + const int HWi = Hi * Wi; + int slice_len = std::min(zyx_block_limit - zyx0, CONV_NR); + + // here some non-continuous sub-row of the row will not be + // filled from the tensor; we need to make sure that the uncovered + // elements are explicitly set to 0's. the easiest way is to + // set all the elements to 0's before the loop. + memset(inpbuf, 0, stripesize*sizeof(inpbuf[0])); + + int z0 = zyx0 / HW0, yx0 = zyx0 - z0 * HW0; + int y0 = yx0 / W0, x0 = yx0 - y0 * W0; + + if (conv_dim == CONV_1D) + { + for (int slice_i = 0; slice_i < slice_len; y0++, x0=0) + { + int delta = std::min(slice_len - slice_i, W0 - x0); + int x1 = x0 + delta; + + int in_w = x0 * stride_w - pad_left; + float* inptrIn = inptr + in_w; + + int s0 = slice_i; + + for (; x0 < x1; x0++, s0++, inptrIn += stride_w, in_w += stride_w) + { + // Pack 8 + if (x0 + 8 <= x1 && 0 <= in_w && + in_w + stride_w*8 <= Wi - (Wk-1)*dilation_w) + { + packData8(inpbuf, inptrIn, in_w, x0, s0, ofstab, stride_w, ksize); + } + else if (x0 + 2 <= x1 && 0 <= in_w && + in_w + stride_w*2 <= Wi - (Wk-1)*dilation_w) + { + packData2(inpbuf, inptrIn, in_w, x0, s0, ofstab, stride_w, ksize); + } + else + { + int w0 = std::max(0, (-in_w + dilation_w-1)/dilation_w); + int w1 = std::min(Wk, (Wi - in_w + dilation_w-1)/dilation_w); + + float* inpbufC = inpbuf + s0; + float* inptrInC = inptrIn; + for (int w = w0; w < w1; w++) + { + int imgofs = w*dilation_w; + inpbufC[w*CONV_NR] = inptrInC[imgofs]; + } + } + } + slice_i += delta; + } + } + else if (conv_dim == CONV_2D) + { + for (int slice_i = 0; slice_i < slice_len; y0++, x0=0) + { + int delta = std::min(slice_len - slice_i, W0 - x0); + int x1 = x0 + delta; + + int in_h = y0 * stride_h - pad_top; + int in_w = x0 * stride_w - pad_left; + + float* inptrIn = inptr + in_h*Wi + in_w; + + bool ok_i = 0 <= in_h && in_h < Hi - (Hk-1)*dilation_h; + int h0 = std::max(0, (-in_h + dilation_h-1)/dilation_h); + int h1 = std::min(Hk, (Hi - in_h + dilation_h-1)/dilation_h); + + int s0 = slice_i; + for (; x0 < x1; x0++, s0++, inptrIn += stride_w, in_w += stride_w) + { + // Pack 8 + if (ok_i && x0 + 8 <= x1 && 0 <= in_w && + in_w + stride_w*8 <= Wi - (Wk-1)*dilation_w) + { + packData8(inpbuf, inptrIn, in_w, x0, s0, ofstab, stride_w, ksize); + } + else if (ok_i && x0 + 2 <= x1 && 0 <= in_w && + in_w + stride_w*2 <= Wi - (Wk-1)*dilation_w) + { + packData2(inpbuf, inptrIn, in_w, x0, s0, ofstab, stride_w, ksize); + } + else + { + int w0 = std::max(0, (-in_w + dilation_w-1)/dilation_w); + int w1 = std::min(Wk, (Wi - in_w + dilation_w-1)/dilation_w); + + float* inpbufC = inpbuf + s0; + float* inptrInC = inptrIn; + + for (int h = h0; h < h1; h++) + { + for (int w = w0; w < w1; w++) + { + int imgofs = h*(dilation_h*Wi) + w*dilation_w; + inpbufC[(h*Wk + w)*CONV_NR] = inptrInC[imgofs]; + } + } + } + } + slice_i += delta; + } + } + else if (conv_dim == CONV_3D) + { + for (int slice_i = 0; slice_i < slice_len; z0 += (y0+1)/H0, y0 = (y0+1)%H0, x0=0) + { + int delta = std::min(slice_len - slice_i, W0 - x0); + int x1 = x0 + delta; + + int in_d = z0 * stride_d - pad_front; + int in_h = y0 * stride_h - pad_top; + int in_w = x0 * stride_w - pad_left; + + float* inptrIn = inptr + in_d*HWi + in_h*Wi + in_w; + + int d0 = std::max(0, (-in_d + dilation_d - 1) / dilation_d); + int d1 = std::min(Dk, (Di - in_d + dilation_d - 1) / dilation_d); + + bool ok_i = 0 <= in_d && in_d < Di - (Dk-1)*dilation_d && + 0 <= in_h && in_h < Hi - (Hk-1)*dilation_h; + int h0 = std::max(0, (-in_h + dilation_h-1)/dilation_h); + int h1 = std::min(Hk, (Hi - in_h + dilation_h-1)/dilation_h); + + int s0 = slice_i; + for (; x0 < x1; x0++, s0++, inptrIn += stride_w, in_w += stride_w) + { + // Pack 8 + if (ok_i && x0 + 8 <= x1 && 0 <= in_w && + in_w + stride_w*8 <= Wi - (Wk-1)*dilation_w) + { + packData8(inpbuf, inptrIn, in_w, x0, s0, ofstab, stride_w, ksize); + } + else if (ok_i && x0 + 2 <= x1 && 0 <= in_w && + in_w + stride_w*2 <= Wi - (Wk-1)*dilation_w) + { + packData2(inpbuf, inptrIn, in_w, x0, s0, ofstab, stride_w, ksize); + } + else + { + int w0 = std::max(0, (-in_w + dilation_w-1)/dilation_w); + int w1 = std::min(Wk, (Wi - in_w + dilation_w-1)/dilation_w); + + float* inpbufC = inpbuf + s0; + float* inptrInC = inptrIn; + + for ( int d = d0; d < d1; d++) + { + for (int h = h0; h < h1; h++) + { + for (int w = w0; w < w1; w++) + { + int imgofs = d*dilation_d*HWi + h*(dilation_h*Wi) + w*dilation_w; + inpbufC[((d*Hk + h)*Wk + w)*CONV_NR] = inptrInC[imgofs]; + } + } + } + } + } + slice_i += delta; + } + } } else { - k0 = 0; - k1 = Kg; - yx0 = kyx0 * FAST_CONV_NR; - yx_limit = kyx1 * FAST_CONV_NR; - yx_limit = yx_limit < out_planesize ? yx_limit : out_planesize; + const int HW0 = H0 * W0; + const int HWi = Hi * Wi; + int z0_ = zyx0 / HW0, yx0 = zyx0 - z0_ * HW0; + int y0_ = yx0 / W0, x0_ = yx0 - y0_ * W0; + for (int k = 0; k < ksize; k++) + { + int dz = dhwTab[k * 3], dy = dhwTab[k * 3 + 1], dx = dhwTab[k * 3 + 2]; + int i = 0, z0 = z0_, y0 = y0_, x0 = x0_; + for (; i < CONV_NR;) + { + float *inpbuf_ki = inpbuf + k * CONV_NR * Cg + i; + int zi = z0 * stride_d + dz - pad_front; + int yi = y0 * stride_h + dy - pad_top; + int xi = x0 * stride_w + dx - pad_left; + + if ((unsigned) zi < (unsigned) Di && (unsigned) yi < (unsigned) Hi && + (unsigned) xi < (unsigned) Wi) + { + const float *inptr_ki = inptr + zi * HWi + yi * Wi + xi; + if (i + 8 <= CONV_NR && x0 + 8 <= W0 && xi + stride_w * 8 <= Wi) + { + if (stride_w == 1) + { + for (int c = 0; c < Cg; c++, inpbuf_ki += CONV_NR, inptr_ki += inp_planesize) + { + float t0 = inptr_ki[0], t1 = inptr_ki[1]; + float t2 = inptr_ki[2], t3 = inptr_ki[3]; + float t4 = inptr_ki[4], t5 = inptr_ki[5]; + float t6 = inptr_ki[6], t7 = inptr_ki[7]; + inpbuf_ki[0] = t0; + inpbuf_ki[1] = t1; + inpbuf_ki[2] = t2; + inpbuf_ki[3] = t3; + inpbuf_ki[4] = t4; + inpbuf_ki[5] = t5; + inpbuf_ki[6] = t6; + inpbuf_ki[7] = t7; + } + } + else if (stride_w == 2) + { + for (int c = 0; c < Cg; c++, inpbuf_ki += CONV_NR, inptr_ki += inp_planesize) + { + float t0 = inptr_ki[0], t1 = inptr_ki[2]; + float t2 = inptr_ki[4], t3 = inptr_ki[6]; + float t4 = inptr_ki[8], t5 = inptr_ki[10]; + float t6 = inptr_ki[12], t7 = inptr_ki[14]; + inpbuf_ki[0] = t0; + inpbuf_ki[1] = t1; + inpbuf_ki[2] = t2; + inpbuf_ki[3] = t3; + inpbuf_ki[4] = t4; + inpbuf_ki[5] = t5; + inpbuf_ki[6] = t6; + inpbuf_ki[7] = t7; + } + } + else + { + for (int c = 0; c < Cg; c++, inpbuf_ki += CONV_NR, inptr_ki += inp_planesize) + { + float t0 = inptr_ki[0], t1 = inptr_ki[stride_w]; + float t2 = inptr_ki[stride_w * 2], t3 = inptr_ki[stride_w * 3]; + float t4 = inptr_ki[stride_w * 4], t5 = inptr_ki[stride_w * 5]; + float t6 = inptr_ki[stride_w * 6], t7 = inptr_ki[stride_w * 7]; + inpbuf_ki[0] = t0; + inpbuf_ki[1] = t1; + inpbuf_ki[2] = t2; + inpbuf_ki[3] = t3; + inpbuf_ki[4] = t4; + inpbuf_ki[5] = t5; + inpbuf_ki[6] = t6; + inpbuf_ki[7] = t7; + } + } + i += 8; + x0 += 8; + } + else if (i + 4 <= CONV_NR && x0 + 4 <= W0 && xi + stride_w * 4 <= Wi) + { + if (stride_w == 1) + { + for (int c = 0; c < Cg; c++, inpbuf_ki += CONV_NR, inptr_ki += inp_planesize) + { + float t0 = inptr_ki[0], t1 = inptr_ki[1]; + float t2 = inptr_ki[2], t3 = inptr_ki[3]; + inpbuf_ki[0] = t0; + inpbuf_ki[1] = t1; + inpbuf_ki[2] = t2; + inpbuf_ki[3] = t3; + } + } + else + { + for (int c = 0; c < Cg; c++, inpbuf_ki += CONV_NR, inptr_ki += inp_planesize) + { + float t0 = inptr_ki[0], t1 = inptr_ki[stride_w]; + float t2 = inptr_ki[stride_w * 2], t3 = inptr_ki[stride_w * 3]; + inpbuf_ki[0] = t0; + inpbuf_ki[1] = t1; + inpbuf_ki[2] = t2; + inpbuf_ki[3] = t3; + } + } + i += 4; + x0 += 4; + } + else + { + for (int c = 0; c < Cg; c++, inpbuf_ki += CONV_NR, inptr_ki += inp_planesize) + *inpbuf_ki = *inptr_ki; + i++; + x0++; + } + } + else + { + for (int c = 0; c < Cg; c++, inpbuf_ki += CONV_NR) + inpbuf_ki[0] = 0.f; + i++; + x0++; + } + + int mask = x0 >= W0; + y0 += mask; + x0 &= mask - 1; + + mask = y0 >= H0; + z0 += mask; + y0 &= mask - 1; + } + } + } + } + + zyx0 = zyx0_saved; + + // spacial branch for depth-wise convolution implemented using generic convolution. + // In this case, CONV_MR is 1, and CONV_NR is the same. + if (conv->conv_type == _FX_CONV_TYPE_DEPTHWISE_REMAIN) + { + size_t outofs = (n * ngroups + g) * out_planesize + zyx0; + float *cptr0 = cbuf_task; + float *weights = conv->weightsBufPtr + g * padded_ksize; + int out_width = zyx_block_limit - zyx0; + float *outptr = out + outofs; + const float biasVal = *(conv->biasBuf.data() + g); + for (int stripe = 0; stripe < nstripes; stripe++) + { + const float *inptr = inpbuf_task + stripe * stripesize; + const int outLen = std::min(out_width - stripe * CONV_NR, CONV_NR); + bool ifBuffer = outLen < CONV_NR; + float *cptr = outptr + stripe * CONV_NR; + if (ifBuffer) + { + memcpy(cptr0, cptr, outLen * sizeof(cptr[0])); + cptr = cptr0; + } +#if CV_TRY_AVX2 + if (conv->useAVX2 && outLen > CONV_NR/3) + opt_AVX2::convBlockMR1(DkHkWkCg, weights, inptr, cptr, biasVal, fusedAdd, minval, maxval, ifMinMaxAct); + else +#endif + convBlockMR1(DkHkWkCg, weights, inptr, cptr, biasVal, fusedAdd, minval, maxval, ifMinMaxAct, outLen); + + if (ifBuffer) + { + memcpy(outptr + stripe * CONV_NR, cptr, outLen * sizeof(cptr[0])); + } + } + if (activ) + activ->forwardSlice(outptr, outptr, out_width, out_planesize, g, g + 1); + continue; + } + + float *weights = conv->weightsBufPtr + g * Kg_aligned * DkHkWkCg; + const float *biasptr = conv->biasBuf.data() + Kg * g; + int ldc = nstripes * CONV_NR; + + // 2. do convolution, compute Kg x (zyx_block_limit - zyx0) part of the output tensor + int out_width = zyx_block_limit - zyx0; + for (int k0_block = k0; k0_block < k1; k0_block += K_BLOCK_SIZE) + { + int k1_block = k0_block + K_BLOCK_SIZE < k1 ? k0_block + K_BLOCK_SIZE : k1; + for (int c0 = 0; c0 < DkHkWkCg; c0 += C_BLOCK_SIZE) + { + int c1 = c0 + C_BLOCK_SIZE < DkHkWkCg ? c0 + C_BLOCK_SIZE : DkHkWkCg; + for (int stripe = 0; stripe < nstripes; stripe++) + { + const int outLen = std::min(out_width - stripe * CONV_NR, CONV_NR); + +#if CV_TRY_AVX2 || CV_TRY_NEON + // The possible CONV_NR is 28, 24, 12, so the possible CONV_NR/3 is 9, 8, 4. + bool runOpt = outLen > std::min(8, CONV_NR/3); +#endif + float *wptr = weights + k0_block * DkHkWkCg + c0 * CONV_MR; + const float *inptr = inpbuf_task + stripe * stripesize + c0 * CONV_NR; + float *cptr = cbuf_task + stripe * CONV_NR; + for (int k = k0_block; k < k1_block; k += CONV_MR, + wptr += DkHkWkCg * CONV_MR, cptr += CONV_MR * ldc) + { +#if CV_TRY_AVX2 + if (conv->useAVX2 && runOpt) + opt_AVX2::convBlock_AVX2(c1 - c0, wptr, inptr, cptr, ldc, c0 == 0); + else +#endif +#if CV_TRY_NEON + if (conv->useNEON && runOpt) + opt_NEON::convBlock_NEON(c1 - c0, wptr, inptr, cptr, ldc, c0 == 0); + else +#endif + // The possible outLen range is 24 or 8~1. + convBlock(c1 - c0, wptr, inptr, cptr, ldc, c0 == 0, outLen); + } + } } - for (; yx0 < yx_limit; yx0 += FAST_CONV_NR) - { - float *inpbuf = inpbuf_task; - const float *inptr = inp + inp_plane_ofs; - int yx1 = yx0 + FAST_CONV_NR; - yx1 = yx1 <= yx_limit ? yx1 : yx_limit; - int slice_len = yx1 - yx0; - bool partial0 = slice_len < FAST_CONV_NR; - packInput(inpbuf, inptr, yxtab, ksize, Cg, Hi, Wi, W0, pad_top, pad_left, stride_x, stride_y, - yx0, slice_len, fast_1x1, partial0, s1d1p0, s1d1); + size_t outofs = ((n * ngroups + g) * Kg + k0_block) * out_planesize + zyx0; + const float *cptr = cbuf_task; - // 2. do convolution, compute Kg x (yx1 - yx0) part of the output tensor - int outstep0 = out_planesize; - size_t outofs = ((n * ngroups + g) * Kg + k0) * outstep0 + yx0; - float *outptr0 = out + outofs; + float *outptr = out + outofs; + const float *pbptr = fusedAddPtr0 ? fusedAddPtr0 + outofs : 0; - matMulCompute(outptr0, inpbuf_task, cbuf, conv, HkWkCg, k0, k1, yx0, yx1, out_planesize, g, - Kg, Kg_aligned, partial0, activ, minval, maxval, ifMinMaxAct); + for (int k = k0_block; k < k1_block; k++, + cptr += ldc, outptr += out_planesize, + pbptr += (pbptr ? out_planesize : 0)) { + float biasval = biasptr[k]; + int j = 0; +#if CV_SIMD128 + v_float32x4 vbias = v_setall_f32(biasval); + v_float32x4 vmax = v_setall_f32(maxval); + v_float32x4 vmin = v_setall_f32(minval); + + if (pbptr) + { + for (; j + 7 < out_width; j += 8) + { + v_float32x4 v0 = v_load(cptr + j) + vbias; + v_float32x4 v1 = v_load(cptr + j + 4) + vbias; + + v0 += v_load(pbptr + j); + v1 += v_load(pbptr + j + 4); + + if (ifMinMaxAct) + { + v0 = v_min(v_max(v0, vmin), vmax); + v1 = v_min(v_max(v1, vmin), vmax); + } + + v_store(outptr + j, v0); + v_store(outptr + j + 4, v1); + } + } + else + { + for (; j + 7 < out_width; j += 8) + { + v_float32x4 v0 = v_load(cptr + j) + vbias; + v_float32x4 v1 = v_load(cptr + j + 4) + vbias; + + if (ifMinMaxAct) + { + v0 = v_min(v_max(v0, vmin), vmax); + v1 = v_min(v_max(v1, vmin), vmax); + } + + v_store(outptr + j, v0); + v_store(outptr + j + 4, v1); + } + } +#endif + if (pbptr) { + for (; j < out_width; j++) { + float v = cptr[j] + biasval; + v += pbptr[j]; + if (ifMinMaxAct) + v = std::min(std::max(v, minval), maxval); + outptr[j] = v; + } + } else { + for (; j < out_width; j++) { + float v = cptr[j] + biasval; + + if (ifMinMaxAct) + v = std::min(std::max(v, minval), maxval); + outptr[j] = v; + } + } + + if (activ) + activ->forwardSlice(outptr, outptr, out_width, out_planesize, Kg * g + k, Kg * g + k + 1); } } } - }); + } } + }); } - -}} // namespace cv::dnn \ No newline at end of file +}} // namespace cv::dnn diff --git a/modules/dnn/src/layers/fast_convolution/fast_convolution.hpp b/modules/dnn/src/layers/fast_convolution/fast_convolution.hpp index c993781f5f..895ad562bb 100644 --- a/modules/dnn/src/layers/fast_convolution/fast_convolution.hpp +++ b/modules/dnn/src/layers/fast_convolution/fast_convolution.hpp @@ -7,83 +7,121 @@ #include "opencv2/core/hal/intrin.hpp" -#ifndef FAST_CONV_PRAM -#define FAST_CONV_PRAM +#ifndef CONV_PRAM +#define CONV_PRAM #if CV_NEON && CV_NEON_AARCH64 // 32 registers. -#define FAST_CONV_MR 4 -#define FAST_CONV_NR 28 -enum { FAST_VEC_NLANES=4 }; +#define CONV_MR 4 +#define CONV_NR 28 #elif CV_NEON // 16 registers. -#define FAST_CONV_MR 4 -#define FAST_CONV_NR 12 -enum { FAST_VEC_NLANES=4 }; +#define CONV_MR 4 +#define CONV_NR 12 #else // SIMD 128, AVX or AVX2 -#define FAST_CONV_MR 4 -#define FAST_CONV_NR 24 -enum { FAST_VEC_NLANES=4 }; +#define CONV_MR 4 +#define CONV_NR 24 #endif + +// Winograd Params +enum { + _FX_WINO_STEP=6, + _FX_WINO_KSIZE=3, + _FX_WINO_SIZE=_FX_WINO_STEP+_FX_WINO_KSIZE-1, + _FX_WINO_AREA=_FX_WINO_SIZE*_FX_WINO_SIZE, + + _FX_WINO_KBLOCK = 4, +#if (CV_NEON && CV_NEON_AARCH64) || CV_TRY_AVX2 + _FX_WINO_IBLOCK = 6, +#else + _FX_WINO_IBLOCK = 3, +#endif + +#if CV_TRY_AVX2 + _FX_WINO_ATOM_F32 = 8, +#else + _FX_WINO_ATOM_F32 = 4, +#endif + + _FX_WINO_NATOMS_F32 = _FX_WINO_AREA / _FX_WINO_ATOM_F32, // for AVX2, it is 8, otherwise, it's 16. +}; +enum { _FX_CONV_TYPE_GENERIC=0, _FX_CONV_TYPE_DEPTHWISE=1, _FX_CONV_TYPE_WINOGRAD3X3=2, _FX_CONV_TYPE_DEPTHWISE_REMAIN=3 }; +enum { CONV_1D = 0, CONV_2D = 1, CONV_3D = 2 }; #endif namespace cv { namespace dnn { -struct FastConv2d +struct FastConv { int ngroups; - int K, C, Hk, Wk; - int stride_y, stride_x; - int dilation_y, dilation_x; - int pad_top, pad_bottom, pad_left, pad_right; - - std::vector weightsBuf; // For generic Conv 2D - std::vector weightsWino63Buf; // For Winograd F(6x6, 3x3). + int K, C, Hk, Wk, Dk; + int stride_h, stride_w, stride_d; + int dilation_h, dilation_w, dilation_d; + int pad_top, pad_bottom, pad_left, pad_right, pad_front, pad_behind; + std::vector weightsBuf; // For generic Conv 2D + float* weightsBufPtr; + std::vector weightsWinoBuf; // For Winograd F(6x6, 3x3). + float* weightsWinoBufPtr; std::vector biasBuf; - bool ifWinograd63 = false; - bool useAVX2 = checkHardwareSupport(CPU_AVX2); + int conv_type; + int conv_dim; // Flag for conv1d, conv2d, or conv3d. +#if CV_SIMD128 + bool useSIMD128 = true; +#else + bool useSIMD128 = false; +#endif + +#if CV_NEON bool useNEON = checkHardwareSupport(CPU_NEON); +#else + bool useNEON = false; +#endif + + bool useAVX = checkHardwareSupport(CPU_AVX); + bool useAVX2 = checkHardwareSupport(CPU_AVX2); + bool useRVV = checkHardwareSupport(CPU_RVV); }; -// return a FastConv2d instance. -Ptr initFastConv2d( +// return a FastConv instance. +Ptr initFastConv( + InputArray weightsMat, + float* srcBias, int ngroups, - int K, int C, int Hk, int Wk, - int stride_x, int stride_y, - int dilation_x, int dilation_y, + int K, int C, + const std::vector& kernel_size, + const std::vector& strides, + const std::vector& dilations, const std::vector& pads_begin, const std::vector& pads_end, - float* srcWeights, - float* srcBias); + int conv_dim, + bool useWinograd); // It contains different computing branches, like winograd, 1x1 conv. -void runFastConv2d(InputArray _input, OutputArray _output, - const Ptr& conv, int ntasks, const Ptr& actLayer); +void runFastConv(InputArray _input, OutputArray _output, const Ptr& conv, int ntasks, + const Ptr& actLayer, const std::vector& reluslope, bool fusedAdd); -void runDepthwise(InputArray _input, OutputArray _output, const Ptr& conv, float minval, float maxval, - ActivationLayer* activ, bool ifMinMaxAct); +void runDepthwise(InputArray _input, OutputArray _output, const Ptr& conv, ActivationLayer* activ, + const std::vector& reluslope); -// winograd init -void initWinograd63(Ptr& conv, float* src_weight, int K, int C); - -int runWinograd63(InputArray _input, OutputArray _output, const Ptr& conv, int ntasks, +int runWinograd63(InputArray _input, InputArray _fusedAddMat, OutputArray _output, const Ptr& conv, int ntasks, float minval, float maxval, ActivationLayer* activ, bool ifMinMaxAct); -} // namespace dnn - namespace opt_AVX2 { #if CV_TRY_AVX2 -void convBlock_AVX2(int k, const float *a, const float *b, - float *c, int ldc, const float *bias, - float minval, float maxval, bool ifActiv); +void convBlock_AVX2(int np, const float* a, const float* b, float* c, int ldc, bool init_c); + +void convBlockMR1(int np, const float* a, const float* b, float *c, const float bias, bool init_c, const float minval, + const float maxval, bool ifMinMaxAct); + +void _fx_winograd_accum_f32(const float* inwptr, const float* wptr, float* outbuf, int Cg, int iblock); +void _fx_winograd_BtXB_8x8_f32(const float* inptr, int inpstep, float* outptr, int Cg); +void _fx_winograd_AtXA_8x8_f32(const float* inptr, int inpstep, float* bpptr, int bpstep, float* outptr, int outstep, + float bias, float minval, float maxval, bool ifMinMaxAct); -void depthWiseBlock_AVX2(const float *inptr, float *outptr, const float *weights, float biasval, int *ofstab, int *yxtab, - float minval, float maxval, int Hi, int Wi, int H0, int W0, int ksize, int pad_top, int pad_left, - int dilation_y, int stride_x, int stride_y, int inner_xleft, int inner_xright, int inner_ytop, - int inner_ybottom, bool ifMinMaxAct, bool useSIMD, bool is3x3); #endif } // namespace opt_AVX2 +} // namespace dnn } // namespace cv #endif //OPENCV_FAST_CONVOLUTION_HPP diff --git a/modules/dnn/src/layers/fast_convolution/fast_convolution.simd.hpp b/modules/dnn/src/layers/fast_convolution/fast_convolution.simd.hpp index 94b08141e9..e146c0974e 100644 --- a/modules/dnn/src/layers/fast_convolution/fast_convolution.simd.hpp +++ b/modules/dnn/src/layers/fast_convolution/fast_convolution.simd.hpp @@ -11,142 +11,383 @@ namespace cv { namespace dnn { -void convBlock(int k, const float *a, const float *b, - float *c, int ldc, const float *bias, - float minval, float maxval, bool ifActiv) +static void convBlockMR1NoSIMD(int np, const float* a, const float* b, float *c, const float bias, bool init_c, + const float minval, const float maxval, bool ifMinMaxAct, const int outLen) +{ + std::vector cbuffer(outLen, 0); + float* cbuf = cbuffer.data(); + for( int p = 0; p < np; p++ ) + { + float ai = a[p]; + for( int j = 0; j < outLen; j++ ) + cbuf[j] += b[CONV_NR*p + j] * ai; + } + + if (init_c) + { + for(int j = 0; j < outLen; j++) + { + c[j] += cbuf[j] + bias; + if (ifMinMaxAct) + c[j] = std::min(std::max(c[j], minval), maxval); + } + } + else + { + for(int j = 0; j < outLen; j++) + { + c[j] = cbuf[j] + bias; + if (ifMinMaxAct) + c[j] = std::min(std::max(c[j], minval), maxval); + } + } +} + +void convBlockMR1(int np, const float* a, const float* b, float *c, const float bias, bool init_c, + const float minval, const float maxval, bool ifMinMaxAct, const int outLen) { #if CV_SIMD128 -#if FAST_CONV_MR == 4 && FAST_CONV_NR == 24 + // The outLen represents the valid output value in CONV_NR length. + // When outLen is very small, we use the no-SIMD branch. + const int CONV_NRby3 = CONV_NR/3; + if (outLen > CONV_NRby3) { - v_float32x4 c0 = v_setall_f32(bias[0]), c1 = c0, c2 = c0, c3 = c0, c4 = c0, c5 = c0; - v_float32x4 c6 = v_setall_f32(bias[1]), c7 = c6, c8 = c6, c9 = c6, c10 = c6, c11 = c6; - v_float32x4 c12 = v_setall_f32(bias[2]), c13 = c12, c14 = c12, c15 = c12, c16 = c12, c17 = c12; - v_float32x4 c18 = v_setall_f32(bias[3]), c19 = c18, c20 = c18, c21 = c18, c22 = c18, c23 = c18; - - for (int p = 0; p < k; p++, a += FAST_CONV_MR, b += FAST_CONV_NR) + v_float32x4 c0 = v_setall_f32(bias), c1 = c0, c2 = c0; // CONV_NR == 12 +#if CONV_NR == 28 || CONV_NR == 24 + v_float32x4 c3 = c0, c4 = c0, c5 = c0; +#endif +#if CONV_NR == 28 + v_float32x4 c6 = c0; +#endif + for (int p = 0; p < np; p++, a++, b += CONV_NR) { v_float32x4 a0 = v_setall_f32(a[0]); v_float32x4 b0 = v_load(b), b1 = v_load(b + 4), b2 = v_load(b + 8); +#if CONV_NR == 28 || CONV_NR == 24 v_float32x4 b3 = v_load(b + 12), b4 = v_load(b + 16), b5 = v_load(b + 20); +#endif +#if CONV_NR == 28 + v_float32x4 b6 = v_load(b + 24); +#endif c0 = v_fma(b0, a0, c0); c1 = v_fma(b1, a0, c1); c2 = v_fma(b2, a0, c2); +#if CONV_NR == 28 || CONV_NR == 24 c3 = v_fma(b3, a0, c3); c4 = v_fma(b4, a0, c4); c5 = v_fma(b5, a0, c5); - - a0 = v_setall_f32(a[1]); - c6 = v_fma(b0, a0, c6); - c7 = v_fma(b1, a0, c7); - c8 = v_fma(b2, a0, c8); - c9 = v_fma(b3, a0, c9); - c10 = v_fma(b4, a0, c10); - c11 = v_fma(b5, a0, c11); - - a0 = v_setall_f32(a[2]); - c12 = v_fma(b0, a0, c12); - c13 = v_fma(b1, a0, c13); - c14 = v_fma(b2, a0, c14); - c15 = v_fma(b3, a0, c15); - c16 = v_fma(b4, a0, c16); - c17 = v_fma(b5, a0, c17); - - a0 = v_setall_f32(a[3]); - c18 = v_fma(b0, a0, c18); - c19 = v_fma(b1, a0, c19); - c20 = v_fma(b2, a0, c20); - c21 = v_fma(b3, a0, c21); - c22 = v_fma(b4, a0, c22); - c23 = v_fma(b5, a0, c23); +#endif +#if CONV_NR == 28 + c6 = v_fma(b6, a0, c6); +#endif } - if (ifActiv) { - v_float32x4 vmin = v_setall_f32(minval), vmax = v_setall_f32(maxval); - c0 = v_min(v_max(c0, vmin), vmax); - c1 = v_min(v_max(c1, vmin), vmax); - c2 = v_min(v_max(c2, vmin), vmax); - c3 = v_min(v_max(c3, vmin), vmax); - c4 = v_min(v_max(c4, vmin), vmax); - c5 = v_min(v_max(c5, vmin), vmax); + if (init_c) + { + c0 += v_load(c); + c1 += v_load(c + 4); + c2 += v_load(c + 8); +#if CONV_NR == 28 || CONV_NR == 24 + c3 += v_load(c + 12); + c4 += v_load(c + 16); + c5 += v_load(c + 20); +#endif +#if CONV_NR == 28 + c6 += v_load(c + 24); +#endif + } + + if (ifMinMaxAct) + { + v_float32x4 vmax = v_setall_f32(maxval), vmin = v_setall_f32(minval); + c0 = v_min(v_max(c0, vmin), vmax); + c1 = v_min(v_max(c1, vmin), vmax); + c2 = v_min(v_max(c2, vmin), vmax); +#if CONV_NR == 28 || CONV_NR == 24 + c3 = v_min(v_max(c3, vmin), vmax); + c4 = v_min(v_max(c4, vmin), vmax); + c5 = v_min(v_max(c5, vmin), vmax); +#endif +#if CONV_NR == 28 c6 = v_min(v_max(c6, vmin), vmax); - c7 = v_min(v_max(c7, vmin), vmax); - c8 = v_min(v_max(c8, vmin), vmax); - c9 = v_min(v_max(c9, vmin), vmax); - c10 = v_min(v_max(c10, vmin), vmax); - c11 = v_min(v_max(c11, vmin), vmax); - c12 = v_min(v_max(c12, vmin), vmax); - c13 = v_min(v_max(c13, vmin), vmax); - c14 = v_min(v_max(c14, vmin), vmax); - c15 = v_min(v_max(c15, vmin), vmax); - c16 = v_min(v_max(c16, vmin), vmax); - c17 = v_min(v_max(c17, vmin), vmax); - c18 = v_min(v_max(c18, vmin), vmax); - c19 = v_min(v_max(c19, vmin), vmax); - c20 = v_min(v_max(c20, vmin), vmax); - c21 = v_min(v_max(c21, vmin), vmax); - c22 = v_min(v_max(c22, vmin), vmax); - c23 = v_min(v_max(c23, vmin), vmax); +#endif } + v_store(c, c0); v_store(c + 4, c1); v_store(c + 8, c2); +#if CONV_NR == 28 || CONV_NR == 24 v_store(c + 12, c3); v_store(c + 16, c4); v_store(c + 20, c5); +#endif +#if CONV_NR == 28 + v_store(c + 24, c6); +#endif + } + else + convBlockMR1NoSIMD(np, a, b, c, bias, init_c, minval, maxval, ifMinMaxAct, outLen); +#else + convBlockMR1NoSIMD(np, a, b, c, bias, init_c, minval, maxval, ifMinMaxAct, outLen); +#endif +} - v_store(c + ldc, c6); - v_store(c + ldc + 4, c7); - v_store(c + ldc + 8, c8); - v_store(c + ldc + 12, c9); - v_store(c + ldc + 16, c10); - v_store(c + ldc + 20, c11); +#if CV_SIMD128 +#if CONV_MR == 4 && CONV_NR == 24 +static void convBlock4x24(int np, const float* a, const float* b, float* c, int ldc, bool init_c) +{ + v_float32x4 c0 = v_setzero_f32(), c1 = c0, c2 = c0, c3 = c0, c4 = c0, c5 = c0; + v_float32x4 c6 = v_setzero_f32(), c7 = c6, c8 = c6, c9 = c6, c10 = c6, c11 = c6; + v_float32x4 c12 = v_setzero_f32(), c13 = c12, c14 = c12, c15 = c12, c16 = c12, c17 = c12; + v_float32x4 c18 = v_setzero_f32(), c19 = c18, c20 = c18, c21 = c18, c22 = c18, c23 = c18; - v_store(c + ldc * 2, c12); - v_store(c + ldc * 2 + 4, c13); - v_store(c + ldc * 2 + 8, c14); - v_store(c + ldc * 2 + 12, c15); - v_store(c + ldc * 2 + 16, c16); - v_store(c + ldc * 2 + 20, c17); + for (int p = 0; p < np; p++, a += CONV_MR, b += CONV_NR) + { + v_float32x4 a0 = v_setall_f32(a[0]); + v_float32x4 b0 = v_load(b), b1 = v_load(b + 4), b2 = v_load(b + 8); + v_float32x4 b3 = v_load(b + 12), b4 = v_load(b + 16), b5 = v_load(b + 20); - v_store(c + ldc * 3, c18); - v_store(c + ldc * 3 + 4, c19); - v_store(c + ldc * 3 + 8, c20); - v_store(c + ldc * 3 + 12, c21); - v_store(c + ldc * 3 + 16, c22); - v_store(c + ldc * 3 + 20, c23); + c0 = v_fma(b0, a0, c0); + c1 = v_fma(b1, a0, c1); + c2 = v_fma(b2, a0, c2); + c3 = v_fma(b3, a0, c3); + c4 = v_fma(b4, a0, c4); + c5 = v_fma(b5, a0, c5); + + a0 = v_setall_f32(a[1]); + c6 = v_fma(b0, a0, c6); + c7 = v_fma(b1, a0, c7); + c8 = v_fma(b2, a0, c8); + c9 = v_fma(b3, a0, c9); + c10 = v_fma(b4, a0, c10); + c11 = v_fma(b5, a0, c11); + + a0 = v_setall_f32(a[2]); + c12 = v_fma(b0, a0, c12); + c13 = v_fma(b1, a0, c13); + c14 = v_fma(b2, a0, c14); + c15 = v_fma(b3, a0, c15); + c16 = v_fma(b4, a0, c16); + c17 = v_fma(b5, a0, c17); + + a0 = v_setall_f32(a[3]); + c18 = v_fma(b0, a0, c18); + c19 = v_fma(b1, a0, c19); + c20 = v_fma(b2, a0, c20); + c21 = v_fma(b3, a0, c21); + c22 = v_fma(b4, a0, c22); + c23 = v_fma(b5, a0, c23); + } + + if (!init_c) + { + c0 += v_load(c); + c1 += v_load(c + 4); + c2 += v_load(c + 8); + c3 += v_load(c + 12); + c4 += v_load(c + 16); + c5 += v_load(c + 20); + + c6 += v_load(c + ldc); + c7 += v_load(c + ldc + 4); + c8 += v_load(c + ldc + 8); + c9 += v_load(c + ldc + 12); + c10 += v_load(c + ldc + 16); + c11 += v_load(c + ldc + 20); + + c12 += v_load(c + ldc*2); + c13 += v_load(c + ldc*2 + 4); + c14 += v_load(c + ldc*2 + 8); + c15 += v_load(c + ldc*2 + 12); + c16 += v_load(c + ldc*2 + 16); + c17 += v_load(c + ldc*2 + 20); + + c18 += v_load(c + ldc*3); + c19 += v_load(c + ldc*3 + 4); + c20 += v_load(c + ldc*3 + 8); + c21 += v_load(c + ldc*3 + 12); + c22 += v_load(c + ldc*3 + 16); + c23 += v_load(c + ldc*3 + 20); + } + + v_store(c, c0); + v_store(c + 4, c1); + v_store(c + 8, c2); + v_store(c + 12, c3); + v_store(c + 16, c4); + v_store(c + 20, c5); + + v_store(c + ldc, c6); + v_store(c + ldc + 4, c7); + v_store(c + ldc + 8, c8); + v_store(c + ldc + 12, c9); + v_store(c + ldc + 16, c10); + v_store(c + ldc + 20, c11); + + v_store(c + ldc * 2, c12); + v_store(c + ldc * 2 + 4, c13); + v_store(c + ldc * 2 + 8, c14); + v_store(c + ldc * 2 + 12, c15); + v_store(c + ldc * 2 + 16, c16); + v_store(c + ldc * 2 + 20, c17); + + v_store(c + ldc * 3, c18); + v_store(c + ldc * 3 + 4, c19); + v_store(c + ldc * 3 + 8, c20); + v_store(c + ldc * 3 + 12, c21); + v_store(c + ldc * 3 + 16, c22); + v_store(c + ldc * 3 + 20, c23); +} +#endif + +static void convBlock4x8(int np, const float* a, const float* b, float* c, int ldc, bool init_c) +{ + CV_Assert(CONV_NR >= 4); + v_float32x4 c0 = v_setzero_f32(), c1 = c0, c2 = c0, c3 = c0; + v_float32x4 c4 = c0, c5 = c0, c6 = c0, c7 = c0; + + for (int p = 0; p < np; p++, a += CONV_MR, b += CONV_NR) + { + v_float32x4 a0 = v_setall_f32(a[0]); + v_float32x4 a1 = v_setall_f32(a[1]); + v_float32x4 a2 = v_setall_f32(a[2]); + v_float32x4 a3 = v_setall_f32(a[3]); + + v_float32x4 b0 = v_load(b), b1 = v_load(b + 4); + + c0 = v_fma(b0, a0, c0); + c1 = v_fma(b1, a0, c1); + + c2 = v_fma(b0, a1, c2); + c3 = v_fma(b1, a1, c3); + + c4 = v_fma(b0, a2, c4); + c5 = v_fma(b1, a2, c5); + + c6 = v_fma(b0, a3, c6); + c7 = v_fma(b1, a3, c7); + } + + if (!init_c) + { + c0 += v_load(c); + c1 += v_load(c + 4); + + c2 += v_load(c + ldc); + c3 += v_load(c + ldc + 4); + + c4 += v_load(c + ldc*2); + c5 += v_load(c + ldc*2 + 4); + + c6 += v_load(c + ldc*3); + c7 += v_load(c + ldc*3 + 4); + } + + v_store(c, c0); + v_store(c + 4, c1); + v_store(c + ldc, c2); + v_store(c + ldc + 4, c3); + v_store(c + ldc * 2, c4); + v_store(c + ldc * 2 + 4, c5); + v_store(c + ldc * 3, c6); + v_store(c + ldc * 3 + 4, c7); +} + +static void convBlock4x4(int np, const float* a, const float* b, float* c, int ldc, bool init_c) +{ + CV_Assert(CONV_NR >= 4); + v_float32x4 c0 = v_setzero_f32(), c1 = c0, c2 = c0, c3 = c0; + + for (int p = 0; p < np; p++, a += CONV_MR, b += CONV_NR) + { + v_float32x4 a0 = v_setall_f32(a[0]); + v_float32x4 a1 = v_setall_f32(a[1]); + v_float32x4 a2 = v_setall_f32(a[2]); + v_float32x4 a3 = v_setall_f32(a[3]); + + v_float32x4 b0 = v_load(b); + + c0 = v_fma(b0, a0, c0); + c1 = v_fma(b0, a1, c1); + c2 = v_fma(b0, a2, c2); + c3 = v_fma(b0, a3, c3); + } + + if (!init_c) + { + c0 += v_load(c); + c1 += v_load(c + ldc); + c2 += v_load(c + ldc*2); + c3 += v_load(c + ldc*3); + } + + v_store(c, c0); + v_store(c + ldc, c1); + v_store(c + ldc * 2, c2); + v_store(c + ldc * 3, c3); +} +#endif + +static void convBlockNoSIMD(int np, const float* a, const float* b, float* c, int ldc, bool init_c, const int outLen) +{ + std::vector cbuffer(CONV_MR * outLen, 0); + float* cbuf = cbuffer.data(); + for( int p = 0; p < np; p++ ) + { + for( int i = 0; i < CONV_MR; i++ ) + { + float ai = a[CONV_MR*p + i]; + for( int j = 0; j < outLen; j++ ) + cbuf[i * outLen+j] += b[CONV_NR*p + j] * ai; + } + } + + if (!init_c) + { + for(int i = 0; i < CONV_MR; i++) + { + for(int j = 0; j < outLen; j++) + c[i*ldc + j] += cbuf[i*outLen + j]; + } + } + else + { + for(int i = 0; i < CONV_MR; i++) + { + for(int j = 0; j < outLen; j++) + c[i*ldc + j] = cbuf[i*outLen + j]; + } + } +} + +void convBlock(int np, const float* a, const float* b, float* c, int ldc, bool init_c, const int outLen) +{ + // The possible outLen range is [24, 8~1]. +#if CV_SIMD128 +#if CONV_MR == 4 && CONV_NR == 24 + const int CONV_NRby3 = CONV_NR/3; + if (outLen > CONV_NRby3) + { + convBlock4x24(np, a, b, c, ldc, init_c); + return; } #endif + + if (outLen <= 8 && outLen > 4) + { + convBlock4x8(np, a, b, c, ldc, init_c); + return; + } + + if (outLen <= 4 && outLen > 1) + { + convBlock4x4(np, a, b, c, ldc, init_c); + return; + } + convBlockNoSIMD(np, a, b, c, ldc, init_c, outLen); #else - for (int i = 0; i < FAST_CONV_MR; i++) - { - float beta = bias[i]; - for (int j = 0; j < FAST_CONV_NR; j++) - c[i*ldc + j] = beta; - } - for (int p = 0; p < k; p++) - { - for (int i = 0; i < FAST_CONV_MR; i++) - { - float alpha = a[FAST_CONV_MR*p + i]; - for (int j = 0; j < FAST_CONV_NR; j++) - { - c[i*ldc+j] += b[FAST_CONV_NR*p + j]*alpha; - } - } - } - if (ifActiv) - { - for (int i = 0; i < FAST_CONV_MR; i++) - { - for (int j = 0; j < FAST_CONV_NR; j++) - { - float v = c[i*ldc + j]; - v = std::min(std::max(v, minval), maxval); - c[i*ldc + j] = v; - } - } - } + convBlockNoSIMD(np, a, b, c, ldc, init_c, outLen); #endif } } // namespace dnn @@ -154,142 +395,122 @@ void convBlock(int k, const float *a, const float *b, namespace opt_NEON { #if CV_TRY_NEON -void convBlock_NEON(int k, const float *a, const float *b, - float *c, int ldc, const float *bias, - float minval, float maxval, bool ifActiv) +void convBlock_NEON(int np, const float* a, const float* b, float* c, int ldc, bool init_c) { -#if CV_NEON_AARCH64 && FAST_CONV_MR == 4 && FAST_CONV_NR == 28 // AARCH64 +#if CONV_MR == 4 && CONV_NR == 28 // AARCH64 { - float32x4_t c0 = vdupq_n_f32(bias[0]), c1 = c0, c2 = c0, c3 = c0, c4 = c0, c5 = c0, c24 = c0; - float32x4_t c6 = vdupq_n_f32(bias[1]), c7 = c6, c8 = c6, c9 = c6, c10 = c6, c11 = c6, c25 = c6; - float32x4_t c12 = vdupq_n_f32(bias[2]), c13 = c12, c14 = c12, c15 = c12, c16 = c12, c17 = c12, c26 = c12; - float32x4_t c18 = vdupq_n_f32(bias[3]), c19 = c18, c20 = c18, c21 = c18, c22 = c18, c23 = c18, c27 = c18; + float32x4_t c00 = vdupq_n_f32(0.f), c01 = c00, c02 = c00, c03 = c00, c04 = c00, c05 = c00, c06 = c00; + float32x4_t c10 = vdupq_n_f32(0.f), c11 = c10, c12 = c10, c13 = c10, c14 = c10, c15 = c10, c16 = c10; + float32x4_t c20 = vdupq_n_f32(0.f), c21 = c20, c22 = c20, c23 = c20, c24 = c20, c25 = c20, c26 = c20; + float32x4_t c30 = vdupq_n_f32(0.f), c31 = c30, c32 = c30, c33 = c30, c34 = c30, c35 = c30, c36 = c30; - float32x4_t a0 = vdupq_n_f32(0.0f); - float32x4_t b0 = vdupq_n_f32(0.0f), b1 = vdupq_n_f32(0.0f), b2 = vdupq_n_f32(0.0f); - - for (int p = 0; p < k; p++, a += FAST_CONV_MR) + for( int p = 0; p < np; p++, a += CONV_MR, b += CONV_NR ) { - a0 = vld1q_f32(a); - b0 = vld1q_f32(b), b1 = vld1q_f32(b + 4), b2 = vld1q_f32(b + 8); - b += 12; + float32x4_t a0 = vld1q_f32(a), b0, b1, b2; + b0 = vld1q_f32(b); b1 = vld1q_f32(b + 4); b2 = vld1q_f32(b + 8); - c0 = vfmaq_laneq_f32(c0, b0, a0, 0); - c1 = vfmaq_laneq_f32(c1, b1, a0, 0); - c2 = vfmaq_laneq_f32(c2, b2, a0, 0); - c6 = vfmaq_laneq_f32(c6, b0, a0, 1); - c7 = vfmaq_laneq_f32(c7, b1, a0, 1); - c8 = vfmaq_laneq_f32(c8, b2, a0, 1); - c12 = vfmaq_laneq_f32(c12, b0, a0, 2); - c13 = vfmaq_laneq_f32(c13, b1, a0, 2); - c14 = vfmaq_laneq_f32(c14, b2, a0, 2); - c18 = vfmaq_laneq_f32(c18, b0, a0, 3); - c19 = vfmaq_laneq_f32(c19, b1, a0, 3); - c20 = vfmaq_laneq_f32(c20, b2, a0, 3); + c00 = vfmaq_laneq_f32(c00, b0, a0, 0); + c01 = vfmaq_laneq_f32(c01, b1, a0, 0); + c02 = vfmaq_laneq_f32(c02, b2, a0, 0); + c10 = vfmaq_laneq_f32(c10, b0, a0, 1); + c11 = vfmaq_laneq_f32(c11, b1, a0, 1); + c12 = vfmaq_laneq_f32(c12, b2, a0, 1); + c20 = vfmaq_laneq_f32(c20, b0, a0, 2); + c21 = vfmaq_laneq_f32(c21, b1, a0, 2); + c22 = vfmaq_laneq_f32(c22, b2, a0, 2); + c30 = vfmaq_laneq_f32(c30, b0, a0, 3); + c31 = vfmaq_laneq_f32(c31, b1, a0, 3); + c32 = vfmaq_laneq_f32(c32, b2, a0, 3); - b0 = vld1q_f32(b), b1 = vld1q_f32(b + 4), b2 = vld1q_f32(b + 8); - b += 12; + b0 = vld1q_f32(b + 12); b1 = vld1q_f32(b + 16); b2 = vld1q_f32(b + 20); - c3 = vfmaq_laneq_f32(c3, b0, a0, 0); - c4 = vfmaq_laneq_f32(c4, b1, a0, 0); - c5 = vfmaq_laneq_f32(c5, b2, a0, 0); + c03 = vfmaq_laneq_f32(c03, b0, a0, 0); + c04 = vfmaq_laneq_f32(c04, b1, a0, 0); + c05 = vfmaq_laneq_f32(c05, b2, a0, 0); + c13 = vfmaq_laneq_f32(c13, b0, a0, 1); + c14 = vfmaq_laneq_f32(c14, b1, a0, 1); + c15 = vfmaq_laneq_f32(c15, b2, a0, 1); + c23 = vfmaq_laneq_f32(c23, b0, a0, 2); + c24 = vfmaq_laneq_f32(c24, b1, a0, 2); + c25 = vfmaq_laneq_f32(c25, b2, a0, 2); + c33 = vfmaq_laneq_f32(c33, b0, a0, 3); + c34 = vfmaq_laneq_f32(c34, b1, a0, 3); + c35 = vfmaq_laneq_f32(c35, b2, a0, 3); - c9 = vfmaq_laneq_f32(c9, b0, a0, 1); - c10 = vfmaq_laneq_f32(c10, b1, a0, 1); - c11 = vfmaq_laneq_f32(c11, b2, a0, 1); - - c15 = vfmaq_laneq_f32(c15, b0, a0, 2); - c16 = vfmaq_laneq_f32(c16, b1, a0, 2); - c17 = vfmaq_laneq_f32(c17, b2, a0, 2); - - c21 = vfmaq_laneq_f32(c21, b0, a0, 3); - - b0 = vld1q_f32(b); - b += 4; - - c22 = vfmaq_laneq_f32(c22, b1, a0, 3); - c23 = vfmaq_laneq_f32(c23, b2, a0, 3); - - c24 = vfmaq_laneq_f32(c24, b0, a0, 0); - c25 = vfmaq_laneq_f32(c25, b0, a0, 1); + b0 = vld1q_f32(b + 24); + c06 = vfmaq_laneq_f32(c06, b0, a0, 0); + c16 = vfmaq_laneq_f32(c16, b0, a0, 1); c26 = vfmaq_laneq_f32(c26, b0, a0, 2); - c27 = vfmaq_laneq_f32(c27, b0, a0, 3); + c36 = vfmaq_laneq_f32(c36, b0, a0, 3); } - if (ifActiv) { - b0 = vdupq_n_f32(minval), b1 = vdupq_n_f32(maxval); - c0 = vminq_f32(vmaxq_f32(c0, b0), b1); - c1 = vminq_f32(vmaxq_f32(c1, b0), b1); - c2 = vminq_f32(vmaxq_f32(c2, b0), b1); - c3 = vminq_f32(vmaxq_f32(c3, b0), b1); - c4 = vminq_f32(vmaxq_f32(c4, b0), b1); - c5 = vminq_f32(vmaxq_f32(c5, b0), b1); - c6 = vminq_f32(vmaxq_f32(c6, b0), b1); - c7 = vminq_f32(vmaxq_f32(c7, b0), b1); - c8 = vminq_f32(vmaxq_f32(c8, b0), b1); - c9 = vminq_f32(vmaxq_f32(c9, b0), b1); - c10 = vminq_f32(vmaxq_f32(c10, b0), b1); - c11 = vminq_f32(vmaxq_f32(c11, b0), b1); - c12 = vminq_f32(vmaxq_f32(c12, b0), b1); - c13 = vminq_f32(vmaxq_f32(c13, b0), b1); - c14 = vminq_f32(vmaxq_f32(c14, b0), b1); - c15 = vminq_f32(vmaxq_f32(c15, b0), b1); - c16 = vminq_f32(vmaxq_f32(c16, b0), b1); - c17 = vminq_f32(vmaxq_f32(c17, b0), b1); - c18 = vminq_f32(vmaxq_f32(c18, b0), b1); - c19 = vminq_f32(vmaxq_f32(c19, b0), b1); - c20 = vminq_f32(vmaxq_f32(c20, b0), b1); - c21 = vminq_f32(vmaxq_f32(c21, b0), b1); - c22 = vminq_f32(vmaxq_f32(c22, b0), b1); - c23 = vminq_f32(vmaxq_f32(c23, b0), b1); - c24 = vminq_f32(vmaxq_f32(c24, b0), b1); - c25 = vminq_f32(vmaxq_f32(c25, b0), b1); - c26 = vminq_f32(vmaxq_f32(c26, b0), b1); - c27 = vminq_f32(vmaxq_f32(c27, b0), b1); + if (!init_c) + { + c00 = vaddq_f32(c00, vld1q_f32(c)); + c01 = vaddq_f32(c01, vld1q_f32(c + 4)); + c02 = vaddq_f32(c02, vld1q_f32(c + 8)); + c03 = vaddq_f32(c03, vld1q_f32(c + 12)); + c04 = vaddq_f32(c04, vld1q_f32(c + 16)); + c05 = vaddq_f32(c05, vld1q_f32(c + 20)); + c06 = vaddq_f32(c06, vld1q_f32(c + 24)); + + c10 = vaddq_f32(c10, vld1q_f32(c + ldc)); + c11 = vaddq_f32(c11, vld1q_f32(c + ldc + 4)); + c12 = vaddq_f32(c12, vld1q_f32(c + ldc + 8)); + c13 = vaddq_f32(c13, vld1q_f32(c + ldc + 12)); + c14 = vaddq_f32(c14, vld1q_f32(c + ldc + 16)); + c15 = vaddq_f32(c15, vld1q_f32(c + ldc + 20)); + c16 = vaddq_f32(c16, vld1q_f32(c + ldc + 24)); + + c20 = vaddq_f32(c20, vld1q_f32(c + ldc*2)); + c21 = vaddq_f32(c21, vld1q_f32(c + ldc*2 + 4)); + c22 = vaddq_f32(c22, vld1q_f32(c + ldc*2 + 8)); + c23 = vaddq_f32(c23, vld1q_f32(c + ldc*2 + 12)); + c24 = vaddq_f32(c24, vld1q_f32(c + ldc*2 + 16)); + c25 = vaddq_f32(c25, vld1q_f32(c + ldc*2 + 20)); + c26 = vaddq_f32(c26, vld1q_f32(c + ldc*2 + 24)); + + c30 = vaddq_f32(c30, vld1q_f32(c + ldc*3)); + c31 = vaddq_f32(c31, vld1q_f32(c + ldc*3 + 4)); + c32 = vaddq_f32(c32, vld1q_f32(c + ldc*3 + 8)); + c33 = vaddq_f32(c33, vld1q_f32(c + ldc*3 + 12)); + c34 = vaddq_f32(c34, vld1q_f32(c + ldc*3 + 16)); + c35 = vaddq_f32(c35, vld1q_f32(c + ldc*3 + 20)); + c36 = vaddq_f32(c36, vld1q_f32(c + ldc*3 + 24)); } - vst1q_f32(c, c0); - vst1q_f32(c + 4, c1); - vst1q_f32(c + 8, c2); - vst1q_f32(c + 12, c3); - vst1q_f32(c + 16, c4); - vst1q_f32(c + 20, c5); - vst1q_f32(c + 24, c24); - vst1q_f32(c + ldc, c6); - vst1q_f32(c + ldc + 4, c7); - vst1q_f32(c + ldc + 8, c8); - vst1q_f32(c + ldc + 12, c9); - vst1q_f32(c + ldc + 16, c10); - vst1q_f32(c + ldc + 20, c11); - vst1q_f32(c + ldc + 24, c25); + vst1q_f32(c, c00); vst1q_f32(c+4, c01); + vst1q_f32(c+8, c02); vst1q_f32(c+12, c03); + vst1q_f32(c+16, c04); vst1q_f32(c+20, c05); + vst1q_f32(c+24, c06); - vst1q_f32(c + ldc * 2, c12); - vst1q_f32(c + ldc * 2 + 4, c13); - vst1q_f32(c + ldc * 2 + 8, c14); - vst1q_f32(c + ldc * 2 + 12, c15); - vst1q_f32(c + ldc * 2 + 16, c16); - vst1q_f32(c + ldc * 2 + 20, c17); - vst1q_f32(c + ldc * 2 + 24, c26); + vst1q_f32(c+ldc, c10); vst1q_f32(c+ldc+4, c11); + vst1q_f32(c+ldc+8, c12); vst1q_f32(c+ldc+12, c13); + vst1q_f32(c+ldc+16, c14); vst1q_f32(c+ldc+20, c15); + vst1q_f32(c+ldc+24, c16); - vst1q_f32(c + ldc * 3, c18); - vst1q_f32(c + ldc * 3 + 4, c19); - vst1q_f32(c + ldc * 3 + 8, c20); - vst1q_f32(c + ldc * 3 + 12, c21); - vst1q_f32(c + ldc * 3 + 16, c22); - vst1q_f32(c + ldc * 3 + 20, c23); - vst1q_f32(c + ldc * 3 + 24, c27); + vst1q_f32(c+ldc*2, c20); vst1q_f32(c+ldc*2+4, c21); + vst1q_f32(c+ldc*2+8, c22); vst1q_f32(c+ldc*2+12, c23); + vst1q_f32(c+ldc*2+16, c24); vst1q_f32(c+ldc*2+20, c25); + vst1q_f32(c+ldc*2+24, c26); + + vst1q_f32(c+ldc*3, c30); vst1q_f32(c+ldc*3+4, c31); + vst1q_f32(c+ldc*3+8, c32); vst1q_f32(c+ldc*3+12, c33); + vst1q_f32(c+ldc*3+16, c34); vst1q_f32(c+ldc*3+20, c35); + vst1q_f32(c+ldc*3+24, c36); } -#elif (!defined(CV_NEON_AARCH64) || !CV_NEON_AARCH64) && FAST_CONV_MR == 4 && FAST_CONV_NR == 12 // ARMv7 +#elif CONV_MR == 4 && CONV_NR == 12 // ARMv7 { - float32x4_t c0 = vdupq_n_f32(bias[0]), c1 = c0, c2 = c0; - float32x4_t c3 = vdupq_n_f32(bias[1]), c4 = c3, c5 = c3; - float32x4_t c6 = vdupq_n_f32(bias[2]), c7 = c6, c8 = c6; - float32x4_t c9 = vdupq_n_f32(bias[3]), c10 = c9, c11 = c9; + float32x4_t c0 = vdupq_n_f32(0.f), c1 = c0, c2 = c0; + float32x4_t c3 = vdupq_n_f32(0.f), c4 = c3, c5 = c3; + float32x4_t c6 = vdupq_n_f32(0.f), c7 = c6, c8 = c6; + float32x4_t c9 = vdupq_n_f32(0.f), c10 = c9, c11 = c9; + float32x2_t a0 = vdup_n_f32(0.0f), a1 = a0; float32x4_t b0 = vdupq_n_f32(0.0f), b1 = vdupq_n_f32(0.0f), b2 = vdupq_n_f32(0.0f); - for (int p = 0; p < k; p++, a += FAST_CONV_MR, b += FAST_CONV_NR) + for (int p = 0; p < np; p++, a += CONV_MR, b += CONV_NR) { a0 = vld1_f32(a), a1 = vld1_f32(a+2); b0 = vld1q_f32(b), b1 = vld1q_f32(b + 4), b2 = vld1q_f32(b + 8); @@ -311,29 +532,32 @@ void convBlock_NEON(int k, const float *a, const float *b, c11 = vmlaq_lane_f32(c11, b2, a1, 1); } - if (ifActiv) + if (!init_c) { - b0 = vdupq_n_f32(minval), b1 = vdupq_n_f32(maxval); - c0 = vminq_f32(vmaxq_f32(c0, b0), b1); - c1 = vminq_f32(vmaxq_f32(c1, b0), b1); - c2 = vminq_f32(vmaxq_f32(c2, b0), b1); - c3 = vminq_f32(vmaxq_f32(c3, b0), b1); - c4 = vminq_f32(vmaxq_f32(c4, b0), b1); - c5 = vminq_f32(vmaxq_f32(c5, b0), b1); - c6 = vminq_f32(vmaxq_f32(c6, b0), b1); - c7 = vminq_f32(vmaxq_f32(c7, b0), b1); - c8 = vminq_f32(vmaxq_f32(c8, b0), b1); - c9 = vminq_f32(vmaxq_f32(c9, b0), b1); - c10 = vminq_f32(vmaxq_f32(c10, b0), b1); - c11 = vminq_f32(vmaxq_f32(c11, b0), b1); + c0 = vaddq_f32(c0, vld1q_f32(c)); + c1 = vaddq_f32(c1, vld1q_f32(c + 4)); + c2 = vaddq_f32(c2, vld1q_f32(c + 8)); + + c3 = vaddq_f32(c3, vld1q_f32(c + ldc)); + c4 = vaddq_f32(c4, vld1q_f32(c + ldc + 4)); + c5 = vaddq_f32(c5, vld1q_f32(c + ldc + 8)); + + c6 = vaddq_f32(c6, vld1q_f32(c + ldc * 2)); + c7 = vaddq_f32(c7, vld1q_f32(c + ldc * 2 + 4)); + c8 = vaddq_f32(c8, vld1q_f32(c + ldc * 2 + 8)); + + c9 = vaddq_f32(c9 , vld1q_f32(c + ldc * 3)); + c10 = vaddq_f32(c10, vld1q_f32(c + ldc * 3 + 4)); + c11 = vaddq_f32(c11, vld1q_f32(c + ldc * 3 + 8)); } - vst1q_f32(c, c0); vst1q_f32(c+4, c1); vst1q_f32(c+8, c2); - vst1q_f32(c + ldc, c3); vst1q_f32(c + ldc + 4, c4); vst1q_f32(c + ldc + 8, c5); - vst1q_f32(c + ldc*2, c6); vst1q_f32(c + ldc*2 + 4, c7); vst1q_f32(c + ldc*2 + 8, c8); - vst1q_f32(c + ldc*3, c9); vst1q_f32(c + ldc*3 + 4, c10); vst1q_f32(c + ldc*3 + 8, c11); + + vst1q_f32(c, c0), vst1q_f32(c+4, c1), vst1q_f32(c+8, c2); + vst1q_f32(c + ldc, c3), vst1q_f32(c + ldc + 4, c4), vst1q_f32(c + ldc + 8, c5); + vst1q_f32(c + ldc*2, c6), vst1q_f32(c + ldc*2 + 4, c7), vst1q_f32(c + ldc*2 + 8, c8); + vst1q_f32(c + ldc*3, c9), vst1q_f32(c + ldc*3 + 4, c10), vst1q_f32(c + ldc*3 + 8, c11); } -#else -#error "unsupported FAST_CONV_MR and/or FAST_CONV_NR in convBlock_NEON." +//#else +//#error "unsupported CONV_MR and/or CONV_NR in convBlock_NEON." #endif } #endif diff --git a/modules/dnn/src/layers/fast_convolution/winograd_3x3s1_f63.cpp b/modules/dnn/src/layers/fast_convolution/winograd_3x3s1_f63.cpp index e841889007..e3b8088410 100644 --- a/modules/dnn/src/layers/fast_convolution/winograd_3x3s1_f63.cpp +++ b/modules/dnn/src/layers/fast_convolution/winograd_3x3s1_f63.cpp @@ -2,486 +2,930 @@ // 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. +// This file is modified from the ficus (https://github.com/vpisarev/ficus/blob/master/lib/NN/OpConv_Winograd.fx). +// Here is the original license: /* -Winograd-based convolution F(6x6, 3x3). -The code has been borrowed from ncnn inference engine (https://github.com/Tencent/ncnn) -and adapted for OpenCV by Zihao Mu. - -Below is the original copyright + This file is a part of ficus language project. + See ficus/LICENSE for the licensing terms */ -// Tencent is pleased to support the open source community by making ncnn available. -// -// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. -// -// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except -// in compliance with the License. You may obtain a copy of the License at -// -// https://opensource.org/licenses/BSD-3-Clause -// -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - #include "../../precomp.hpp" #include "fast_convolution.hpp" namespace cv { namespace dnn { -enum -{ - WINO_STEP=6, - WINO_KSIZE=3, - WINO_SIZE= WINO_STEP + WINO_KSIZE - 1, - WINO_AREA= WINO_SIZE * WINO_SIZE -}; + +#if CV_NEON || CV_SIMD128 || CV_TRY_AVX2 +enum { VEC_ALIGN = 32, DFT_TYPE = CV_32F }; // Memory alignment. + +static void +_fx_winograd_accum_f32(const float* inwptr, const float* wptr, + float* outbuf, int Cg, int iblock) + { +#if CV_NEON && CV_NEON_AARCH64 + CV_Assert(_FX_WINO_IBLOCK == 6 && _FX_WINO_KBLOCK == 4); + if (iblock > 3) + { + for (int atom_id = 0; atom_id < _FX_WINO_NATOMS_F32; atom_id++, + outbuf += _FX_WINO_ATOM_F32) + { + float32x4_t s00 = vdupq_n_f32(0.f), s01 = s00, s02 = s00, s03 = s00, s04 = s00, s05 = s00; + float32x4_t s10 = vdupq_n_f32(0.f), s11 = s00, s12 = s00, s13 = s00, s14 = s00, s15 = s00; + float32x4_t s20 = vdupq_n_f32(0.f), s21 = s00, s22 = s00, s23 = s00, s24 = s00, s25 = s00; + float32x4_t s30 = vdupq_n_f32(0.f), s31 = s00, s32 = s00, s33 = s00, s34 = s00, s35 = s00; + for (int c = 0; c < Cg; c++, inwptr += _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32, + wptr += _FX_WINO_KBLOCK*_FX_WINO_ATOM_F32) { + float32x4_t w0 = vld1q_f32(wptr), w1 = vld1q_f32(wptr + 4); + float32x4_t w2 = vld1q_f32(wptr + 8), w3 = vld1q_f32(wptr + 12); + float32x4_t x0, x1; + x0 = vld1q_f32(inwptr); + x1 = vld1q_f32(inwptr + 4); + s00 = vfmaq_f32(s00, w0, x0); + s01 = vfmaq_f32(s01, w0, x1); + s10 = vfmaq_f32(s10, w1, x0); + s11 = vfmaq_f32(s11, w1, x1); + s20 = vfmaq_f32(s20, w2, x0); + s21 = vfmaq_f32(s21, w2, x1); + s30 = vfmaq_f32(s30, w3, x0); + s31 = vfmaq_f32(s31, w3, x1); + x0 = vld1q_f32(inwptr + 8); + x1 = vld1q_f32(inwptr + 12); + s02 = vfmaq_f32(s02, w0, x0); + s03 = vfmaq_f32(s03, w0, x1); + s12 = vfmaq_f32(s12, w1, x0); + s13 = vfmaq_f32(s13, w1, x1); + s22 = vfmaq_f32(s22, w2, x0); + s23 = vfmaq_f32(s23, w2, x1); + s32 = vfmaq_f32(s32, w3, x0); + s33 = vfmaq_f32(s33, w3, x1); + x0 = vld1q_f32(inwptr + 16); + x1 = vld1q_f32(inwptr + 20); + s04 = vfmaq_f32(s04, w0, x0); + s05 = vfmaq_f32(s05, w0, x1); + s14 = vfmaq_f32(s14, w1, x0); + s15 = vfmaq_f32(s15, w1, x1); + s24 = vfmaq_f32(s24, w2, x0); + s25 = vfmaq_f32(s25, w2, x1); + s34 = vfmaq_f32(s34, w3, x0); + s35 = vfmaq_f32(s35, w3, x1); + } + + vst1q_f32(outbuf, s00); + vst1q_f32(outbuf + 1*64, s01); + vst1q_f32(outbuf + 2*64, s02); + vst1q_f32(outbuf + 3*64, s03); + vst1q_f32(outbuf + 4*64, s04); + vst1q_f32(outbuf + 5*64, s05); + + vst1q_f32(outbuf + 6*64, s10); + vst1q_f32(outbuf + 7*64, s11); + vst1q_f32(outbuf + 8*64, s12); + vst1q_f32(outbuf + 9*64, s13); + vst1q_f32(outbuf + 10*64, s14); + vst1q_f32(outbuf + 11*64, s15); + + vst1q_f32(outbuf + 12*64, s20); + vst1q_f32(outbuf + 13*64, s21); + vst1q_f32(outbuf + 14*64, s22); + vst1q_f32(outbuf + 15*64, s23); + vst1q_f32(outbuf + 16*64, s24); + vst1q_f32(outbuf + 17*64, s25); + + vst1q_f32(outbuf + 18*64, s30); + vst1q_f32(outbuf + 19*64, s31); + vst1q_f32(outbuf + 20*64, s32); + vst1q_f32(outbuf + 21*64, s33); + vst1q_f32(outbuf + 22*64, s34); + vst1q_f32(outbuf + 23*64, s35); + } + } + else + { + for (int atom_id = 0; atom_id < _FX_WINO_NATOMS_F32; atom_id++, + outbuf += _FX_WINO_ATOM_F32) + { + float32x4_t s00 = vdupq_n_f32(0.f), s01 = s00, s02 = s00; + float32x4_t s10 = vdupq_n_f32(0.f), s11 = s00, s12 = s00; + float32x4_t s20 = vdupq_n_f32(0.f), s21 = s00, s22 = s00; + float32x4_t s30 = vdupq_n_f32(0.f), s31 = s00, s32 = s00; + for (int c = 0; c < Cg; c++, inwptr += _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32, + wptr += _FX_WINO_KBLOCK*_FX_WINO_ATOM_F32) { + float32x4_t w0 = vld1q_f32(wptr), w1 = vld1q_f32(wptr + 4); + float32x4_t w2 = vld1q_f32(wptr + 8), w3 = vld1q_f32(wptr + 12); + float32x4_t x0, x1, x2; + x0 = vld1q_f32(inwptr); + x1 = vld1q_f32(inwptr + 4); + x2 = vld1q_f32(inwptr + 8); + s00 = vfmaq_f32(s00, w0, x0); + s01 = vfmaq_f32(s01, w0, x1); + s02 = vfmaq_f32(s02, w0, x2); + s10 = vfmaq_f32(s10, w1, x0); + s11 = vfmaq_f32(s11, w1, x1); + s12 = vfmaq_f32(s12, w1, x2); + s20 = vfmaq_f32(s20, w2, x0); + s21 = vfmaq_f32(s21, w2, x1); + s22 = vfmaq_f32(s22, w2, x2); + s30 = vfmaq_f32(s30, w3, x0); + s31 = vfmaq_f32(s31, w3, x1); + s32 = vfmaq_f32(s32, w3, x2); + } + + vst1q_f32(outbuf, s00); + vst1q_f32(outbuf + 1*64, s01); + vst1q_f32(outbuf + 2*64, s02); + vst1q_f32(outbuf + 6*64, s10); + vst1q_f32(outbuf + 7*64, s11); + vst1q_f32(outbuf + 8*64, s12); + vst1q_f32(outbuf + 12*64, s20); + vst1q_f32(outbuf + 13*64, s21); + vst1q_f32(outbuf + 14*64, s22); + vst1q_f32(outbuf + 18*64, s30); + vst1q_f32(outbuf + 19*64, s31); + vst1q_f32(outbuf + 20*64, s32); + } + } +#elif CV_SIMD128 + CV_Assert(_FX_WINO_IBLOCK == 3 && _FX_WINO_KBLOCK == 4); + for (int atom_id = 0; atom_id < _FX_WINO_NATOMS_F32; atom_id++, + outbuf += _FX_WINO_ATOM_F32) + { + v_float32x4 s00 = v_setzero_f32(), s01 = s00, s02 = s00; + v_float32x4 s10 = v_setzero_f32(), s11 = s00, s12 = s00; + v_float32x4 s20 = v_setzero_f32(), s21 = s00, s22 = s00; + v_float32x4 s30 = v_setzero_f32(), s31 = s00, s32 = s00; + + for (int c = 0; c < Cg; c++, inwptr += _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32, + wptr += _FX_WINO_KBLOCK*_FX_WINO_ATOM_F32) + { + v_float32x4 x0, x1, x2; + x0 = v_load(inwptr); + x1 = v_load(inwptr + 4); + x2 = v_load(inwptr + 8); + + v_float32x4 w0 = v_load(wptr); + s00 = v_fma(w0, x0, s00); + s01 = v_fma(w0, x1, s01); + s02 = v_fma(w0, x2, s02); + + w0 = v_load(wptr + 4); + s10 = v_fma(w0, x0, s10); + s11 = v_fma(w0, x1, s11); + s12 = v_fma(w0, x2, s12); + + w0 = v_load(wptr + 8); + s20 = v_fma(w0, x0, s20); + s21 = v_fma(w0, x1, s21); + s22 = v_fma(w0, x2, s22); + + w0 = v_load(wptr + 12); + s30 = v_fma(w0, x0, s30); + s31 = v_fma(w0, x1, s31); + s32 = v_fma(w0, x2, s32); + } + + v_store(outbuf, s00); + v_store(outbuf + 1*64, s01); + v_store(outbuf + 2*64, s02); + v_store(outbuf + 3*64, s10); + v_store(outbuf + 4*64, s11); + v_store(outbuf + 5*64, s12); + v_store(outbuf + 6*64, s20); + v_store(outbuf + 7*64, s21); + v_store(outbuf + 8*64, s22); + v_store(outbuf + 9*64, s30); + v_store(outbuf + 10*64, s31); + v_store(outbuf + 11*64, s32); + } +#else + for (int atom_id = 0; atom_id < _FX_WINO_NATOMS_F32; + atom_id++, outbuf += _FX_WINO_ATOM_F32) + { + float sumbuf[_FX_WINO_IBLOCK*_FX_WINO_KBLOCK*_FX_WINO_ATOM_F32]; + memset(sumbuf, 0, sizeof(sumbuf)); + for (int c = 0; c < Cg; c++, inwptr += _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32, + wptr += _FX_WINO_KBLOCK*_FX_WINO_ATOM_F32) + { + for (int i = 0; i < _FX_WINO_KBLOCK; i++) + { + for (int j = 0; j < _FX_WINO_IBLOCK; j++) + { + int i_ = i*_FX_WINO_ATOM_F32; + int j_ = j*_FX_WINO_ATOM_F32; + int ij_ = i_*_FX_WINO_IBLOCK + j_; + float s0 = inwptr[j_ + 0]*wptr[i_ + 0]; + float s1 = inwptr[j_ + 1]*wptr[i_ + 1]; + float s2 = inwptr[j_ + 2]*wptr[i_ + 2]; + float s3 = inwptr[j_ + 3]*wptr[i_ + 3]; + sumbuf[ij_ + 0] += s0; + sumbuf[ij_ + 1] += s1; + sumbuf[ij_ + 2] += s2; + sumbuf[ij_ + 3] += s3; + } + } + } + for (int ij = 0; ij < _FX_WINO_KBLOCK*_FX_WINO_IBLOCK; ij++) + { + int ij_ = ij*_FX_WINO_ATOM_F32; + int ij_out = ij*_FX_WINO_AREA; + outbuf[ij_out + 0] = sumbuf[ij_ + 0]; + outbuf[ij_out + 1] = sumbuf[ij_ + 1]; + outbuf[ij_out + 2] = sumbuf[ij_ + 2]; + outbuf[ij_out + 3] = sumbuf[ij_ + 3]; + } + } +#endif +} #if CV_NEON -static void winograd_trans_input_F63(float* src, float* dst, int Channle_div4, const int tiles, const int big_step, const int line_step, const int* ofstab0) -{ - // const float itm[8][8] = { - // {1.0f, 0.0f, -5.25f, 0.00f, 5.25f, 0.00f, -1.0f, 0.0f}, - // - // {0.0f, 1.0f, 1.00f, -4.25f, -4.25f, 1.00f, 1.0f, 0.0f}, - // {0.0f, -1.0f, 1.00f, 4.25f, -4.25f, -1.00f, 1.0f, 0.0f}, - // - // {0.0f, 0.5f, 0.25f, -2.50f, -1.25f, 2.00f, 1.0f, 0.0f}, - // {0.0f, -0.5f, 0.25f, 2.50f, -1.25f, -2.00f, 1.0f, 0.0f}, - // - // {0.0f, 2.0f, 4.00f, -2.50f, -5.00f, 0.50f, 1.0f, 0.0f}, - // {0.0f, -2.0f, 4.00f, 2.50f, -5.00f, -0.50f, 1.0f, 0.0f}, - // - // {0.0f, -1.0f, 0.00f, 5.25f, 0.00f, -5.25f, 0.0f, 1.0f} - // }; - - // 0 = r00 - r06 + (r04 - r02) * 5.25 - // 7 = r07 - r01 + (r03 - r05) * 5.25 - - // 1 = (r02 + r06 - r04 * 4.25) + (r01 - r03 * 4.25 + r05) - // 2 = (r02 + r06 - r04 * 4.25) - (r01 - r03 * 4.25 + r05) - - // 3 = (r06 + r02 * 0.25 - r04 * 1.25) + (r01 * 0.5 - r03 * 2.5 + r05 * 2) - // 4 = (r06 + r02 * 0.25 - r04 * 1.25) - (r01 * 0.5 - r03 * 2.5 + r05 * 2) - - // reuse r04 * 1.25 - // reuse r03 * 2.5 - // 5 = (r06 + (r02 - r04 * 1.25) * 4) + (r01 * 2 - r03 * 2.5 + r05 * 0.5) - // 6 = (r06 + (r02 - r04 * 1.25) * 4) - (r01 * 2 - r03 * 2.5 + r05 * 0.5) - - float tmp[8][8][FAST_VEC_NLANES]; - AutoBuffer input_buf0_; - input_buf0_.allocate(64 * tiles * FAST_VEC_NLANES); - - float* input_buf0 = input_buf0_.data(); - memset(input_buf0, 0, 64 * tiles * FAST_VEC_NLANES * sizeof(float )); - - for (int ti = 0; ti < tiles; ti++) - { - float* input0 = src + ti * 64 * 4; - float* input = input0; - for (int m = 0; m < 8; m++) - { - float32x4_t _r00 = vld1q_f32(input); - float32x4_t _r01 = vld1q_f32(input + 4); - float32x4_t _r02 = vld1q_f32(input + 8); - float32x4_t _r03 = vld1q_f32(input + 12); - float32x4_t _r04 = vld1q_f32(input + 16); - float32x4_t _r05 = vld1q_f32(input + 20); - float32x4_t _r06 = vld1q_f32(input + 24); - float32x4_t _r07 = vld1q_f32(input + 28); - - float32x4_t _tmp0m = vmlaq_n_f32(vsubq_f32(_r00, _r06), vsubq_f32(_r04, _r02), 5.25f); - float32x4_t _tmp7m = vmlaq_n_f32(vsubq_f32(_r07, _r01), vsubq_f32(_r03, _r05), 5.25f); - vst1q_f32(tmp[0][m], _tmp0m); - vst1q_f32(tmp[7][m], _tmp7m); - - float32x4_t _tmp12a = vmlsq_n_f32(vaddq_f32(_r02, _r06), _r04, 4.25f); - float32x4_t _tmp12b = vmlsq_n_f32(vaddq_f32(_r01, _r05), _r03, 4.25f); - - float32x4_t _tmp1m = vaddq_f32(_tmp12a, _tmp12b); - float32x4_t _tmp2m = vsubq_f32(_tmp12a, _tmp12b); - vst1q_f32(tmp[1][m], _tmp1m); - vst1q_f32(tmp[2][m], _tmp2m); - - float32x4_t _tmp34a = vmlsq_n_f32(vmlaq_n_f32(_r06, _r02, 0.25f), _r04, 1.25f); - float32x4_t _tmp34b = vmlaq_n_f32(vmlsq_n_f32(vmulq_n_f32(_r01, 0.5f), _r03, 2.5f), _r05, 2.f); - - float32x4_t _tmp3m = vaddq_f32(_tmp34a, _tmp34b); - float32x4_t _tmp4m = vsubq_f32(_tmp34a, _tmp34b); - vst1q_f32(tmp[3][m], _tmp3m); - vst1q_f32(tmp[4][m], _tmp4m); - - float32x4_t _tmp56a = vmlaq_n_f32(_r06, vmlsq_n_f32(_r02, _r04, 1.25f), 4.f); - float32x4_t _tmp56b = vmlaq_n_f32(vmlsq_n_f32(vmulq_n_f32(_r01, 2.f), _r03, 2.5f), _r05, 0.5f); - - float32x4_t _tmp5m = vaddq_f32(_tmp56a, _tmp56b); - float32x4_t _tmp6m = vsubq_f32(_tmp56a, _tmp56b); - vst1q_f32(tmp[5][m], _tmp5m); - vst1q_f32(tmp[6][m], _tmp6m); - - input += 8 * FAST_VEC_NLANES; - } - - float* input_buf00 = input_buf0 + ti * 4; - float* input_buf01 = input_buf00 + tiles * 4; - float* input_buf02 = input_buf00 + tiles * 8; - float* input_buf03 = input_buf00 + tiles * 12; - float* input_buf04 = input_buf00 + tiles * 16; - float* input_buf05 = input_buf00 + tiles * 20; - float* input_buf06 = input_buf00 + tiles * 24; - float* input_buf07 = input_buf00 + tiles * 28; - - for (int m = 0; m < 8; m++) - { - float32x4_t _tmp00 = vld1q_f32(tmp[m][0]); - float32x4_t _tmp01 = vld1q_f32(tmp[m][1]); - float32x4_t _tmp02 = vld1q_f32(tmp[m][2]); - float32x4_t _tmp03 = vld1q_f32(tmp[m][3]); - float32x4_t _tmp04 = vld1q_f32(tmp[m][4]); - float32x4_t _tmp05 = vld1q_f32(tmp[m][5]); - float32x4_t _tmp06 = vld1q_f32(tmp[m][6]); - float32x4_t _tmp07 = vld1q_f32(tmp[m][7]); - - float32x4_t _r0tm0 = vmlaq_n_f32(vsubq_f32(_tmp00, _tmp06), vsubq_f32(_tmp04, _tmp02), 5.25f); - float32x4_t _r0tm7 = vmlaq_n_f32(vsubq_f32(_tmp07, _tmp01), vsubq_f32(_tmp03, _tmp05), 5.25f); - - float32x4_t _tmp12a = vmlsq_n_f32(vaddq_f32(_tmp02, _tmp06), _tmp04, 4.25f); - float32x4_t _tmp12b = vmlsq_n_f32(vaddq_f32(_tmp01, _tmp05), _tmp03, 4.25f); - - float32x4_t _r0tm1 = vaddq_f32(_tmp12a, _tmp12b); - float32x4_t _r0tm2 = vsubq_f32(_tmp12a, _tmp12b); - - float32x4_t _tmp34a = vmlsq_n_f32(vmlaq_n_f32(_tmp06, _tmp02, 0.25f), _tmp04, 1.25f); - float32x4_t _tmp34b = vmlaq_n_f32(vmlsq_n_f32(vmulq_n_f32(_tmp01, 0.5f), _tmp03, 2.5f), _tmp05, 2.f); - - float32x4_t _r0tm3 = vaddq_f32(_tmp34a, _tmp34b); - float32x4_t _r0tm4 = vsubq_f32(_tmp34a, _tmp34b); - - float32x4_t _tmp56a = vmlaq_n_f32(_tmp06, vmlsq_n_f32(_tmp02, _tmp04, 1.25f), 4.f); - float32x4_t _tmp56b = vmlaq_n_f32(vmlsq_n_f32(vmulq_n_f32(_tmp01, 2.f), _tmp03, 2.5f), _tmp05, 0.5f); - - float32x4_t _r0tm5 = vaddq_f32(_tmp56a, _tmp56b); - float32x4_t _r0tm6 = vsubq_f32(_tmp56a, _tmp56b); - - vst1q_f32(input_buf00, _r0tm0); - vst1q_f32(input_buf01, _r0tm1); - vst1q_f32(input_buf02, _r0tm2); - vst1q_f32(input_buf03, _r0tm3); - vst1q_f32(input_buf04, _r0tm4); - vst1q_f32(input_buf05, _r0tm5); - vst1q_f32(input_buf06, _r0tm6); - vst1q_f32(input_buf07, _r0tm7); - - input_buf00 += tiles * 32; - input_buf01 += tiles * 32; - input_buf02 += tiles * 32; - input_buf03 += tiles * 32; - input_buf04 += tiles * 32; - input_buf05 += tiles * 32; - input_buf06 += tiles * 32; - input_buf07 += tiles * 32; - } - } - - // [line Number, input pack] - // if InpPack == 8; - for (int r = 0; r < 64; r++) - { - int ti = 0; - float* out0 = dst + r * big_step; - float* input0 = input_buf0 + 4 * tiles * r; - - // TODO! support tiles > 12 -//#if CV_NEON_AARCH64 -// for (; ti + 11 < tiles; ti += 12) -// { -// float* out1 = out0 + line_step * ofstab0[ti * 2] + Channle_div4 * ofstab0[ti * 2 + 1] * 4; -//// std::cout<<"ofstab0[ti * 2] = "<& conv, int ntasks, float minval, - float maxval, ActivationLayer* activ, bool ifMinMaxAct) +/* Inverse Winograd 8x8 transform: + out = (A'*inp*A)', where + inp is input 8x8 FP32 matrix, + A' is + [1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 0.f, + 0.f, 1.f, -1.f, 2.f, -2.f, 0.5f, -0.5f, 0.f, + 0.f, 1.f, 1.f, 4.f, 4.f, 0.25f, 0.25f, 0.f, + 0.f, 1.f, -1.f, 8.f, -8.f, 0.125f, -0.125f, 0.f, + 0.f, 1.f, 1.f, 16.f, 16.f, 1.f/16, 1.f/16, 0.f, + 0.f, 1.f, -1.f, 32.f, -32.f, 1.f/32, -1.f/32, 1.f] + + inp is pre-loaded into xij registers, + out will be stored in zij, where (0<=i<=7 for x, 0<=i<=5 for z), 0<=j<=1. + + After the inverse transform is done, we add bias, + optionally add results from the earlier tensors (by-pass), + optionally apply activation function and then + store the final results. + + Note that both _FX_WINOGRAD_FWD_8x8() and + _FX_WINOGRAD_INV_8x8() produce tranposed output. + That is, after both forward and then inverse transformation, + we get non-transposed result. + Of course, for the correct work of Winograd-based convolution, + the Winograd-transformed weights should also be transposed. + init_conv() (see OpConv.fx) takes care of that. +*/ +static void +_fx_winograd_AtXA_8x8_f32(const float* inptr, int inpstep, + float* bpptr, int bpstep, float* outptr, int outstep, + float bias, float minval, float maxval, bool ifMinMaxAct) +{ +#if CV_NEON && CV_NEON_AARCH64 + float32x4_t x00 = vld1q_f32(inptr), x01 = vld1q_f32(inptr + 4); + float32x4_t x10 = vld1q_f32(inptr + inpstep), x11 = vld1q_f32(inptr + inpstep + 4); + float32x4_t x20 = vld1q_f32(inptr + inpstep*2), x21 = vld1q_f32(inptr + inpstep*2 + 4); + float32x4_t x30 = vld1q_f32(inptr + inpstep*3), x31 = vld1q_f32(inptr + inpstep*3 + 4); + float32x4_t x40 = vld1q_f32(inptr + inpstep*4), x41 = vld1q_f32(inptr + inpstep*4 + 4); + float32x4_t x50 = vld1q_f32(inptr + inpstep*5), x51 = vld1q_f32(inptr + inpstep*5 + 4); + float32x4_t x60 = vld1q_f32(inptr + inpstep*6), x61 = vld1q_f32(inptr + inpstep*6 + 4); + float32x4_t x70 = vld1q_f32(inptr + inpstep*7), x71 = vld1q_f32(inptr + inpstep*7 + 4); + float32x4_t z00, z01, z10, z11, z20, z21, z30, z31, z40, z41, z50, z51; + + { + float32x4_t s12_0, s12_1, s34_0, s34_1, s56_0, s56_1; + s12_0 = vaddq_f32(x10, x20); s12_1 = vaddq_f32(x11, x21); + s34_0 = vaddq_f32(x30, x40); s34_1 = vaddq_f32(x31, x41); + s56_0 = vaddq_f32(x50, x60); s56_1 = vaddq_f32(x51, x61); + + float32x4_t y00 = vaddq_f32(vaddq_f32(vaddq_f32(x00, s12_0), s34_0), s56_0); + float32x4_t y01 = vaddq_f32(vaddq_f32(vaddq_f32(x01, s12_1), s34_1), s56_1); + float32x4_t y20 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 4.0f), s56_0, 0.25f); + float32x4_t y21 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 4.0f), s56_1, 0.25f); + float32x4_t y40 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 16.0f), s56_0, 1.f/16); + float32x4_t y41 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 16.0f), s56_1, 1.f/16); + + s12_0 = vsubq_f32(x10, x20); s12_1 = vsubq_f32(x11, x21); + s34_0 = vsubq_f32(x30, x40); s34_1 = vsubq_f32(x31, x41); + s56_0 = vsubq_f32(x50, x60); s56_1 = vsubq_f32(x51, x61); + + float32x4_t y50 = vfmaq_n_f32(vfmaq_n_f32(vaddq_f32(x70, s12_0), + s34_0, 32.f), s56_0, 1.f/32); + float32x4_t y51 = vfmaq_n_f32(vfmaq_n_f32(vaddq_f32(x71, s12_1), + s34_1, 32.f), s56_1, 1.f/32); + float32x4_t y10 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 2.0f), s56_0, 0.5f); + float32x4_t y11 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 2.0f), s56_1, 0.5f); + float32x4_t y30 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 8.0f), s56_0, 0.125f); + float32x4_t y31 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 8.0f), s56_1, 0.125f); + float32x4_t y60 = vdupq_n_f32(0.f), y61 = y60, y70 = y60, y71 = y60; + + /* transpose 8x8 matrix in-place with some renumeration of the elements: */ + /* Y: */ + /* y00 y01 */ + /* y10 y11 */ + /* ... */ + /* y50 y51 */ + /* 0 0 */ + /* 0 0 */ + /* Y': */ + /* y00 y40 */ + /* y10 y50 */ + /* y20 y60 */ + /* y30 y70 */ + /* y01 y41 */ + /* y11 y51 */ + /* y21 y61 */ + /* y31 y71 */ + /* in other words, y40 <-> y01, y50 <-> y11, y60 <-> y21, y70 <-> y31 */ + float32x4x2_t tr0, tr1; + + T4x4(y00, y10, y20, y30, tr0, tr1); + T4x4(y01, y11, y21, y31, tr0, tr1); + T4x4(y40, y50, y60, y70, tr0, tr1); + T4x4(y41, y51, y61, y71, tr0, tr1); + + s12_0 = vaddq_f32(y10, y20); s12_1 = vaddq_f32(y50, y60); + s34_0 = vaddq_f32(y30, y01); s34_1 = vaddq_f32(y70, y41); + s56_0 = vaddq_f32(y11, y21); s56_1 = vaddq_f32(y51, y61); + + z00 = vaddq_f32(vaddq_f32(vaddq_f32(y00, s12_0), s34_0), s56_0); + z01 = vaddq_f32(vaddq_f32(vaddq_f32(y40, s12_1), s34_1), s56_1); + z20 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 4.0f), s56_0, 0.25f); + z21 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 4.0f), s56_1, 0.25f); + z40 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 16.0f), s56_0, 1.f/16); + z41 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 16.0f), s56_1, 1.f/16); + + s12_0 = vsubq_f32(y10, y20); s12_1 = vsubq_f32(y50, y60); + s34_0 = vsubq_f32(y30, y01); s34_1 = vsubq_f32(y70, y41); + s56_0 = vsubq_f32(y11, y21); s56_1 = vsubq_f32(y51, y61); + + z50 = vfmaq_n_f32(vfmaq_n_f32(vaddq_f32(y31, s12_0), + s34_0, 32.f), s56_0, 1.f/32); + z51 = vfmaq_n_f32(vfmaq_n_f32(vaddq_f32(y71, s12_1), + s34_1, 32.f), s56_1, 1.f/32); + z10 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 2.0f), s56_0, 0.5f); + z11 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 2.0f), s56_1, 0.5f); + z30 = vfmaq_n_f32(vfmaq_n_f32(s12_0, s34_0, 8.0f), s56_0, 0.125f); + z31 = vfmaq_n_f32(vfmaq_n_f32(s12_1, s34_1, 8.0f), s56_1, 0.125f); + float32x4_t vbias = vdupq_n_f32(bias); + + z00 = vaddq_f32(z00, vbias); + z01 = vaddq_f32(z01, vbias); + z10 = vaddq_f32(z10, vbias); + z11 = vaddq_f32(z11, vbias); + z20 = vaddq_f32(z20, vbias); + z21 = vaddq_f32(z21, vbias); + z30 = vaddq_f32(z30, vbias); + z31 = vaddq_f32(z31, vbias); + z40 = vaddq_f32(z40, vbias); + z41 = vaddq_f32(z41, vbias); + z50 = vaddq_f32(z50, vbias); + z51 = vaddq_f32(z51, vbias); + } + + if (bpptr) + { + float32x2_t zhalf = vdup_n_f32(0.f); + z00 = vaddq_f32(z00, vld1q_f32(bpptr)); + z01 = vaddq_f32(z01, vcombine_f32(vld1_f32(bpptr + 4), zhalf)); + z10 = vaddq_f32(z10, vld1q_f32(bpptr + bpstep)); + z11 = vaddq_f32(z11, vcombine_f32(vld1_f32(bpptr + bpstep + 4), zhalf)); + z20 = vaddq_f32(z20, vld1q_f32(bpptr + bpstep*2)); + z21 = vaddq_f32(z21, vcombine_f32(vld1_f32(bpptr + bpstep*2 + 4), zhalf)); + z30 = vaddq_f32(z30, vld1q_f32(bpptr + bpstep*3)); + z31 = vaddq_f32(z31, vcombine_f32(vld1_f32(bpptr + bpstep*3 + 4), zhalf)); + z40 = vaddq_f32(z40, vld1q_f32(bpptr + bpstep*4)); + z41 = vaddq_f32(z41, vcombine_f32(vld1_f32(bpptr + bpstep*4 + 4), zhalf)); + z50 = vaddq_f32(z50, vld1q_f32(bpptr + bpstep*5)); + z51 = vaddq_f32(z51, vcombine_f32(vld1_f32(bpptr + bpstep*5 + 4), zhalf)); + } + + if (ifMinMaxAct) + { + float32x4_t vmax = vdupq_n_f32(maxval); + float32x4_t vmin = vdupq_n_f32(minval); + + z00 = vminq_f32(vmaxq_f32(z00, vmin), vmax); + z01 = vminq_f32(vmaxq_f32(z01, vmin), vmax); + z10 = vminq_f32(vmaxq_f32(z10, vmin), vmax); + z11 = vminq_f32(vmaxq_f32(z11, vmin), vmax); + z20 = vminq_f32(vmaxq_f32(z20, vmin), vmax); + z21 = vminq_f32(vmaxq_f32(z21, vmin), vmax); + z30 = vminq_f32(vmaxq_f32(z30, vmin), vmax); + z31 = vminq_f32(vmaxq_f32(z31, vmin), vmax); + z40 = vminq_f32(vmaxq_f32(z40, vmin), vmax); + z41 = vminq_f32(vmaxq_f32(z41, vmin), vmax); + z50 = vminq_f32(vmaxq_f32(z50, vmin), vmax); + z51 = vminq_f32(vmaxq_f32(z51, vmin), vmax); + } + + vst1q_f32(outptr, z00); + vst1_f32(outptr + 4, vget_low_f32(z01)); + vst1q_f32(outptr + outstep, z10); + vst1_f32(outptr + outstep + 4, vget_low_f32(z11)); + vst1q_f32(outptr + outstep*2, z20); + vst1_f32(outptr + outstep*2 + 4, vget_low_f32(z21)); + vst1q_f32(outptr + outstep*3, z30); + vst1_f32(outptr + outstep*3 + 4, vget_low_f32(z31)); + vst1q_f32(outptr + outstep*4, z40); + vst1_f32(outptr + outstep*4 + 4, vget_low_f32(z41)); + vst1q_f32(outptr + outstep*5, z50); + vst1_f32(outptr + outstep*5 + 4, vget_low_f32(z51)); +#elif CV_SIMD128 + v_float32x4 x00 = v_load(inptr), x01 = v_load(inptr + 4); + v_float32x4 x10 = v_load(inptr + inpstep), x11 = v_load(inptr + inpstep + 4); + v_float32x4 x20 = v_load(inptr + inpstep*2), x21 = v_load(inptr + inpstep*2 + 4); + v_float32x4 x30 = v_load(inptr + inpstep*3), x31 = v_load(inptr + inpstep*3 + 4); + v_float32x4 x40 = v_load(inptr + inpstep*4), x41 = v_load(inptr + inpstep*4 + 4); + v_float32x4 x50 = v_load(inptr + inpstep*5), x51 = v_load(inptr + inpstep*5 + 4); + v_float32x4 x60 = v_load(inptr + inpstep*6), x61 = v_load(inptr + inpstep*6 + 4); + v_float32x4 x70 = v_load(inptr + inpstep*7), x71 = v_load(inptr + inpstep*7 + 4); + v_float32x4 z00, z01, z10, z11, z20, z21, z30, z31, z40, z41, z50, z51; + + { + v_float32x4 s12_0, s12_1, s34_0, s34_1, s56_0, s56_1; + s12_0 = x10 + x20; s12_1 = x11 + x21; + s34_0 = x30 + x40; s34_1 = x31 + x41; + s56_0 = x50 + x60; s56_1 = x51 + x61; + + v_float32x4 y00 = x00 + s12_0 + s34_0 + s56_0; + v_float32x4 y01 = x01 + s12_1 + s34_1 + s56_1; + + v_float32x4 a0 = v_setall_f32(0.25f), a1 = v_setall_f32(4.0f); + v_float32x4 y20 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + v_float32x4 y21 = v_fma(s56_1, a0 ,v_fma(s34_1, a1, s12_1) ); + + a0 = v_setall_f32(1.f/16), a1 = v_setall_f32(16.0f); + v_float32x4 y40 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + v_float32x4 y41 = v_fma(s56_1, a0, v_fma(s34_1, a1, s12_1)); + + s12_0 = x10 - x20; s12_1 = x11 - x21; + s34_0 = x30 - x40; s34_1 = x31 - x41; + s56_0 = x50 - x60; s56_1 = x51 - x61; + + a0 = v_setall_f32(1.f/32), a1 = v_setall_f32(32.f); + v_float32x4 y50 = v_fma(s56_0, a0, v_fma(s34_0, a1, x70 + s12_0)); + v_float32x4 y51 = v_fma(s56_1, a0, v_fma(s34_1, a1, x71 + s12_1)); + + a0 = v_setall_f32(0.5f), a1 = v_setall_f32(2.f); + v_float32x4 y10 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + v_float32x4 y11 = v_fma(s56_1, a0, v_fma(s34_1, a1, s12_1)); + + a0 = v_setall_f32(0.125f), a1 = v_setall_f32(8.f); + v_float32x4 y30 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + v_float32x4 y31 = v_fma(s56_1, a0, v_fma(s34_1, a1, s12_1)); + + v_float32x4 y60 = v_setall_f32(0.f), y61 = y60, y70 = y60, y71 = y60; + + /* transpose 8x8 matrix in-place with some renumeration of the elements: */ + /* Y: */ + /* y00 y01 */ + /* y10 y11 */ + /* ... */ + /* y50 y51 */ + /* 0 0 */ + /* 0 0 */ + /* Y': */ + /* y00 y40 */ + /* y10 y50 */ + /* y20 y60 */ + /* y30 y70 */ + /* y01 y41 */ + /* y11 y51 */ + /* y21 y61 */ + /* y31 y71 */ + /* in other words, y40 <-> y01, y50 <-> y11, y60 <-> y21, y70 <-> y31 */ + + v_transpose4x4(y00, y10, y20, y30, y00, y10, y20, y30); + v_transpose4x4(y01, y11, y21, y31, y01, y11, y21, y31); + v_transpose4x4(y40, y50, y60, y70, y40, y50, y60, y70); + v_transpose4x4(y41, y51, y61, y71, y41, y51, y61, y71); + + s12_0 = y10 + y20; s12_1 = y50 + y60; + s34_0 = y30 + y01; s34_1 = y70 + y41; + s56_0 = y11 + y21; s56_1 = y51 + y61; + + z00 = y00 + s12_0 + s34_0 + s56_0; + z01 = y40 + s12_1 + s34_1 + s56_1; + + a0 = v_setall_f32(0.25f), a1 = v_setall_f32(4.0f); + z20 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + z21 = v_fma(s56_1, a0, v_fma(s34_1, a1, s12_1)); + + a0 = v_setall_f32(1.f/16), a1 = v_setall_f32(16.0f); + z40 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + z41 = v_fma(s56_1, a0, v_fma(s34_1, a1, s12_1)); + + s12_0 = y10 - y20; s12_1 = y50 - y60; + s34_0 = y30 - y01; s34_1 = y70 - y41; + s56_0 = y11 - y21; s56_1 = y51 - y61; + + a0 = v_setall_f32(1.f/32), a1 = v_setall_f32(32.0f); + z50 = v_fma(s56_0, a0, v_fma(s34_0, a1, y31 + s12_0)); + z51 = v_fma(s56_1, a0, v_fma(s34_1, a1, y71 + s12_1)); + + a0 = v_setall_f32(0.5f), a1 = v_setall_f32(2.0f); + z10 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + z11 = v_fma(s56_1, a0, v_fma(s34_1, a1, s12_1)); + + a0 = v_setall_f32(0.125f), a1 = v_setall_f32(8.0f); + z30 = v_fma(s56_0, a0, v_fma(s34_0, a1, s12_0)); + z31 = v_fma(s56_1, a0, v_fma(s34_1, a1, s12_1)); + + v_float32x4 vbias = v_setall_f32(bias); + z00 += vbias; + z01 += vbias; + z10 += vbias; + z11 += vbias; + z20 += vbias; + z21 += vbias; + z30 += vbias; + z31 += vbias; + z40 += vbias; + z41 += vbias; + z50 += vbias; + z51 += vbias; + } + + if (bpptr) + { + z00 += v_load(bpptr); + z01 += v_load_low(bpptr + 4); + z10 += v_load(bpptr + bpstep); + z11 += v_load_low(bpptr + bpstep + 4); + z20 += v_load(bpptr + bpstep*2); + z21 += v_load_low(bpptr + bpstep*2 + 4); + z30 += v_load(bpptr + bpstep*3); + z31 += v_load_low(bpptr + bpstep*3 + 4); + z40 += v_load(bpptr + bpstep*4); + z41 += v_load_low(bpptr + bpstep*4 + 4); + z50 += v_load(bpptr + bpstep*5); + z51 += v_load_low(bpptr + bpstep*5 + 4); + } + + if (ifMinMaxAct) + { + v_float32x4 vmax = v_setall_f32(maxval); + v_float32x4 vmin = v_setall_f32(minval); + + z00 = v_min(v_max(z00, vmin), vmax); + z01 = v_min(v_max(z01, vmin), vmax); + z10 = v_min(v_max(z10, vmin), vmax); + z11 = v_min(v_max(z11, vmin), vmax); + z20 = v_min(v_max(z20, vmin), vmax); + z21 = v_min(v_max(z21, vmin), vmax); + z30 = v_min(v_max(z30, vmin), vmax); + z31 = v_min(v_max(z31, vmin), vmax); + z40 = v_min(v_max(z40, vmin), vmax); + z41 = v_min(v_max(z41, vmin), vmax); + z50 = v_min(v_max(z50, vmin), vmax); + z51 = v_min(v_max(z51, vmin), vmax); + } + + v_store(outptr, z00); + v_store_low(outptr + 4, z01); + v_store(outptr + outstep, z10); + v_store_low(outptr + outstep + 4, z11); + v_store(outptr + outstep*2, z20); + v_store_low(outptr + outstep*2 + 4, z21); + v_store(outptr + outstep*3, z30); + v_store_low(outptr + outstep*3 + 4, z31); + v_store(outptr + outstep*4, z40); + v_store_low(outptr + outstep*4 + 4, z41); + v_store(outptr + outstep*5, z50); + v_store_low(outptr + outstep*5 + 4, z51); +#else +#error "Only SIMD128, AVX2 and NEON are supported in Winograd." +#endif +} + +int runWinograd63(InputArray _input, InputArray _fusedAddMat, OutputArray _output, const Ptr& conv, + int ntasks, float minval, float maxval, ActivationLayer* activ, bool ifMinMaxAct) { Mat input = _input.getMat(); Mat output = _output.getMat(); + Mat fusedAddMat = _fusedAddMat.getMat(); MatShape inputShape = shape(input); MatShape outputShape = shape(output); @@ -491,942 +935,219 @@ int runWinograd63(InputArray _input, OutputArray _output, const Ptr& int K = conv->K; int H0 = outputShape[2], W0 = outputShape[3]; - // Allocate the right memory size for output. - // H and W is integer of 6. the output HxW is integer of 6x6 - int H_tiles = ((H0 + 5) / 6); - int W_tiles = ((W0 + 5) / 6); - int tiles = H_tiles * W_tiles; - - int H0_align = H_tiles * 6; - int W0_align = W_tiles * 6; - - int Hi_align = H0_align + 2; - int Wi_align = W0_align + 2; - - int pad_top = conv->pad_top, pad_bottom = Hi_align - pad_top - Hi; - int pad_left = conv->pad_left, pad_right = Wi_align - pad_left - Wi; - - int in_top = pad_top, in_bottom = Hi_align - pad_bottom; - int in_left = pad_left, in_right = Wi_align - pad_right; - - CV_Assert(in_bottom >= in_top && in_right >= in_left); - - int C_aligned = ((C + FAST_VEC_NLANES - 1)/FAST_VEC_NLANES) * FAST_VEC_NLANES; - int K_aligned = ((K + FAST_VEC_NLANES - 1)/FAST_VEC_NLANES) * FAST_VEC_NLANES; - - int inpPack = 0; - int lineNum =0; - - // TODO! tiles > 12 -//#if CV_NEON_AARCH64 -// if (tiles >= 12) -// { -// inpPack = 12; -// lineNum = tiles / 12 + (tiles % 12) / 8 + (tiles % 12 % 8) / 4 + (tiles % 12 % 4) / 2 + tiles % 12 % 2; -// } -// else -//#endif - if (tiles >= 8) - { - inpPack = 8; - lineNum = tiles / 8 + (tiles % 8) / 4 + (tiles % 4) / 2 + tiles % 2; - } - else - if (tiles >= 4) - { - inpPack = 4; - lineNum = tiles / 4 + (tiles % 4) / 2 + tiles % 2; - } - else if (tiles >= 2) - { - inpPack = 2; - lineNum = tiles / 2 + tiles % 2; - } - else // tiles >= 1 - { - inpPack = 1; - lineNum = tiles; - } - CV_Assert(lineNum > 0 && inpPack > 0); - std::vector ofstab0_(tiles * 2, 0); - int* ofstab0 = ofstab0_.data(); // [line Number, input pack] - - int tiles_tmp = tiles; - int line_0 = 0; - - int* ofstab_tmp = ofstab0; - int big_step = inpPack * C_aligned * lineNum; - int line_step = inpPack * C_aligned; - - std::vector linePackList = {12, 8, 4, 2, 1}; - auto iter = std::find(linePackList.begin(), linePackList.end(), inpPack); - CV_Assert(iter != linePackList.end()); - int ptr = iter - linePackList.begin(); - - while (ptr < linePackList.size() && tiles_tmp != 0) - { - if (tiles_tmp >= linePackList[ptr]) - { - int num = tiles_tmp / linePackList[ptr]; - for (int i = 0; i < num; i ++) - { - for (int j = 0; j < linePackList[ptr]; j++) - { - ofstab_tmp[0] = line_0; - ofstab_tmp[1] = linePackList[ptr]; - ofstab_tmp += 2; - } - line_0++; - } - tiles_tmp -= num * linePackList[ptr]; - } - else - { - ptr++; - } - } + int pad_top = conv->pad_top; + int pad_left = conv->pad_left; + int ngroups = conv->ngroups, Cg = C/ngroups, Kg = K/ngroups; + int Kg_nblocks = (Kg + _FX_WINO_KBLOCK - 1)/_FX_WINO_KBLOCK; + const size_t inp_planesize = (size_t)Hi*Wi; const size_t out_planesize = (size_t)H0*W0; - size_t inputbuf_size = inpPack * C_aligned * lineNum * 64; - size_t inputbufCn_size = ntasks * tiles * 4 * 8 * 8; + int blocks_per_row = (W0+_FX_WINO_STEP-1)/_FX_WINO_STEP; + int blocks_per_plane = ((H0+_FX_WINO_STEP-1)/_FX_WINO_STEP)*blocks_per_row; + int blocks_per_plane_aligned = ((blocks_per_plane + + _FX_WINO_IBLOCK-1)/_FX_WINO_IBLOCK)*_FX_WINO_IBLOCK; - size_t outputbuf_size = tiles * K_aligned * 8 * 8; - size_t outputCnbuf_size = ntasks * 8 * 8 * 4; + size_t totalbufsize = (size_t)N*C*blocks_per_plane_aligned*_FX_WINO_AREA; - AutoBuffer inputbuf0_, inputCnbuf0_, outputbuf0_, outputCnbuf0_; + AutoBuffer _buf; + _buf.allocate(totalbufsize + VEC_ALIGN); + float* wbuf_all = alignPtr(_buf.data(), VEC_ALIGN); - inputbuf0_.allocate(inputbuf_size); - float* inputbuf0 = alignPtr(inputbuf0_.data(), (int)(sizeof(float))); - memset(inputbuf0, 0, inputbuf_size * sizeof(float )); + float* inp = input.ptr(); + float* out = output.ptr(); - inputCnbuf0_.allocate(inputbufCn_size); - float* inputCnbuf0 = inputCnbuf0_.data(); + float* fusedAddPtr = fusedAddMat.empty() ? nullptr : fusedAddMat.ptr(); - outputbuf0_.allocate(outputbuf_size); - float* outputbuf0 = outputbuf0_.data(); - - outputCnbuf0_.allocate(outputCnbuf_size); - float* outputCnbuf0 = outputCnbuf0_.data(); - - // Input Parallel For - float* weight_ptr0 = conv->weightsWino63Buf.data(); - for (int bn = 0; bn < N; bn++) + // Phase 1. compute forward Winograd transforms for all input blocks, + // all input planes, all samples in the batch. + // [TODO]: maybe, if there are too many input channels, it makes sense to + // transform only part of input channels at once and then compute the partial + // accumulated sums (i.e. update the output buffers several times, + // rather than compute them in one pass). + parallel_for_(Range(0, ntasks), [&](const Range& r0) { + for (int task_id = r0.start; task_id < r0.end; task_id++) { - float* input_ptr0 = input.ptr() + bn * Hi * Wi * C; - float* output_ptr0 = output.ptr() + bn * out_planesize * K; - - // Transform Input - int C_aligned_div4 = C_aligned/4; - - parallel_for_(Range(0, ntasks), [&](const Range& range) + int nc0 = (N*C)*task_id/ntasks; + int nc1 = (N*C)*(task_id+1)/ntasks; + for(; nc0 < nc1; nc0++) { - for (int task_i = range.start; task_i < range.end; task_i++) + int n = nc0 / C; + int c = nc0 - n*C; + int g = c / Cg; + c -= g*Cg; + for (int block_id = 0; block_id < blocks_per_plane; block_id += _FX_WINO_IBLOCK) { - float *inpCnbuf = inputCnbuf0 + tiles * 256 * task_i; - for (int inc4 = task_i; inc4 < C_aligned_div4; inc4 += ntasks) + for (int db = 0; db < _FX_WINO_IBLOCK; db++) { - for (int cn = 0; cn < 4; cn++) + size_t inwofs = ((n*ngroups + g)*blocks_per_plane_aligned + + block_id)*Cg*_FX_WINO_AREA + + (c*_FX_WINO_IBLOCK + db)*_FX_WINO_ATOM_F32; + float* inwptr = (float*)wbuf_all + inwofs; + + if (block_id + db < blocks_per_plane) { - if (cn + inc4 * 4 >= C) + int y0 = (block_id + db) / blocks_per_row; + int x0 = (block_id + db) - y0 * blocks_per_row; + y0 = y0*_FX_WINO_STEP - pad_top; + x0 = x0*_FX_WINO_STEP - pad_left; + bool partial = y0 < 0 || y0 + _FX_WINO_SIZE > Hi || + x0 < 0 || x0 + _FX_WINO_SIZE > Wi; + int dx1 = 0, dx2 = _FX_WINO_SIZE, dy1 = 0, dy2 = _FX_WINO_SIZE; + int inpstep = Wi; + + float inpbuf[_FX_WINO_AREA]; + float* inptr0 = (float*)inp + nc0*inp_planesize + y0*Wi + x0; + float* inptr = inptr0; + + if (partial) { - // set value to zero - for (int ti = 0; ti < tiles; ti++) + memset(inpbuf, 0, sizeof(inpbuf)); + dy1 = -y0 > 0 ? -y0 : 0; + dy2 = Hi - y0 < _FX_WINO_SIZE ? Hi - y0 : _FX_WINO_SIZE; + + if (dy2 < dy1) {dy2 = dy1 = 0;} + dx1 = -x0 > 0 ? -x0 : 0; + dx2 = Wi - x0 < _FX_WINO_SIZE ? Wi - x0 : _FX_WINO_SIZE; + + if (dx2 < dx1) {dx2 = dx1 = 0;} + inptr0 -= y0*Wi + x0; + + if (dx1 < dx2 && dy1 < dy2) { - float *inpCnbuf_i = inpCnbuf + ti * 4 * 64 + cn; - - for (int i = 0; i < 8; i++) - { - inpCnbuf_i[0] = 0.0f; - inpCnbuf_i[4] = 0.0f; - inpCnbuf_i[8] = 0.0f; - inpCnbuf_i[12] = 0.0f; - - inpCnbuf_i[16] = 0.0f; - inpCnbuf_i[20] = 0.0f; - inpCnbuf_i[24] = 0.0f; - inpCnbuf_i[28] = 0.0f; - - inpCnbuf_i += 4 * 8; - } + for(int dy = dy1; dy < dy2; dy++) + memcpy(&inpbuf[dy*_FX_WINO_SIZE + dx1], + inptr0 + (y0+dy)*Wi + (x0+dx1), + (dx2-dx1)*sizeof(inpbuf[0])); } + + inptr = inpbuf; + inpstep = _FX_WINO_SIZE; } +#if CV_TRY_AVX2 + if (conv->useAVX2) + opt_AVX2::_fx_winograd_BtXB_8x8_f32(inptr, inpstep, inwptr, Cg); else - { - float *input_ptr = input_ptr0 + (inc4 * 4 + cn) * Hi * Wi; - - for (int ti = 0; ti < tiles; ti++) - { - float *input_buf0_i = inpCnbuf + ti * 256 + cn; - - int hi = ti / W_tiles; - int wi = ti % W_tiles; - - int h_top = hi * 6, h_bottom = hi * 6 + 8; - int w_left = wi * 6, w_right = wi * 6 + 8; - - for (int h = h_top; h < h_bottom; h++) - { - if (h >= in_bottom || h < in_top) - { - input_buf0_i[0] = 0.0f; - input_buf0_i[4] = 0.0f; - input_buf0_i[8] = 0.0f; - input_buf0_i[12] = 0.0f; - - input_buf0_i[16] = 0.0f; - input_buf0_i[20] = 0.0f; - input_buf0_i[24] = 0.0f; - input_buf0_i[28] = 0.0f; - - input_buf0_i += 32; - continue; - } - - for (int w = w_left; w < w_right; w++) - { - if (w >= in_right || w < in_left) - { - input_buf0_i[0] = 0.0f; - input_buf0_i += 4; - continue; - } - input_buf0_i[0] = input_ptr[(h - pad_top) * Wi + w - pad_left]; - input_buf0_i += 4; - } - } - } - } +#endif + _fx_winograd_BtXB_8x8_f32(inptr, inpstep, inwptr, Cg); } - - // Transfor Compute BdB^T - winograd_trans_input_F63(inpCnbuf, inputbuf0, inc4, tiles, big_step, line_step, ofstab0); - } - } - }); - - // Matrix multiplication 8 channel - int K_div8 = 0; - -#if CV_NEON_AARCH64 - K_div8 = K_aligned/8; - - parallel_for_(Range(0, K_div8), [&](const Range &range){ - for (int outcn = range.start; outcn < range.end; outcn ++) - { - float* output_tmp = outputbuf0 + tiles * outcn * 8; - float* kernel_tmp = weight_ptr0 + outcn * 8 * C_aligned; - for (int r = 0; r < 64; r++) - { - float* input_tm = inputbuf0 + r * big_step; - float* output0_tm = output_tmp + tiles * K_aligned * r; - float* output1_tm = output0_tm + tiles * 4; - float* kernel_tm_i = kernel_tmp + r * C_aligned * K_aligned; - - int ti = 0; - for (; ti + 7 < tiles; ti += 8) - { - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k01 = kernel_tm_i; - - int nn = C_aligned/4; - - // init 32 registers. FMA/load ratio = 64/16 - float32x4_t r00 = vdupq_n_f32(0.0f), r01 = r00, r02 = r00, r03 = r00; - float32x4_t r04 = r00, r05 = r00, r06 = r00, r07 = r00; - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r12 = r00, r13 = r00, r14 = r00, r15 = r00; - float32x4_t r16 = r00, r17 = r00, r18 = r00, r19 = r00; - float32x4_t r20 = r00, r21 = r00, r22 = r00, r23 = r00; - float32x4_t r24 = r00, r25 = r00, r26 = r00, r27 = r00; - float32x4_t r28 = r00, r29 = r00, r30 = r00, r31 = r00; - - for(;nn > 0; nn--) + else { - r00 = vld1q_f32(r0), r01 = vld1q_f32(r0+4), r02 = vld1q_f32(r0+8), r03 = vld1q_f32(r0+12); - r08 = vld1q_f32(k01), r09 = vld1q_f32(k01+4), r10 = vld1q_f32(k01+8), r11 = vld1q_f32(k01+12); - r0 += 16, k01 += 16; - - r16 = vfmaq_laneq_f32(r16, r08, r00, 0); - r17 = vfmaq_laneq_f32(r17, r08, r01, 0); - r18 = vfmaq_laneq_f32(r18, r08, r02, 0); - r19 = vfmaq_laneq_f32(r19, r08, r03, 0); - - r04 = vld1q_f32(r0), r05 = vld1q_f32(r0+4), r06 = vld1q_f32(r0+8), r07 = vld1q_f32(r0+12); - r0 += 16; - - r20 = vfmaq_laneq_f32(r20, r08, r04, 0); - r21 = vfmaq_laneq_f32(r21, r08, r05, 0); - r22 = vfmaq_laneq_f32(r22, r08, r06, 0); - r23 = vfmaq_laneq_f32(r23, r08, r07, 0); - - r24 = vfmaq_laneq_f32(r24, r09, r00, 0); - r25 = vfmaq_laneq_f32(r25, r09, r01, 0); - r26 = vfmaq_laneq_f32(r26, r09, r02, 0); - r27 = vfmaq_laneq_f32(r27, r09, r03, 0); - r28 = vfmaq_laneq_f32(r28, r09, r04, 0); - r29 = vfmaq_laneq_f32(r29, r09, r05, 0); - r30 = vfmaq_laneq_f32(r30, r09, r06, 0); - r31 = vfmaq_laneq_f32(r31, r09, r07, 0); - - r12 = vld1q_f32(k01), r13 = vld1q_f32(k01+4), r14 = vld1q_f32(k01+8), r15 = vld1q_f32(k01+12); - k01 += 16; - - r16 = vfmaq_laneq_f32(r16, r10, r00, 1); - r17 = vfmaq_laneq_f32(r17, r10, r01, 1); - r18 = vfmaq_laneq_f32(r18, r10, r02, 1); - r19 = vfmaq_laneq_f32(r19, r10, r03, 1); - r20 = vfmaq_laneq_f32(r20, r10, r04, 1); - r21 = vfmaq_laneq_f32(r21, r10, r05, 1); - r22 = vfmaq_laneq_f32(r22, r10, r06, 1); - r23 = vfmaq_laneq_f32(r23, r10, r07, 1); - - r24 = vfmaq_laneq_f32(r24, r11, r00, 1); - r25 = vfmaq_laneq_f32(r25, r11, r01, 1); - r26 = vfmaq_laneq_f32(r26, r11, r02, 1); - r27 = vfmaq_laneq_f32(r27, r11, r03, 1); - r28 = vfmaq_laneq_f32(r28, r11, r04, 1); - r29 = vfmaq_laneq_f32(r29, r11, r05, 1); - r30 = vfmaq_laneq_f32(r30, r11, r06, 1); - r31 = vfmaq_laneq_f32(r31, r11, r07, 1); - - r16 = vfmaq_laneq_f32(r16, r12, r00, 2); - r17 = vfmaq_laneq_f32(r17, r12, r01, 2); - r18 = vfmaq_laneq_f32(r18, r12, r02, 2); - r19 = vfmaq_laneq_f32(r19, r12, r03, 2); - r20 = vfmaq_laneq_f32(r20, r12, r04, 2); - r21 = vfmaq_laneq_f32(r21, r12, r05, 2); - r22 = vfmaq_laneq_f32(r22, r12, r06, 2); - r23 = vfmaq_laneq_f32(r23, r12, r07, 2); - - r24 = vfmaq_laneq_f32(r24, r13, r00, 2); - r25 = vfmaq_laneq_f32(r25, r13, r01, 2); - r26 = vfmaq_laneq_f32(r26, r13, r02, 2); - r27 = vfmaq_laneq_f32(r27, r13, r03, 2); - r28 = vfmaq_laneq_f32(r28, r13, r04, 2); - r29 = vfmaq_laneq_f32(r29, r13, r05, 2); - r30 = vfmaq_laneq_f32(r30, r13, r06, 2); - r31 = vfmaq_laneq_f32(r31, r13, r07, 2); - - r16 = vfmaq_laneq_f32(r16, r14, r00, 3); - r17 = vfmaq_laneq_f32(r17, r14, r01, 3); - r18 = vfmaq_laneq_f32(r18, r14, r02, 3); - r19 = vfmaq_laneq_f32(r19, r14, r03, 3); - r20 = vfmaq_laneq_f32(r20, r14, r04, 3); - r21 = vfmaq_laneq_f32(r21, r14, r05, 3); - r22 = vfmaq_laneq_f32(r22, r14, r06, 3); - r23 = vfmaq_laneq_f32(r23, r14, r07, 3); - - r24 = vfmaq_laneq_f32(r24, r15, r00, 3); - r25 = vfmaq_laneq_f32(r25, r15, r01, 3); - r26 = vfmaq_laneq_f32(r26, r15, r02, 3); - r27 = vfmaq_laneq_f32(r27, r15, r03, 3); - r28 = vfmaq_laneq_f32(r28, r15, r04, 3); - r29 = vfmaq_laneq_f32(r29, r15, r05, 3); - r30 = vfmaq_laneq_f32(r30, r15, r06, 3); - r31 = vfmaq_laneq_f32(r31, r15, r07, 3); + for (int i = 0; i < _FX_WINO_NATOMS_F32; i++, inwptr += _FX_WINO_IBLOCK*_FX_WINO_ATOM_F32) + memset(inwptr, 0, _FX_WINO_ATOM_F32*sizeof(inwptr[0])); } - - vst1q_f32(output0_tm, r16), vst1q_f32(output0_tm + 4, r17), vst1q_f32(output0_tm + 8, r18), vst1q_f32(output0_tm + 12, r19); - output0_tm += 16; - vst1q_f32(output1_tm, r24), vst1q_f32(output1_tm + 4, r25), vst1q_f32(output1_tm + 8, r26), vst1q_f32(output1_tm + 12, r27); - output1_tm += 16; - - vst1q_f32(output0_tm, r20), vst1q_f32(output0_tm + 4, r21), vst1q_f32(output0_tm + 8, r22), vst1q_f32(output0_tm + 12, r23); - output0_tm += 16; - vst1q_f32(output1_tm, r28), vst1q_f32(output1_tm + 4, r29), vst1q_f32(output1_tm + 8, r30), vst1q_f32(output1_tm + 12, r31); - output1_tm += 16; - } - - for (; ti + 3 < tiles; ti += 4) - { - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k01 = kernel_tm_i; - - int nn = C_aligned/4; - - // init 20 registers. FMA/load ratio = 32/12 - float32x4_t r00 = vdupq_n_f32(0.0f), r01 = r00, r02 = r00, r03 = r00; - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r12 = r00, r13 = r00, r14 = r00, r15 = r00; - float32x4_t r24 = r00, r25 = r00, r26 = r00, r27 = r00; - float32x4_t r28 = r00, r29 = r00, r30 = r00, r31 = r00; - - for(; nn > 0; nn--) - { - r00 = vld1q_f32(r0), r01 = vld1q_f32(r0+4), r02 = vld1q_f32(r0+8), r03 = vld1q_f32(r0+12); - r08 = vld1q_f32(k01), r09 = vld1q_f32(k01+4), r10 = vld1q_f32(k01+8), r11 = vld1q_f32(k01+12); - r0 += 16, k01 += 16; - - r24 = vfmaq_laneq_f32(r24, r08, r00, 0); - r25 = vfmaq_laneq_f32(r25, r08, r01, 0); - r26 = vfmaq_laneq_f32(r26, r08, r02, 0); - r27 = vfmaq_laneq_f32(r27, r08, r03, 0); - - r28 = vfmaq_laneq_f32(r28, r09, r00, 0); - r29 = vfmaq_laneq_f32(r29, r09, r01, 0); - r30 = vfmaq_laneq_f32(r30, r09, r02, 0); - r31 = vfmaq_laneq_f32(r31, r09, r03, 0); - - r12 = vld1q_f32(k01), r13 = vld1q_f32(k01+4), r14 = vld1q_f32(k01+8), r15 = vld1q_f32(k01+12); - k01 += 16; - - r24 = vfmaq_laneq_f32(r24, r10, r00, 1); - r25 = vfmaq_laneq_f32(r25, r10, r01, 1); - r26 = vfmaq_laneq_f32(r26, r10, r02, 1); - r27 = vfmaq_laneq_f32(r27, r10, r03, 1); - - r28 = vfmaq_laneq_f32(r28, r11, r00, 1); - r29 = vfmaq_laneq_f32(r29, r11, r01, 1); - r30 = vfmaq_laneq_f32(r30, r11, r02, 1); - r31 = vfmaq_laneq_f32(r31, r11, r03, 1); - - r24 = vfmaq_laneq_f32(r24, r12, r00, 2); - r25 = vfmaq_laneq_f32(r25, r12, r01, 2); - r26 = vfmaq_laneq_f32(r26, r12, r02, 2); - r27 = vfmaq_laneq_f32(r27, r12, r03, 2); - - r28 = vfmaq_laneq_f32(r28, r13, r00, 2); - r29 = vfmaq_laneq_f32(r29, r13, r01, 2); - r30 = vfmaq_laneq_f32(r30, r13, r02, 2); - r31 = vfmaq_laneq_f32(r31, r13, r03, 2); - - r24 = vfmaq_laneq_f32(r24, r14, r00, 3); - r25 = vfmaq_laneq_f32(r25, r14, r01, 3); - r26 = vfmaq_laneq_f32(r26, r14, r02, 3); - r27 = vfmaq_laneq_f32(r27, r14, r03, 3); - - r28 = vfmaq_laneq_f32(r28, r15, r00, 3); - r29 = vfmaq_laneq_f32(r29, r15, r01, 3); - r30 = vfmaq_laneq_f32(r30, r15, r02, 3); - r31 = vfmaq_laneq_f32(r31, r15, r03, 3); - } - - vst1q_f32(output0_tm, r24), vst1q_f32(output0_tm + 4, r25), vst1q_f32(output0_tm + 8, r26), vst1q_f32(output0_tm + 12, r27); - output0_tm += 16; - vst1q_f32(output1_tm, r28), vst1q_f32(output1_tm + 4, r29), vst1q_f32(output1_tm + 8, r30), vst1q_f32(output1_tm + 12, r31); - output1_tm += 16; - } - - for (; ti + 1 < tiles; ti += 2) - { - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k01 = kernel_tm_i; - - int nn = C_aligned/4; - - // init 14 registers. FMA/load ratio = 15/10 - float32x4_t r00 = vdupq_n_f32(0.0f), r01 = r00; - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r12 = r00, r13 = r00, r14 = r00, r15 = r00; - float32x4_t r24 = r00, r25 = r00; - float32x4_t r28 = r00, r29 = r00; - - for (; nn > 0; nn--) - { - r00 = vld1q_f32(r0), r01 = vld1q_f32(r0+4); - r08 = vld1q_f32(k01), r09 = vld1q_f32(k01+4), r10 = vld1q_f32(k01+8), r11 = vld1q_f32(k01+12); - r0 += 8, k01 += 16; - - r24 = vfmaq_laneq_f32(r24, r08, r00, 0); - r25 = vfmaq_laneq_f32(r25, r08, r01, 0); - - r28 = vfmaq_laneq_f32(r28, r09, r00, 0); - r29 = vfmaq_laneq_f32(r29, r09, r01, 0); - - r12 = vld1q_f32(k01), r13 = vld1q_f32(k01+4), r14 = vld1q_f32(k01+8), r15 = vld1q_f32(k01+12); - k01 += 16; - - r24 = vfmaq_laneq_f32(r24, r10, r00, 1); - r25 = vfmaq_laneq_f32(r25, r10, r01, 1); - - r28 = vfmaq_laneq_f32(r28, r11, r00, 1); - r29 = vfmaq_laneq_f32(r29, r11, r01, 1); - - r24 = vfmaq_laneq_f32(r24, r12, r00, 2); - r25 = vfmaq_laneq_f32(r25, r12, r01, 2); - - r28 = vfmaq_laneq_f32(r28, r13, r00, 2); - r29 = vfmaq_laneq_f32(r29, r13, r01, 2); - - r24 = vfmaq_laneq_f32(r24, r14, r00, 3); - r25 = vfmaq_laneq_f32(r25, r14, r01, 3); - - r28 = vfmaq_laneq_f32(r28, r15, r00, 3); - r29 = vfmaq_laneq_f32(r29, r15, r01, 3); - } - - vst1q_f32(output0_tm, r24), vst1q_f32(output0_tm + 4, r25); - output0_tm += 8; - vst1q_f32(output1_tm, r28), vst1q_f32(output1_tm + 4, r29); - output1_tm += 8; - } - - for (; ti < tiles; ti ++) - { - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k01 = kernel_tm_i; - - int nn = C_aligned/4; - - float32x4_t r00 = vdupq_n_f32(0.0f); - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r12 = r00, r13 = r00, r14 = r00, r15 = r00; - float32x4_t r24 = r00; - float32x4_t r28 = r00; - - for(;nn > 0; nn--) - { - r00 = vld1q_f32(r0); - r08 = vld1q_f32(k01), r09 = vld1q_f32(k01+4), r10 = vld1q_f32(k01+8), r11 = vld1q_f32(k01+12); - r0 += 4, k01 += 16; - - r24 = vfmaq_laneq_f32(r24, r08, r00, 0); - r28 = vfmaq_laneq_f32(r28, r09, r00, 0); - - r12 = vld1q_f32(k01), r13 = vld1q_f32(k01+4), r14 = vld1q_f32(k01+8), r15 = vld1q_f32(k01+12); - k01 += 16; - - r24 = vfmaq_laneq_f32(r24, r10, r00, 1); - r28 = vfmaq_laneq_f32(r28, r11, r00, 1); - - r24 = vfmaq_laneq_f32(r24, r12, r00, 2); - r28 = vfmaq_laneq_f32(r28, r13, r00, 2); - - r24 = vfmaq_laneq_f32(r24, r14, r00, 3); - r28 = vfmaq_laneq_f32(r28, r15, r00, 3); - } - - vst1q_f32(output0_tm, r24); - output0_tm += 4; - vst1q_f32(output1_tm, r28); - output1_tm += 4; } } } - }); -#endif + }}); - // Matrix multiplication, 4 output channel. - int Ock_div4 = (K_aligned - K_div8 * 8) / 4; - parallel_for_(Range(0, Ock_div4), [&](const Range &range){ - for (int outcn = range.start; outcn < range.end; outcn++) - { - float* output_tmp = outputbuf0 + tiles * (outcn + K_div8 * 2)* 4; - float* kernel_tmp = weight_ptr0 + (outcn + K_div8 * 2) * 4 * C_aligned; + // Phase 2. compute elemwise-weighted sums of transformed blocks, + // apply inverse Winograd transforms to the sums, + // add bias, apply activation function if any and store the results. + parallel_for_(Range(0, ntasks), [&](const Range& r0) { + for (int task_id = r0.start; task_id < r0.end; task_id++) + { + size_t out_wbuf_size = _FX_WINO_AREA*_FX_WINO_KBLOCK*_FX_WINO_IBLOCK; + size_t outbuf_size = _FX_WINO_AREA; + AutoBuffer out_wbuf_, outbuf_; + out_wbuf_.allocate(out_wbuf_size + VEC_ALIGN); + float* out_wbuf = alignPtr(out_wbuf_.data(), VEC_ALIGN); + outbuf_.allocate(outbuf_size + VEC_ALIGN); + float* outbuf = alignPtr(outbuf_.data(), VEC_ALIGN); - for (int r = 0; r < 64; r++) - { - float *input_tm = inputbuf0 + r * big_step; - float *output0_tm = output_tmp + tiles * K_aligned * r; - float *kernel_tm_i = kernel_tmp + r * C_aligned * K_aligned; + memset(out_wbuf, 0, out_wbuf_size * sizeof(float)); + memset(outbuf, 0, outbuf_size * sizeof(float)); - int ti = 0; - for (; ti + 7 < tiles; ti += 8) - { - int nn = C_aligned/4; - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k0 = kernel_tm_i; + int ngk0 = (int)(((int64_t)N*Kg_nblocks*ngroups)*task_id/ntasks); + int ngk1 = (int)(((int64_t)N*Kg_nblocks*ngroups)*(task_id+1)/ntasks); -#if CV_NEON_AARCH64 - // init 24 registers. FMA/load ratio = 32/12 - float32x4_t r00 = vdupq_n_f32(0.0f), r01 = r00, r02 = r00, r03 = r00; - float32x4_t r04 = r00, r05 = r00, r06 = r00, r07 = r00; - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r16 = r00, r17 = r00, r18 = r00, r19 = r00; - float32x4_t r20 = r00, r21 = r00, r22 = r00, r23 = r00; - - for(; nn > 0; nn--) - { - r00 = vld1q_f32(r0), r01 = vld1q_f32(r0+4), r02 = vld1q_f32(r0+8), r03 = vld1q_f32(r0+12); - r08 = vld1q_f32(k0), r09 = vld1q_f32(k0+4), r10 = vld1q_f32(k0+8), r11 = vld1q_f32(k0+12); - r0 += 16, k0 += 16; - - r16 = vfmaq_laneq_f32(r16, r08, r00, 0); - r17 = vfmaq_laneq_f32(r17, r08, r01, 0); - r18 = vfmaq_laneq_f32(r18, r08, r02, 0); - r19 = vfmaq_laneq_f32(r19, r08, r03, 0); - - r04 = vld1q_f32(r0), r05 = vld1q_f32(r0+4), r06 = vld1q_f32(r0+8), r07 = vld1q_f32(r0+12); - r0 += 16; - - r20 = vfmaq_laneq_f32(r20, r08, r04, 0); - r21 = vfmaq_laneq_f32(r21, r08, r05, 0); - r22 = vfmaq_laneq_f32(r22, r08, r06, 0); - r23 = vfmaq_laneq_f32(r23, r08, r07, 0); - - r16 = vfmaq_laneq_f32(r16, r09, r00, 1); - r17 = vfmaq_laneq_f32(r17, r09, r01, 1); - r18 = vfmaq_laneq_f32(r18, r09, r02, 1); - r19 = vfmaq_laneq_f32(r19, r09, r03, 1); - r20 = vfmaq_laneq_f32(r20, r09, r04, 1); - r21 = vfmaq_laneq_f32(r21, r09, r05, 1); - r22 = vfmaq_laneq_f32(r22, r09, r06, 1); - r23 = vfmaq_laneq_f32(r23, r09, r07, 1); - - r16 = vfmaq_laneq_f32(r16, r10, r00, 2); - r17 = vfmaq_laneq_f32(r17, r10, r01, 2); - r18 = vfmaq_laneq_f32(r18, r10, r02, 2); - r19 = vfmaq_laneq_f32(r19, r10, r03, 2); - r20 = vfmaq_laneq_f32(r20, r10, r04, 2); - r21 = vfmaq_laneq_f32(r21, r10, r05, 2); - r22 = vfmaq_laneq_f32(r22, r10, r06, 2); - r23 = vfmaq_laneq_f32(r23, r10, r07, 2); - - r16 = vfmaq_laneq_f32(r16, r11, r00, 3); - r17 = vfmaq_laneq_f32(r17, r11, r01, 3); - r18 = vfmaq_laneq_f32(r18, r11, r02, 3); - r19 = vfmaq_laneq_f32(r19, r11, r03, 3); - r20 = vfmaq_laneq_f32(r20, r11, r04, 3); - r21 = vfmaq_laneq_f32(r21, r11, r05, 3); - r22 = vfmaq_laneq_f32(r22, r11, r06, 3); - r23 = vfmaq_laneq_f32(r23, r11, r07, 3); - } - - vst1q_f32(output0_tm, r16), vst1q_f32(output0_tm + 4, r17), vst1q_f32(output0_tm + 8, r18), vst1q_f32(output0_tm + 12, r19); - output0_tm += 16; - - vst1q_f32(output0_tm, r20), vst1q_f32(output0_tm + 4, r21), vst1q_f32(output0_tm + 8, r22), vst1q_f32(output0_tm + 12, r23); - output0_tm += 16; - -#else // ARMv7 16 registers. - - // init 16 registers. FMA/load ratio = 32/12 - float32x2_t q00 = vdup_n_f32(0.0f), q01 = q00, q02 = q00, q03 = q00, - q04 = q00, q05 = q00, q06 = q00, q07 = q00; - - float32x4_t r04 = vdupq_n_f32(0.0f), r05 = r04, r06 = r04, r07 = r04; - float32x4_t r08 = r04, r09 = r04, r10 = r04, r11 = r04; - float32x4_t r12 = r04, r13 = r04, r14 = r04, r15 = r04; - - for (; nn > 0; nn--) - { - q00 = vld1_f32(r0), q01 = vld1_f32(r0+2), q02 = vld1_f32(r0+4), q03 = vld1_f32(r0+6); - q04 = vld1_f32(r0+8), q05 = vld1_f32(r0+10), q06 = vld1_f32(r0+12), q07 = vld1_f32(r0+14); - r04 = vld1q_f32(k0), r05 = vld1q_f32(k0+4), r06 = vld1q_f32(k0+8), r07 = vld1q_f32(k0+12); - r0 += 16, k0 += 16; - - r08 = vmlaq_lane_f32(r08, r04, q00, 0); - r09 = vmlaq_lane_f32(r09, r04, q02, 0); - r10 = vmlaq_lane_f32(r10, r04, q04, 0); - r11 = vmlaq_lane_f32(r11, r04, q06, 0); - - r08 = vmlaq_lane_f32(r08, r05, q00, 1); - r09 = vmlaq_lane_f32(r09, r05, q02, 1); - r10 = vmlaq_lane_f32(r10, r05, q04, 1); - r11 = vmlaq_lane_f32(r11, r05, q06, 1); - - r08 = vmlaq_lane_f32(r08, r06, q01, 0); - r09 = vmlaq_lane_f32(r09, r06, q03, 0); - r10 = vmlaq_lane_f32(r10, r06, q05, 0); - r11 = vmlaq_lane_f32(r11, r06, q07, 0); - - r08 = vmlaq_lane_f32(r08, r07, q01, 1); - r09 = vmlaq_lane_f32(r09, r07, q03, 1); - r10 = vmlaq_lane_f32(r10, r07, q05, 1); - r11 = vmlaq_lane_f32(r11, r07, q07, 1); - - q00 = vld1_f32(r0), q01 = vld1_f32(r0+2), q02 = vld1_f32(r0+4), q03 = vld1_f32(r0+6); - q04 = vld1_f32(r0+8), q05 = vld1_f32(r0+10), q06 = vld1_f32(r0+12), q07 = vld1_f32(r0+14); - r0 += 16; - - r12 = vmlaq_lane_f32(r12, r04, q00, 0); - r13 = vmlaq_lane_f32(r13, r04, q02, 0); - r14 = vmlaq_lane_f32(r14, r04, q04, 0); - r15 = vmlaq_lane_f32(r15, r04, q06, 0); - - r12 = vmlaq_lane_f32(r12, r05, q00, 1); - r13 = vmlaq_lane_f32(r13, r05, q02, 1); - r14 = vmlaq_lane_f32(r14, r05, q04, 1); - r15 = vmlaq_lane_f32(r15, r05, q06, 1); - - r12 = vmlaq_lane_f32(r12, r06, q01, 0); - r13 = vmlaq_lane_f32(r13, r06, q03, 0); - r14 = vmlaq_lane_f32(r14, r06, q05, 0); - r15 = vmlaq_lane_f32(r15, r06, q07, 0); - - r12 = vmlaq_lane_f32(r12, r07, q01, 1); - r13 = vmlaq_lane_f32(r13, r07, q03, 1); - r14 = vmlaq_lane_f32(r14, r07, q05, 1); - r15 = vmlaq_lane_f32(r15, r07, q07, 1); - } - - vst1q_f32(output0_tm, r08), vst1q_f32(output0_tm + 4, r09), vst1q_f32(output0_tm + 8, r10), vst1q_f32(output0_tm + 12, r11); - output0_tm += 16; - - vst1q_f32(output0_tm, r12), vst1q_f32(output0_tm + 4, r13), vst1q_f32(output0_tm + 8, r14), vst1q_f32(output0_tm + 12, r15); - output0_tm += 16; -#endif - } - - for (; ti + 3 < tiles; ti += 4) - { - int nn = C_aligned/4; - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k0 = kernel_tm_i; - -#if CV_NEON_AARCH64 - // init 12 registers. FMA/load ratio = 12/8 - float32x4_t r00 = vdupq_n_f32(0.0f), r01 = r00, r02 = r00, r03 = r00; - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r16 = r00, r17 = r00, r18 = r00, r19 = r00; - - for(; nn > 0; nn--) - { - r00 = vld1q_f32(r0), r01 = vld1q_f32(r0+4), r02 = vld1q_f32(r0+8), r03 = vld1q_f32(r0+12); - r08 = vld1q_f32(k0), r09 = vld1q_f32(k0+4), r10 = vld1q_f32(k0+8), r11 = vld1q_f32(k0+12); - r0 += 16, k0 += 16; - - r16 = vfmaq_laneq_f32(r16, r08, r00, 0); - r17 = vfmaq_laneq_f32(r17, r08, r01, 0); - r18 = vfmaq_laneq_f32(r18, r08, r02, 0); - r19 = vfmaq_laneq_f32(r19, r08, r03, 0); - - r16 = vfmaq_laneq_f32(r16, r09, r00, 1); - r17 = vfmaq_laneq_f32(r17, r09, r01, 1); - r18 = vfmaq_laneq_f32(r18, r09, r02, 1); - r19 = vfmaq_laneq_f32(r19, r09, r03, 1); - - r16 = vfmaq_laneq_f32(r16, r10, r00, 2); - r17 = vfmaq_laneq_f32(r17, r10, r01, 2); - r18 = vfmaq_laneq_f32(r18, r10, r02, 2); - r19 = vfmaq_laneq_f32(r19, r10, r03, 2); - - r16 = vfmaq_laneq_f32(r16, r11, r00, 3); - r17 = vfmaq_laneq_f32(r17, r11, r01, 3); - r18 = vfmaq_laneq_f32(r18, r11, r02, 3); - r19 = vfmaq_laneq_f32(r19, r11, r03, 3); - } -#else - // init 12 registers. FMA/load ratio = 12/8 - float32x2_t q00 = vdup_n_f32(0.0f), q01 = q00, q02 = q00, q03 = q00, - q04 = q00, q05 = q00, q06 = q00, q07 = q00; - float32x4_t r08 = vdupq_n_f32(0.0f), r09 = r08, r10 = r08, r11 = r08; - float32x4_t r16 = r08, r17 = r08, r18 = r08, r19 = r08; - - for(; nn > 0; nn--) - { - q00 = vld1_f32(r0), q01 = vld1_f32(r0+2), q02 = vld1_f32(r0+4), q03 = vld1_f32(r0+6); - q04 = vld1_f32(r0+8), q05 = vld1_f32(r0+10), q06 = vld1_f32(r0+12), q07 = vld1_f32(r0+14); - r08 = vld1q_f32(k0), r09 = vld1q_f32(k0+4), r10 = vld1q_f32(k0+8), r11 = vld1q_f32(k0+12); - r0 += 16, k0 += 16; - - r16 = vmlaq_lane_f32(r16, r08, q00, 0); - r17 = vmlaq_lane_f32(r17, r08, q02, 0); - r18 = vmlaq_lane_f32(r18, r08, q04, 0); - r19 = vmlaq_lane_f32(r19, r08, q06, 0); - - r16 = vmlaq_lane_f32(r16, r09, q00, 1); - r17 = vmlaq_lane_f32(r17, r09, q02, 1); - r18 = vmlaq_lane_f32(r18, r09, q04, 1); - r19 = vmlaq_lane_f32(r19, r09, q06, 1); - - r16 = vmlaq_lane_f32(r16, r10, q01, 0); - r17 = vmlaq_lane_f32(r17, r10, q03, 0); - r18 = vmlaq_lane_f32(r18, r10, q05, 0); - r19 = vmlaq_lane_f32(r19, r10, q07, 0); - - r16 = vmlaq_lane_f32(r16, r11, q01, 1); - r17 = vmlaq_lane_f32(r17, r11, q03, 1); - r18 = vmlaq_lane_f32(r18, r11, q05, 1); - r19 = vmlaq_lane_f32(r19, r11, q07, 1); - - } -#endif - vst1q_f32(output0_tm, r16), vst1q_f32(output0_tm + 4, r17), vst1q_f32(output0_tm + 8, r18), vst1q_f32(output0_tm + 12, r19); - output0_tm += 16; - } - - for (; ti + 1 < tiles; ti += 2) - { - int nn = C_aligned/4; - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k0 = kernel_tm_i; - -#if CV_NEON_AARCH64 - // init 8 registers. FMA/load ratio = 8/6 - float32x4_t r00 = vdupq_n_f32(0.0f), r01 = r00; - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r16 = r00, r17 = r00; - - for(; nn > 0; nn--) - { - r00 = vld1q_f32(r0), r01 = vld1q_f32(r0+4); - r08 = vld1q_f32(k0), r09 = vld1q_f32(k0+4), r10 = vld1q_f32(k0+8), r11 = vld1q_f32(k0+12); - r0 += 8, k0 += 16; - - r16 = vfmaq_laneq_f32(r16, r08, r00, 0); - r17 = vfmaq_laneq_f32(r17, r08, r01, 0); - - r16 = vfmaq_laneq_f32(r16, r09, r00, 1); - r17 = vfmaq_laneq_f32(r17, r09, r01, 1); - - r16 = vfmaq_laneq_f32(r16, r10, r00, 2); - r17 = vfmaq_laneq_f32(r17, r10, r01, 2); - - r16 = vfmaq_laneq_f32(r16, r11, r00, 3); - r17 = vfmaq_laneq_f32(r17, r11, r01, 3); - } -#else - // init 8 registers. FMA/load ratio = 8/6 - float32x2_t q00 = vdup_n_f32(0.0f), q01 = q00, q02 = q00, q03 = q00; - float32x4_t r08 = vdupq_n_f32(0.0f), r09 = r08, r10 = r08, r11 = r08; - float32x4_t r16 = r08, r17 = r08; - - for(; nn > 0; nn--) - { - q00 = vld1_f32(r0), q01 = vld1_f32(r0+2), q02 = vld1_f32(r0+4), q03 = vld1_f32(r0+6); - r08 = vld1q_f32(k0), r09 = vld1q_f32(k0+4), r10 = vld1q_f32(k0+8), r11 = vld1q_f32(k0+12); - r0 += 8, k0 += 16; - - r16 = vmlaq_lane_f32(r16, r08, q00, 0); - r17 = vmlaq_lane_f32(r17, r08, q02, 0); - - r16 = vmlaq_lane_f32(r16, r09, q00, 1); - r17 = vmlaq_lane_f32(r17, r09, q02, 1); - - r16 = vmlaq_lane_f32(r16, r10, q01, 0); - r17 = vmlaq_lane_f32(r17, r10, q03, 0); - - r16 = vmlaq_lane_f32(r16, r11, q01, 1); - r17 = vmlaq_lane_f32(r17, r11, q03, 1); - } -#endif - vst1q_f32(output0_tm, r16), vst1q_f32(output0_tm + 4, r17); - output0_tm += 8; - } - - for (; ti < tiles; ti ++) - { - int nn = C_aligned/4; - const float* r0 = input_tm + ofstab0[ti * 2] * line_step; - const float* k0 = kernel_tm_i; - -#if CV_NEON_AARCH64 - // init 6 registers. FMA/load ratio = 6/5 - float32x4_t r00 = vdupq_n_f32(0.0f); - float32x4_t r08 = r00, r09 = r00, r10 = r00, r11 = r00; - float32x4_t r16 = r00; - - for(; nn > 0; nn--) - { - r00 = vld1q_f32(r0); - r08 = vld1q_f32(k0), r09 = vld1q_f32(k0+4), r10 = vld1q_f32(k0+8), r11 = vld1q_f32(k0+12); - r0 += 4, k0 += 16; - - r16 = vfmaq_laneq_f32(r16, r08, r00, 0); - r16 = vfmaq_laneq_f32(r16, r09, r00, 1); - r16 = vfmaq_laneq_f32(r16, r10, r00, 2); - r16 = vfmaq_laneq_f32(r16, r11, r00, 3); - } -#else - // init 6 registers. FMA/load ratio = 6/5 - float32x2_t q00 = vdup_n_f32(0.0f), q01 = q00; - float32x4_t r08 = vdupq_n_f32(0.0f), r09 = r08, r10 = r08, r11 = r08; - float32x4_t r16 = r08; - - for(; nn > 0; nn--) - { - q00 = vld1_f32(r0), q01 = vld1_f32(r0+2); - r08 = vld1q_f32(k0), r09 = vld1q_f32(k0+4), r10 = vld1q_f32(k0+8), r11 = vld1q_f32(k0+12); - r0 += 4, k0 += 16; - - r16 = vmlaq_lane_f32(r16, r08, q00, 0); - r16 = vmlaq_lane_f32(r16, r09, q00, 1); - r16 = vmlaq_lane_f32(r16, r10, q01, 0); - r16 = vmlaq_lane_f32(r16, r11, q01, 1); - } -#endif - vst1q_f32(output0_tm, r16); - output0_tm += 4; - } - } - } - }); - - int bigStepOut = tiles * K_aligned; - - // Transfor Ouput - parallel_for_(Range(0, ntasks), [&](const Range& range) + for(; ngk0 < ngk1; ngk0++) { - for (int task_i = range.start; task_i < range.end; task_i++) + int n = ngk0 / (Kg_nblocks*ngroups); + int gk0 = ngk0 % (Kg_nblocks*ngroups); + int g = gk0 / Kg_nblocks; + int k0 = (gk0 % Kg_nblocks)*_FX_WINO_KBLOCK; + int k1 = k0 + _FX_WINO_KBLOCK <= Kg ? k0 + _FX_WINO_KBLOCK : Kg; + + for (int block_id0 = 0; block_id0 < blocks_per_plane; block_id0 += _FX_WINO_IBLOCK) { - float* outputCnbuf = outputCnbuf0 + task_i * 8 * 8 * 4; - for (int outCn4 = task_i; outCn4 < K_aligned / 4; outCn4 += ntasks) + int block_id1 = block_id0 + _FX_WINO_IBLOCK; + block_id1 = block_id1 < blocks_per_plane ? block_id1 : blocks_per_plane; + size_t inwofs = ((n*ngroups + g)*blocks_per_plane_aligned + block_id0)*Cg*_FX_WINO_AREA; + size_t wofs = (g*Kg_nblocks*_FX_WINO_KBLOCK + k0)*Cg*_FX_WINO_AREA; + + float* inwptr = wbuf_all + inwofs; + const float* wptr = conv->weightsWinoBufPtr + wofs; + +#if CV_TRY_AVX2 + if (conv->useAVX2) + opt_AVX2::_fx_winograd_accum_f32(inwptr, wptr, out_wbuf, Cg, block_id1 - block_id0); + else +#endif + _fx_winograd_accum_f32(inwptr, wptr, out_wbuf, Cg, block_id1 - block_id0); + for (int k = k0; k < k1; k++) { - - int outCn = outCn4 * 4; - float* output_buf = outputbuf0 + outCn * tiles; - float* output_ptr = output_ptr0 + outCn * W0 * H0; - - for (int ti = 0; ti < tiles; ti++) + float biasv = conv->biasBuf[g*Kg + k]; + for (int block_id = block_id0; block_id < block_id1; block_id++) { - float* output_buf_i = output_buf + ti * 4; - float* outputCnbuf_i = outputCnbuf; - int hi = ti / W_tiles; - int wi = ti % W_tiles; + int y0 = block_id / blocks_per_row; + int x0 = block_id - y0 * blocks_per_row; + y0 = y0*_FX_WINO_STEP; + x0 = x0*_FX_WINO_STEP; + int dy1 = H0 - y0; + if (dy1 > _FX_WINO_STEP) dy1 = _FX_WINO_STEP; + int dx1 = W0 - x0; + if (dx1 > _FX_WINO_STEP) dx1 = _FX_WINO_STEP; + assert(dx1 > 0 && dy1 > 0); + bool partial = activ || dy1 < _FX_WINO_STEP || dx1 < _FX_WINO_STEP; + size_t outofs = (n*K + g*Kg + k)*out_planesize + y0*W0 + x0; + int outstep = W0; - // construct the output tile. - for (int r = 0; r < 64; r++) + float* outptr0 = (float*)out + outofs; + float* pbptr0 = fusedAddPtr ? fusedAddPtr + outofs : nullptr; + float *outptr = outptr0, *bpptr = pbptr0; + + if (partial) { - memcpy(outputCnbuf_i, output_buf_i, FAST_VEC_NLANES * sizeof(float )); - output_buf_i += bigStepOut; - outputCnbuf_i += FAST_VEC_NLANES; - } - - winograd_trans_output_F63(outputCnbuf, conv->biasBuf.data() + outCn, - minval, maxval, ifMinMaxAct); - - int wEnd = (wi + 1) * 6 > W0 ? W0 - (wi * 6) : 6; - int hEnd = (hi + 1) * 6 > H0 ? H0 - (hi * 6) : 6; - - float* output_ptr_i = output_ptr + (hi * W0 + wi) * 6; - - // write back the output data. - for (int outCni = 0; outCni < FAST_VEC_NLANES; outCni++) - { - float* output_ptr_i_cn = output_ptr_i + outCni * out_planesize; - outputCnbuf_i = outputCnbuf + outCni; - - if (outCni + outCn < K) + outptr = outbuf; + outstep = _FX_WINO_SIZE; + if (pbptr0) { - for (int i = 0; i < hEnd; i++) - { - for (int j = 0; j < wEnd; j++) - { - output_ptr_i_cn[i * W0 + j] = outputCnbuf_i[(i * 6 + j) * FAST_VEC_NLANES ]; - } - } + bpptr = outbuf; + for (int y = 0; y < dy1; y++) + memcpy(outbuf + y*_FX_WINO_SIZE, pbptr0 + y*W0, + dx1*sizeof(pbptr0[0])); } } - } - - if (activ) - { - int outCnEnd = std::min(outCn + FAST_VEC_NLANES, K); - activ->forwardSlice(output_ptr, output_ptr, out_planesize, - out_planesize, outCn, outCnEnd); +#if CV_TRY_AVX2 + if (conv->useAVX2) + opt_AVX2::_fx_winograd_AtXA_8x8_f32(out_wbuf + ((k - k0)*_FX_WINO_IBLOCK + (block_id - block_id0))*_FX_WINO_AREA, _FX_WINO_SIZE, + bpptr, outstep, outptr, outstep, biasv, minval, maxval, ifMinMaxAct); + else +#endif + _fx_winograd_AtXA_8x8_f32(out_wbuf + ((k - k0)*_FX_WINO_IBLOCK + (block_id - block_id0))*_FX_WINO_AREA, _FX_WINO_SIZE, + bpptr, outstep, outptr, outstep, biasv, minval, maxval, ifMinMaxAct); + if (partial) + { + if (activ) + activ->forwardSlice(outptr, outptr, _FX_WINO_SIZE*_FX_WINO_STEP, 0, g*Kg + k, g*Kg + k + 1); + for (int y = 0; y < dy1; y++) + memcpy(outptr0 + y*W0, outptr + y*_FX_WINO_SIZE,dx1*sizeof(outptr0[0])); + } } } } - }); - } - + } + }}); return 1; } #else -void initWinograd63(Ptr& conv, float* src_weight, int K, int C) -{ - conv->ifWinograd63 = false; -} - -int runWinograd63(InputArray _input, OutputArray _output, const Ptr& conv, int ntasks, float minval, float maxval, ActivationLayer* activ, bool ifMinMaxAct) +int runWinograd63(InputArray _input, InputArray _fusedAddMat, OutputArray _output, const Ptr& conv, + int ntasks, float minval, float maxval, ActivationLayer* activ, bool ifMinMaxAct) { return 0; } - #endif - }} // namespace cv::dnn diff --git a/modules/dnn/src/layers/flatten_layer.cpp b/modules/dnn/src/layers/flatten_layer.cpp index b3f57dc7cd..ff30da3a11 100644 --- a/modules/dnn/src/layers/flatten_layer.cpp +++ b/modules/dnn/src/layers/flatten_layer.cpp @@ -45,6 +45,7 @@ #include "../op_cuda.hpp" #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" +#include "../op_cann.hpp" #include #include @@ -77,7 +78,8 @@ public: return true; #endif return backendId == DNN_BACKEND_OPENCV || - backendId == DNN_BACKEND_CUDA; + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_CANN; } bool getMemoryShapes(const std::vector &inputs, @@ -173,6 +175,33 @@ public: } } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + auto x_desc = x->getTensorDesc(); + auto op_x = nodes[0].dynamicCast()->getOp(); + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::string op_name = cv::format("flatten_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + int num_axes = x->host->dims; + int start_axis = normalize_axis(_startAxis, num_axes); + int end_axis = normalize_axis(_endAxis, num_axes); + op->set_attr_axis(start_axis); + op->set_attr_end_axis(end_axis); + + // set inputs + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + // set outputs + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, diff --git a/modules/dnn/src/layers/fully_connected_layer.cpp b/modules/dnn/src/layers/fully_connected_layer.cpp index 509f6cc177..539c083399 100644 --- a/modules/dnn/src/layers/fully_connected_layer.cpp +++ b/modules/dnn/src/layers/fully_connected_layer.cpp @@ -47,6 +47,7 @@ #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" #include "../op_webnn.hpp" +#include "../op_cann.hpp" #include @@ -80,8 +81,12 @@ public: FullyConnectedLayerImpl(const LayerParams& params) { setParamsFrom(params); + transA = params.get("transA", false); + transB = params.get("transB", false); + bias = params.get("bias_term", true); axis = params.get("axis", 1); + isMatMul = params.get("is_matmul", false); if (!blobs.empty()) { CV_Assert(1 <= blobs.size() && blobs.size() <= 2); @@ -91,6 +96,7 @@ public: CV_Assert(blobs[0].dims >= 2 && (size_t)(innerSize * numOutput) == blobs[0].total()); CV_Assert(!bias || (blobs.size() == 2 && (size_t)numOutput == blobs[1].total())); + blobs[0].copyTo(oriMat); weightsMat = blobs[0] = blobs[0].reshape(1, numOutput); int vecsize = weightsMat.cols; if (vecsize % VEC_ALIGN != 0) @@ -105,6 +111,8 @@ public: if (bias) biasMat = blobs[1] = blobs[1].reshape(1, 1); + else if(isMatMul) + biasMat = Mat::zeros(1, oriMat.size[oriMat.dims - 2], weightsMat.type()); else biasMat = Mat::zeros(1, numOutput, weightsMat.type()); } @@ -116,30 +124,51 @@ public: std::vector &) const CV_OVERRIDE { int numOutput, cAxis; + + std::vector inputsTmp; + inputsTmp.assign(inputs.begin(), inputs.end()); + if (blobs.empty()) { - CV_CheckEQ(inputs.size(), (size_t)2, ""); - numOutput = inputs[1].back(); - cAxis = inputs[0].size() - 1; - int dims = inputs[0].size(); - CV_CheckEQ(inputs[1].size(), (size_t)dims, ""); + CV_CheckEQ(inputsTmp.size(), (size_t)2, ""); + + if (transA) + { + CV_CheckEQ(inputsTmp[0].size(), (size_t)2, ""); + std::swap(inputsTmp[0][0], inputsTmp[0][1]); + } + + if (transB) + { + CV_CheckEQ(inputsTmp[1].size(), (size_t)2, ""); + std::swap(inputsTmp[1][0], inputsTmp[1][1]); + } + + numOutput = inputsTmp[1].back(); + cAxis = inputsTmp[0].size() - 1; + int dims = inputsTmp[0].size(); + CV_CheckEQ(inputsTmp[1].size(), (size_t)dims, ""); CV_CheckGE(dims, 2, ""); for (int i = 0; i < dims - 2; i++) - CV_CheckEQ(inputs[0][i], inputs[1][i], ""); - CV_CheckEQ(inputs[0].back(), inputs[1][dims - 2], ""); + CV_CheckEQ(inputsTmp[0][i], inputsTmp[1][i], ""); + CV_CheckEQ(inputsTmp[0].back(), inputsTmp[1][dims - 2], ""); } else { - CV_CheckEQ(inputs.size(), (size_t)1, ""); + CV_Assert(!transA && !transB); + CV_CheckEQ(inputsTmp.size(), (size_t)1, ""); CV_CheckEQ(blobs[0].dims, 2, ""); - numOutput = blobs[0].size[0]; + if(isMatMul) + numOutput = oriMat.size[oriMat.dims - 2]; + else + numOutput = blobs[0].size[0]; CV_Assert(!bias || (size_t)numOutput == blobs[1].total()); - cAxis = normalize_axis(axis, inputs[0]); + cAxis = normalize_axis(axis, inputsTmp[0]); } MatShape outShape(cAxis + 1); for (int i = 0; i < cAxis; ++i) - outShape[i] = inputs[0][i]; + outShape[i] = inputsTmp[0][i]; outShape.back() = numOutput; outputs.resize(1, outShape); @@ -148,15 +177,16 @@ public: virtual bool supportBackend(int backendId) CV_OVERRIDE { + bool tranAorB = transA || transB; #ifdef HAVE_INF_ENGINE if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) - return axis == 1; + return axis == 1 && !tranAorB; #endif - return backendId == DNN_BACKEND_OPENCV || - backendId == DNN_BACKEND_CUDA || - (backendId == DNN_BACKEND_HALIDE && haveHalide() && axis == 1) || - (backendId == DNN_BACKEND_WEBNN && axis == 1); + (backendId == DNN_BACKEND_CUDA && !tranAorB) || + (backendId == DNN_BACKEND_HALIDE && haveHalide() && axis == 1 && !tranAorB) || + (backendId == DNN_BACKEND_WEBNN && axis == 1 && !tranAorB) || + backendId == DNN_BACKEND_CANN;; } virtual bool setActivation(const Ptr& layer) CV_OVERRIDE @@ -173,7 +203,7 @@ public: class FullyConnected : public ParallelLoopBody { public: - FullyConnected() : srcMat(0), weights(0), biasMat(0), activ(0), dstMat(0), nstripes(0), useAVX(false), useAVX2(false), useAVX512(false), useRVV(false) {} + FullyConnected() : srcMat(0), weights(0), biasMat(0), activ(0), dstMat(0), nstripes(0), useAVX(false), useAVX2(false), useAVX512(false), useRVV(false), useLASX(false) {} static void run(const Mat& srcMat, const Mat& weights, const Mat& biasMat, Mat& dstMat, const ActivationLayer* activ, int nstripes) @@ -197,6 +227,7 @@ public: p.useAVX2 = checkHardwareSupport(CPU_AVX2); p.useAVX512 = CV_CPU_HAS_SUPPORT_AVX512_SKX; p.useRVV = checkHardwareSupport(CPU_RVV); + p.useLASX = checkHardwareSupport(CPU_LASX); parallel_for_(Range(0, nstripes), p, nstripes); } @@ -250,6 +281,11 @@ public: if( useRVV ) opt_RVV::fastGEMM1T( sptr, wptr, wstep, biasptr, dptr, nw, vecsize); else + #endif + #if CV_TRY_LASX + if( useLASX ) + opt_LASX::fastGEMM1T( sptr, wptr, wstep, biasptr, dptr, nw, vecsize); + else #endif { int i = 0; @@ -305,6 +341,7 @@ public: bool useAVX2; bool useAVX512; bool useRVV; + bool useLASX; }; #ifdef HAVE_OPENCL @@ -475,7 +512,7 @@ public: CV_TRACE_FUNCTION(); CV_TRACE_ARG_VALUE(name, "name", name.c_str()); - CV_OCL_RUN(IS_DNN_OPENCL_TARGET(preferableTarget), + CV_OCL_RUN(IS_DNN_OPENCL_TARGET(preferableTarget) && !isMatMul, forward_ocl(inputs_arr, outputs_arr, internals_arr)) if (inputs_arr.depth() == CV_16S) @@ -490,29 +527,69 @@ public: if (!blobs.empty()) { - int axisCan = normalize_axis(axis, input[0].dims); - int outerSize = input[0].total(0, axisCan); - - for (size_t i = 0; i < input.size(); i++) + CV_Assert(!transA && !transB); + int inp1Dim = input[0].dims; + if (isMatMul) { - Mat srcMat = input[i].reshape(1, outerSize); - Mat dstMat = output[i].reshape(1, outerSize); + int matNum = input[0].total(0, inp1Dim - 2); + int rowMatMul = oriMat.size[oriMat.dims - 2]; + Mat srcMatTmp = input[0].reshape(1, matNum); + Mat dstMatTmp = output[0].reshape(1, matNum); - const int nstripes = getNumThreads(); - FullyConnected::run(srcMat, weightsMat, biasMat, dstMat, activ.get(), nstripes); + int outerSize = input[0].size[inp1Dim - 2]; + int rowStart = -rowMatMul; + for (int n = 0; n < matNum; ++n) + { + Mat srcMat = srcMatTmp.row(n).reshape(1, outerSize); + Mat dstMat = dstMatTmp.row(n).reshape(1, outerSize); + rowStart = (rowStart + rowMatMul) % weightsMat.rows; + Mat weiMat = weightsMat.rowRange(rowStart, rowStart + rowMatMul); + + const int nstripes = getNumThreads(); + FullyConnected::run(srcMat, weiMat, biasMat, dstMat, activ.get(), nstripes); + } + } + else + { + int axisCan = normalize_axis(axis, inp1Dim); + int outerSize = input[0].total(0, axisCan); + + for (size_t i = 0; i < input.size(); i++) + { + Mat srcMat = input[i].reshape(1, outerSize); + Mat dstMat = output[i].reshape(1, outerSize); + + const int nstripes = getNumThreads(); + FullyConnected::run(srcMat, weightsMat, biasMat, dstMat, activ.get(), nstripes); + } } } else { - float* inpData = input[0].ptr(); - float* weightData = input[1].ptr(); + Mat input0 = input[0]; + Mat input1 = input[1]; + + if (transA) + { + CV_Assert(input0.dims == 2); + input0 = input0.t(); + } + + if (transB) + { + CV_Assert(input1.dims == 2); + input1 = input1.t(); + } + + float* inpData = input0.ptr(); + float* weightData = input1.ptr(); float* outData = output[0].ptr(); int dims = output[0].dims; int numSlice = output[0].total() / output[0].total(dims - 2); - int m = input[0].size[dims - 2]; - int n = input[0].size[dims - 1]; - int k = input[1].size[dims - 1]; + int m = input0.size[dims - 2]; + int n = input0.size[dims - 1]; + int k = input1.size[dims - 1]; for (int i = 0; i < numSlice; i++) { Mat inpSlice(m, n, CV_32F, inpData); @@ -535,14 +612,26 @@ public: ) override { auto context = reinterpret_cast(context_); + auto input_wrapper = inputs[0].dynamicCast(); - if (weightsMat.empty()) + if (weightsMat.empty() || isMatMul) { CV_Assert(!bias); - return make_cuda_node(preferableTarget, std::move(context->stream), std::move(context->cublas_handle)); + int inp2Dim; + // broadcast is not supported with CUDA + if(weightsMat.empty()) + { + auto input_wrapper2 = inputs[1].dynamicCast(); + inp2Dim = input_wrapper2->getRank(); + }else + inp2Dim = oriMat.dims; + + if(input_wrapper->getRank() == inp2Dim) + return make_cuda_node(preferableTarget, std::move(context->stream), std::move(context->cublas_handle), oriMat); + else + return Ptr(); } - auto input_wrapper = inputs[0].dynamicCast(); auto flatten_start_axis = normalize_axis(axis, input_wrapper->getRank()); auto biasMat_ = bias ? biasMat : Mat(); return make_cuda_node(preferableTarget, std::move(context->stream), std::move(context->cublas_handle), flatten_start_axis, weightsMat, biasMat_); @@ -573,6 +662,65 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x1 = inputsWrapper[0].dynamicCast(); + auto x1_desc = x1->getTensorDesc(); + auto op_x1 = nodes[0].dynamicCast()->getOp(); + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::string op_name = cv::format("matmul_%d", index); + auto op = std::make_shared(op_name); + + if (!blobs.empty()) // if B is const + { + // set attributes + op->set_attr_transpose_x1(false); + // weightMat always needs to be transposed, since CPU backend + // implementation is input * weight.im2row + op->set_attr_transpose_x2(true); + + // set inputs + // set inputs : x2 (weight) + auto op_const_weight = std::make_shared(weightsMat.data, weightsMat.type(), shape(weightsMat), cv::format("%s_w", op_name.c_str())); + op->set_input_x2_by_name(*(op_const_weight->getOp()), "y"); + op->update_input_desc_x2(*(op_const_weight->getTensorDesc())); + } + else + { + // A and B are variable inputs; non-const bias is not considered + CV_Assert(inputsWrapper.size() == 2); + CV_Assert(nodes.size() == 2); + + // set attributes + op->set_attr_transpose_x1(transA); + op->set_attr_transpose_x2(transB); + + // set inputs : x2 (weight) + auto op_x2 = nodes[1].dynamicCast()->getOp(); + auto x2_desc = inputsWrapper[1].dynamicCast()->getTensorDesc(); + op->set_input_x2_by_name(*op_x2, "y"); + op->update_input_desc_x2(*x2_desc); + } + + // set inputs + // set inputs : x1 (input) + op->set_input_x1_by_name(*op_x1, "y"); + op->update_input_desc_x1(*x1_desc); + // set inputs : bias (bias) + auto bias_mat = bias ? biasMat : Mat::zeros(1, weightsMat.size[0], weightsMat.type()); + std::vector bias_shape{weightsMat.size[0]}; + auto op_const_bias = std::make_shared(bias_mat.data, bias_mat.type(), bias_shape, cv::format("%s_b", op_name.c_str())); + op->set_input_bias(*(op_const_bias->getOp())); + op->update_input_desc_bias(*(op_const_bias->getTensorDesc())); + + // set outputs + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, @@ -708,7 +856,9 @@ public: } bool bias; - Mat weightsMat, biasMat; + Mat weightsMat, biasMat, oriMat; + bool transA, transB; + bool isMatMul = false; Ptr activ; }; diff --git a/modules/dnn/src/layers/gather_layer.cpp b/modules/dnn/src/layers/gather_layer.cpp new file mode 100644 index 0000000000..924b5fcbc1 --- /dev/null +++ b/modules/dnn/src/layers/gather_layer.cpp @@ -0,0 +1,127 @@ +// 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 "layers_common.hpp" + + +namespace cv { namespace dnn { + +class GatherLayerImpl CV_FINAL : public GatherLayer +{ +public: + GatherLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + m_axis = params.get("axis", 0); + m_real_ndims = params.get("real_ndims", -1); + } + + virtual bool supportBackend(int backendId) CV_OVERRIDE + { + return backendId == DNN_BACKEND_OPENCV; + } + + virtual bool getMemoryShapes(const std::vector &inputs, + const int requiredOutputs, + std::vector &outputs, + std::vector &internals) const CV_OVERRIDE + { + CV_CheckEQ(inputs.size(), 2ull, ""); + MatShape inpShape = inputs[0]; + const int axis = normalize_axis(m_axis, inpShape); + + inpShape.erase(inpShape.begin() + axis); + auto end = m_real_ndims == -1 ? inputs[1].end() : inputs[1].begin() + m_real_ndims; + inpShape.insert(inpShape.begin() + axis, inputs[1].begin(), end); + + outputs.assign(1, inpShape); + return false; + } + + void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(name, "name", name.c_str()); + + // FP16 fallback is not needed as we handle FP16 below + + std::vector inputs, outputs; + inputs_arr.getMatVector(inputs); + outputs_arr.getMatVector(outputs); + + CV_CheckEQ(inputs.size(), (size_t)2, ""); + CV_CheckEQ(outputs.size(), (size_t)1, ""); + + const Mat& inp = inputs[0]; + + int indicesType = inputs[1].type(); + CV_CheckType(indicesType, indicesType == CV_32FC1 || indicesType == CV_16SC1, ""); + Mat indices32S; + if (indicesType == CV_16S/*FP16*/) + { + Mat indicesF32; + convertFp16(inputs[1], indicesF32); + indicesF32.convertTo(indices32S, CV_32S); + } + else + { + inputs[1].convertTo(indices32S, CV_32S); + } + const size_t indices_total = indices32S.total(); + indices32S = indices32S.reshape(1, indices_total); + + Mat& out = outputs[0]; + + CV_CheckTypeEQ(inp.type(), out.type(), ""); + CV_CheckTypeEQ(indices32S.type(), CV_32SC1, ""); + + const int axis = normalize_axis(m_axis, shape(inp)); + + // FIXIT: why should we work with non-normalized input? it should be handled in importer or layers's output generator + const int axis_size = (int)inp.size[axis]; + for (size_t j = 0 ; j < indices_total; ++j) + { + int& idx = indices32S.at(j); + idx = normalize_axis(idx, axis_size); // validate and normalize indices + } + + const size_t outer_size = axis == 0 ? inp.total() : inp.step1(axis - 1); + const size_t outer_dims = inp.total() / outer_size; + const size_t inner_size = inp.step1(axis); + + const int* idx = indices32S.ptr(); + const char* src = inp.ptr(); + char* dst = out.ptr(); + CV_CheckEQ(out.total(), outer_dims * indices_total * inner_size, ""); + + const size_t es = inp.elemSize1(); + // TODO: optimize through switch (inner_size * es) + const size_t inner_bytes = inner_size * es; + for (size_t i = 0; i < outer_dims; ++i) + { + const size_t src_offset = i * outer_size; + for (size_t j = 0 ; j < indices_total; ++j) + { + const int index = idx[j]; + CV_DbgCheck(index, index >= 0 && index < axis_size, ""); + const size_t new_offset = src_offset + index * inner_size; + std::memcpy(dst, src + new_offset * es, inner_bytes); + dst += inner_bytes; + } + } + } + +private: + // The axis to gather along + int m_axis; + int m_real_ndims; +}; + +Ptr GatherLayer::create(const LayerParams& params) +{ + return makePtr(params); +} + +}} // namespace cv::dnn diff --git a/modules/dnn/src/layers/layers_common.cpp b/modules/dnn/src/layers/layers_common.cpp index 445a89ff98..b128872817 100644 --- a/modules/dnn/src/layers/layers_common.cpp +++ b/modules/dnn/src/layers/layers_common.cpp @@ -187,12 +187,14 @@ void getPoolingKernelParams(const LayerParams ¶ms, std::vector& kern void getConvolutionKernelParams(const LayerParams ¶ms, std::vector& kernel, std::vector& pads_begin, std::vector& pads_end, std::vector& strides, - std::vector& dilations, cv::String &padMode, std::vector& adjust_pads) + std::vector& dilations, cv::String &padMode, std::vector& adjust_pads, + bool& useWinograd) { util::getKernelSize(params, kernel); util::getStrideAndPadding(params, pads_begin, pads_end, strides, padMode, kernel.size()); util::getParameter(params, "dilation", "dilation", dilations, true, std::vector(kernel.size(), 1)); util::getParameter(params, "adj", "adj", adjust_pads, true, std::vector(kernel.size(), 0)); + useWinograd = params.get("use_winograd", true); for (int i = 0; i < dilations.size(); i++) CV_Assert(dilations[i] > 0); diff --git a/modules/dnn/src/layers/layers_common.hpp b/modules/dnn/src/layers/layers_common.hpp index 85f442c78e..4510f6b106 100644 --- a/modules/dnn/src/layers/layers_common.hpp +++ b/modules/dnn/src/layers/layers_common.hpp @@ -61,7 +61,7 @@ namespace dnn { void getConvolutionKernelParams(const LayerParams ¶ms, std::vector& kernel, std::vector& pads_begin, std::vector& pads_end, std::vector& strides, std::vector& dilations, - cv::String &padMode, std::vector& adjust_pads); + cv::String &padMode, std::vector& adjust_pads, bool& useWinograd); void getPoolingKernelParams(const LayerParams ¶ms, std::vector& kernel, std::vector& globalPooling, std::vector& pads_begin, std::vector& pads_end, std::vector& strides, cv::String &padMode); diff --git a/modules/dnn/src/layers/layers_common.simd.hpp b/modules/dnn/src/layers/layers_common.simd.hpp index fd88a3c3d2..eb1735639e 100644 --- a/modules/dnn/src/layers/layers_common.simd.hpp +++ b/modules/dnn/src/layers/layers_common.simd.hpp @@ -46,10 +46,6 @@ namespace cv { namespace dnn { CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN -void fastConv( const float* weights, size_t wstep, const float* bias, - const float* rowbuf, float* output, const int* outShape, - int blockSize, int vecsize, int vecsize_aligned, - const float* relu, bool initOutput ); void fastDepthwiseConv( const float* weights, int kernel_h, int kernel_w, int stride_h, int stride_w, @@ -74,305 +70,6 @@ void fastGEMM( const float* aptr, size_t astep, const float* bptr, #define _mm256_fmadd_ps(a, b, c) _mm256_add_ps(c, _mm256_mul_ps(a, b)) #endif -enum { FASCONV_BASE_VECSZ = 4 }; - -void fastConv( const float* weights, size_t wstep, const float* bias, - const float* rowbuf, float* output, const int* outShape, - int blockSize, int vecsize, int vecsize_aligned, - const float* relu, bool initOutput ) -{ - CV_Assert(isAligned<32>(weights)); - - int outCn = outShape[1]; - size_t outPlaneSize = outShape[2]*outShape[3]; - float r0 = 1.f, r1 = 1.f, r2 = 1.f; - __m128 vr0 = _mm_set1_ps(1.f), vr1 = vr0, vr2 = vr0, z = _mm_setzero_ps(); - int CV_DECL_ALIGNED(16) maskbuf[FASCONV_BASE_VECSZ] = {0}; - int rsz = blockSize % FASCONV_BASE_VECSZ; - for( int i = 0; i < rsz; i++ ) - maskbuf[FASCONV_BASE_VECSZ - i - 1] = -1; - __m128 mask = _mm_loadu_ps((const float*)maskbuf); - - // now compute dot product of the weights - // and im2row-transformed part of the tensor - for( int i = 0; i < outCn; i += 3 ) - { - const float* wptr0 = weights + i*wstep; - const float* wptr1 = wptr0 + wstep; - const float* wptr2 = wptr1 + wstep; - float* outptr0 = output + i*outPlaneSize; - float* outptr1 = outptr0 + outPlaneSize; - float* outptr2 = outptr1 + outPlaneSize; - float bias0 = bias[i], bias1 = bias[i+1], bias2 = bias[i+2]; - - if( i+2 >= outCn ) - { - wptr2 = wptr1; - outptr2 = outptr1; - bias2 = bias1; - if( i+1 >= outCn ) - { - wptr2 = wptr1 = wptr0; - outptr2 = outptr1 = outptr0; - bias2 = bias1 = bias0; - } - } - - if( relu ) - { - r0 = relu[i]; r1 = relu[i+1]; r2 = relu[i+2]; - if( i+2 >= outCn ) - { - r2 = r1; - if( i+1 >= outCn ) - r2 = r1 = r0; - } - vr0 = _mm_set1_ps(r0); - vr1 = _mm_set1_ps(r1); - vr2 = _mm_set1_ps(r2); - } - - int j = 0; - for( ; j < blockSize; j += FASCONV_BASE_VECSZ ) - { - bool tail = false; - if (j + FASCONV_BASE_VECSZ > blockSize) - { - if (j == 0) - break; - j = blockSize - FASCONV_BASE_VECSZ; - tail = true; - } - int k = 0; - const float* rptr = rowbuf + j*vecsize_aligned; - - __m256 vs00 = _mm256_setzero_ps(), vs01 = _mm256_setzero_ps(), - vs02 = _mm256_setzero_ps(), vs03 = _mm256_setzero_ps(), - vs10 = _mm256_setzero_ps(), vs11 = _mm256_setzero_ps(), - vs12 = _mm256_setzero_ps(), vs13 = _mm256_setzero_ps(), - vs20 = _mm256_setzero_ps(), vs21 = _mm256_setzero_ps(), - vs22 = _mm256_setzero_ps(), vs23 = _mm256_setzero_ps(); - -#if CV_AVX512_SKX // AVX512VL is necessary to avoid register spilling - if (vecsize >= 32) - { - __m512 vs00_5 = _mm512_setzero_ps(), vs01_5 = _mm512_setzero_ps(), - vs02_5 = _mm512_setzero_ps(), vs03_5 = _mm512_setzero_ps(), - vs10_5 = _mm512_setzero_ps(), vs11_5 = _mm512_setzero_ps(), - vs12_5 = _mm512_setzero_ps(), vs13_5 = _mm512_setzero_ps(), - vs20_5 = _mm512_setzero_ps(), vs21_5 = _mm512_setzero_ps(), - vs22_5 = _mm512_setzero_ps(), vs23_5 = _mm512_setzero_ps(); - - for (; k <= vecsize - 16; k += 16, rptr += 16) - { - __m512 w0 = _mm512_loadu_ps(wptr0 + k); - __m512 w1 = _mm512_loadu_ps(wptr1 + k); - __m512 w2 = _mm512_loadu_ps(wptr2 + k); - __m512 r0 = _mm512_loadu_ps(rptr); - - vs00_5 = _mm512_fmadd_ps(w0, r0, vs00_5); - vs10_5 = _mm512_fmadd_ps(w1, r0, vs10_5); - vs20_5 = _mm512_fmadd_ps(w2, r0, vs20_5); - - r0 = _mm512_loadu_ps(rptr + vecsize_aligned); - vs01_5 = _mm512_fmadd_ps(w0, r0, vs01_5); - vs11_5 = _mm512_fmadd_ps(w1, r0, vs11_5); - vs21_5 = _mm512_fmadd_ps(w2, r0, vs21_5); - - r0 = _mm512_loadu_ps(rptr + vecsize_aligned*2); - vs02_5 = _mm512_fmadd_ps(w0, r0, vs02_5); - vs12_5 = _mm512_fmadd_ps(w1, r0, vs12_5); - vs22_5 = _mm512_fmadd_ps(w2, r0, vs22_5); - - r0 = _mm512_loadu_ps(rptr + vecsize_aligned*3); - vs03_5 = _mm512_fmadd_ps(w0, r0, vs03_5); - vs13_5 = _mm512_fmadd_ps(w1, r0, vs13_5); - vs23_5 = _mm512_fmadd_ps(w2, r0, vs23_5); - } - /* - * now fold the 512 bit accumulator vectors into 256 bit vectors so that the AVX2 code can finish - * the tail of the vector - */ - vs00 = _mm256_add_ps( _mm512_extractf32x8_ps(vs00_5, 0), _mm512_extractf32x8_ps(vs00_5, 1)); - vs10 = _mm256_add_ps( _mm512_extractf32x8_ps(vs10_5, 0), _mm512_extractf32x8_ps(vs10_5, 1)); - vs20 = _mm256_add_ps( _mm512_extractf32x8_ps(vs20_5, 0), _mm512_extractf32x8_ps(vs20_5, 1)); - - vs01 = _mm256_add_ps( _mm512_extractf32x8_ps(vs01_5, 0), _mm512_extractf32x8_ps(vs01_5, 1)); - vs11 = _mm256_add_ps( _mm512_extractf32x8_ps(vs11_5, 0), _mm512_extractf32x8_ps(vs11_5, 1)); - vs21 = _mm256_add_ps( _mm512_extractf32x8_ps(vs21_5, 0), _mm512_extractf32x8_ps(vs21_5, 1)); - - vs02 = _mm256_add_ps( _mm512_extractf32x8_ps(vs02_5, 0), _mm512_extractf32x8_ps(vs02_5, 1)); - vs12 = _mm256_add_ps( _mm512_extractf32x8_ps(vs12_5, 0), _mm512_extractf32x8_ps(vs12_5, 1)); - vs22 = _mm256_add_ps( _mm512_extractf32x8_ps(vs22_5, 0), _mm512_extractf32x8_ps(vs22_5, 1)); - - vs03 = _mm256_add_ps( _mm512_extractf32x8_ps(vs03_5, 0), _mm512_extractf32x8_ps(vs03_5, 1)); - vs13 = _mm256_add_ps( _mm512_extractf32x8_ps(vs13_5, 0), _mm512_extractf32x8_ps(vs13_5, 1)); - vs23 = _mm256_add_ps( _mm512_extractf32x8_ps(vs23_5, 0), _mm512_extractf32x8_ps(vs23_5, 1)); - } -#endif - - for (; k < vecsize; k += 8, rptr += 8 ) - { - __m256 w0 = _mm256_load_ps(wptr0 + k); - __m256 w1 = _mm256_load_ps(wptr1 + k); - __m256 w2 = _mm256_load_ps(wptr2 + k); - __m256 r0 = _mm256_load_ps(rptr); - - vs00 = _mm256_fmadd_ps(w0, r0, vs00); - vs10 = _mm256_fmadd_ps(w1, r0, vs10); - vs20 = _mm256_fmadd_ps(w2, r0, vs20); - - r0 = _mm256_load_ps(rptr + vecsize_aligned); - vs01 = _mm256_fmadd_ps(w0, r0, vs01); - vs11 = _mm256_fmadd_ps(w1, r0, vs11); - vs21 = _mm256_fmadd_ps(w2, r0, vs21); - - r0 = _mm256_load_ps(rptr + vecsize_aligned*2); - vs02 = _mm256_fmadd_ps(w0, r0, vs02); - vs12 = _mm256_fmadd_ps(w1, r0, vs12); - vs22 = _mm256_fmadd_ps(w2, r0, vs22); - - r0 = _mm256_load_ps(rptr + vecsize_aligned*3); - vs03 = _mm256_fmadd_ps(w0, r0, vs03); - vs13 = _mm256_fmadd_ps(w1, r0, vs13); - vs23 = _mm256_fmadd_ps(w2, r0, vs23); - } - - __m256 t0 = _mm256_hadd_ps(_mm256_hadd_ps(vs00, vs01), _mm256_hadd_ps(vs02, vs03)); - __m256 t1 = _mm256_hadd_ps(_mm256_hadd_ps(vs10, vs11), _mm256_hadd_ps(vs12, vs13)); - __m256 t2 = _mm256_hadd_ps(_mm256_hadd_ps(vs20, vs21), _mm256_hadd_ps(vs22, vs23)); - - t0 = _mm256_add_ps(t0, _mm256_permute2f128_ps(t0, t0, 1)); - t1 = _mm256_add_ps(t1, _mm256_permute2f128_ps(t1, t1, 1)); - t2 = _mm256_add_ps(t2, _mm256_permute2f128_ps(t2, t2, 1)); - - __m128 s0, s1, s2; - - if( initOutput ) - { - s0 = _mm_set1_ps(bias0); - s1 = _mm_set1_ps(bias1); - s2 = _mm_set1_ps(bias2); - } - else - { - s0 = _mm_loadu_ps(outptr0 + j); - s1 = _mm_loadu_ps(outptr1 + j); - s2 = _mm_loadu_ps(outptr2 + j); - } - - s0 = _mm_add_ps(s0, _mm256_castps256_ps128(t0)); - s1 = _mm_add_ps(s1, _mm256_castps256_ps128(t1)); - s2 = _mm_add_ps(s2, _mm256_castps256_ps128(t2)); - - if( relu ) - { - __m128 m0 = _mm_cmp_ps(s0, z, _CMP_GT_OS); - __m128 m1 = _mm_cmp_ps(s1, z, _CMP_GT_OS); - __m128 m2 = _mm_cmp_ps(s2, z, _CMP_GT_OS); - s0 = _mm_blendv_ps(_mm_mul_ps(s0, vr0), s0, m0); - s1 = _mm_blendv_ps(_mm_mul_ps(s1, vr1), s1, m1); - s2 = _mm_blendv_ps(_mm_mul_ps(s2, vr2), s2, m2); - } - - if( tail ) - { - s0 = _mm_blendv_ps(_mm_loadu_ps(outptr0 + j), s0, mask); - s1 = _mm_blendv_ps(_mm_loadu_ps(outptr1 + j), s1, mask); - s2 = _mm_blendv_ps(_mm_loadu_ps(outptr2 + j), s2, mask); - } - - _mm_storeu_ps(outptr0 + j, s0); - _mm_storeu_ps(outptr1 + j, s1); - _mm_storeu_ps(outptr2 + j, s2); - } - - for( ; j <= blockSize - 2; j += 2 ) - { - const float* rptr0 = rowbuf + j*vecsize_aligned; - const float* rptr1 = rowbuf + (j+1)*vecsize_aligned; - float s00, s01, s10, s11, s20, s21; - - if( initOutput ) - { - s00 = s01 = bias0; - s10 = s11 = bias1; - s20 = s21 = bias2; - } - else - { - s00 = outptr0[j]; s01 = outptr0[j+1]; - s10 = outptr1[j]; s11 = outptr1[j+1]; - s20 = outptr2[j]; s21 = outptr2[j+1]; - } - - for( int k = 0; k < vecsize; k++ ) - { - float w0 = wptr0[k], w1 = wptr1[k], w2 = wptr2[k]; - float r = rptr0[k]; - s00 += w0*r; s10 += w1*r; s20 += w2*r; - r = rptr1[k]; - s01 += w0*r; s11 += w1*r; s21 += w2*r; - } - - if( relu ) - { - s00 = s00 > 0.f ? s00 : s00*r0; - s01 = s01 > 0.f ? s01 : s01*r0; - s10 = s10 > 0.f ? s10 : s10*r1; - s11 = s11 > 0.f ? s11 : s11*r1; - s20 = s20 > 0.f ? s20 : s20*r2; - s21 = s21 > 0.f ? s21 : s21*r2; - } - - outptr0[j] = s00; - outptr0[j+1] = s01; - outptr1[j] = s10; - outptr1[j+1] = s11; - outptr2[j] = s20; - outptr2[j+1] = s21; - } - - for( ; j < blockSize; j++ ) - { - const float* rptr0 = rowbuf + j*vecsize_aligned; - float s00, s10, s20; - - if( initOutput ) - { - s00 = bias0; - s10 = bias1; - s20 = bias2; - } - else - { - s00 = outptr0[j]; - s10 = outptr1[j]; - s20 = outptr2[j]; - } - - for( int k = 0; k < vecsize; k++ ) - { - float w0 = wptr0[k], w1 = wptr1[k], w2 = wptr2[k]; - float r = rptr0[k]; - s00 += w0*r; s10 += w1*r; s20 += w2*r; - } - - if( relu ) - { - s00 = s00 > 0.f ? s00 : s00*r0; - s10 = s10 > 0.f ? s10 : s10*r1; - s20 = s20 > 0.f ? s20 : s20*r2; - } - - outptr0[j] = s00; - outptr1[j] = s10; - outptr2[j] = s20; - } - } - _mm256_zeroupper(); -} - static inline void _mm256_load_deinterleave(const float* ptr, __m256& a, __m256& b) { __m256 t0 = _mm256_loadu_ps(ptr); @@ -957,198 +654,6 @@ void fastGEMM1T( const float* vec, const float* weights, } } -enum { FASCONV_BASE_VECSZ = 8 }; -void fastConv( const float* weights, size_t wstep, const float* bias, - const float* rowbuf, float* output, const int* outShape, - int blockSize, int vecsize, int vecsize_aligned, - const float* relu, bool initOutput ) -{ - const int vlm1 = vsetvlmax_e32m1(); - int outCn = outShape[1]; - size_t outPlaneSize = outShape[2]*outShape[3]; - // now compute dot product of the weights - // and im2row-transformed part of the tensor - for( int i = 0; i < outCn; i += 3 ) - { - int unroll_tail = FASCONV_BASE_VECSZ; - const float* wptr0 = weights + i*wstep; - const float* wptr1 = wptr0 + wstep; - const float* wptr2 = wptr1 + wstep; - float* outptr0 = output + i*outPlaneSize; - float* outptr1 = outptr0 + outPlaneSize; - float* outptr2 = outptr1 + outPlaneSize; - float bias0 = bias[i], bias1 = bias[i+1], bias2 = bias[i+2]; - - if( i+2 >= outCn ) - { - wptr2 = wptr1; - outptr2 = outptr1; - bias2 = bias1; - if( i+1 >= outCn ) - { - wptr2 = wptr1 = wptr0; - outptr2 = outptr1 = outptr0; - bias2 = bias1 = bias0; - } - } - - int j = 0; - for( ; j < blockSize; j += FASCONV_BASE_VECSZ ) - { - const float* rptr = rowbuf + j*vecsize_aligned; - const float *rptr1 = rptr + vecsize_aligned*1, - *rptr2 = rptr + vecsize_aligned*2, - *rptr3 = rptr + vecsize_aligned*3, - *rptr4 = rptr + vecsize_aligned*4, - *rptr5 = rptr + vecsize_aligned*5, - *rptr6 = rptr + vecsize_aligned*6, - *rptr7 = rptr + vecsize_aligned*7; - if (j + FASCONV_BASE_VECSZ > blockSize) - { - unroll_tail = blockSize - j; - rptr1 = rptr + vecsize_aligned*std::min(1, unroll_tail-1), - rptr2 = rptr + vecsize_aligned*std::min(2, unroll_tail-1), - rptr3 = rptr + vecsize_aligned*std::min(3, unroll_tail-1), - rptr4 = rptr + vecsize_aligned*std::min(4, unroll_tail-1), - rptr5 = rptr + vecsize_aligned*std::min(5, unroll_tail-1), - rptr6 = rptr + vecsize_aligned*std::min(6, unroll_tail-1), - rptr7 = rptr + vecsize_aligned*std::min(7, unroll_tail-1); - } - - int vl, avl = vecsize; - vfloat32m1_t - vs00 = vfmv_v_f_f32m1(0, vlm1), vs10 = vfmv_v_f_f32m1(0, vlm1), vs20 = vfmv_v_f_f32m1(0, vlm1), - vs01 = vfmv_v_f_f32m1(0, vlm1), vs11 = vfmv_v_f_f32m1(0, vlm1), vs21 = vfmv_v_f_f32m1(0, vlm1), - vs02 = vfmv_v_f_f32m1(0, vlm1), vs12 = vfmv_v_f_f32m1(0, vlm1), vs22 = vfmv_v_f_f32m1(0, vlm1), - vs03 = vfmv_v_f_f32m1(0, vlm1), vs13 = vfmv_v_f_f32m1(0, vlm1), vs23 = vfmv_v_f_f32m1(0, vlm1), - vs04 = vfmv_v_f_f32m1(0, vlm1), vs14 = vfmv_v_f_f32m1(0, vlm1), vs24 = vfmv_v_f_f32m1(0, vlm1), - vs05 = vfmv_v_f_f32m1(0, vlm1), vs15 = vfmv_v_f_f32m1(0, vlm1), vs25 = vfmv_v_f_f32m1(0, vlm1), - vs06 = vfmv_v_f_f32m1(0, vlm1), vs16 = vfmv_v_f_f32m1(0, vlm1), vs26 = vfmv_v_f_f32m1(0, vlm1), - vs07 = vfmv_v_f_f32m1(0, vlm1), vs17 = vfmv_v_f_f32m1(0, vlm1), vs27 = vfmv_v_f_f32m1(0, vlm1); - - for (int k = 0; k < vecsize; k += vl, avl -= vl) - { - vl = vsetvl_e32m1(avl); - vfloat32m1_t w0 = vle32_v_f32m1(wptr0 + k, vl); - vfloat32m1_t w1 = vle32_v_f32m1(wptr1 + k, vl); - vfloat32m1_t w2 = vle32_v_f32m1(wptr2 + k, vl); - vfloat32m1_t r0 = vle32_v_f32m1(rptr, vl); - - vs00 = vfmacc_vv_f32m1(vs00, w0, r0, vl); - vs10 = vfmacc_vv_f32m1(vs10, w1, r0, vl); - vs20 = vfmacc_vv_f32m1(vs20, w2, r0, vl); - - r0 = vle32_v_f32m1(rptr1, vl); - vs01 = vfmacc_vv_f32m1(vs01, w0, r0, vl); - vs11 = vfmacc_vv_f32m1(vs11, w1, r0, vl); - vs21 = vfmacc_vv_f32m1(vs21, w2, r0, vl); - - r0 = vle32_v_f32m1(rptr2, vl); - vs02 = vfmacc_vv_f32m1(vs02, w0, r0, vl); - vs12 = vfmacc_vv_f32m1(vs12, w1, r0, vl); - vs22 = vfmacc_vv_f32m1(vs22, w2, r0, vl); - - r0 = vle32_v_f32m1(rptr3, vl); - vs03 = vfmacc_vv_f32m1(vs03, w0, r0, vl); - vs13 = vfmacc_vv_f32m1(vs13, w1, r0, vl); - vs23 = vfmacc_vv_f32m1(vs23, w2, r0, vl); - - r0 = vle32_v_f32m1(rptr4, vl); - vs04 = vfmacc_vv_f32m1(vs04, w0, r0, vl); - vs14 = vfmacc_vv_f32m1(vs14, w1, r0, vl); - vs24 = vfmacc_vv_f32m1(vs24, w2, r0, vl); - - r0 = vle32_v_f32m1(rptr5, vl); - vs05 = vfmacc_vv_f32m1(vs05, w0, r0, vl); - vs15 = vfmacc_vv_f32m1(vs15, w1, r0, vl); - vs25 = vfmacc_vv_f32m1(vs25, w2, r0, vl); - - r0 = vle32_v_f32m1(rptr6, vl); - vs06 = vfmacc_vv_f32m1(vs06, w0, r0, vl); - vs16 = vfmacc_vv_f32m1(vs16, w1, r0, vl); - vs26 = vfmacc_vv_f32m1(vs26, w2, r0, vl); - - r0 = vle32_v_f32m1(rptr7, vl); - vs07 = vfmacc_vv_f32m1(vs07, w0, r0, vl); - vs17 = vfmacc_vv_f32m1(vs17, w1, r0, vl); - vs27 = vfmacc_vv_f32m1(vs27, w2, r0, vl); - - rptr += vl; rptr1 += vl; rptr2 += vl; rptr3 += vl; - rptr4 += vl; rptr5 += vl; rptr6 += vl; rptr7 += vl; - } - - // compute sum of each vs - vfloat32m1_t zero = vfmv_v_f_f32m1(0, vlm1); - // unroll_tail(vl) is required here to be at least FASCONV_BASE_VECSZ, aka 8. - float sum0[FASCONV_BASE_VECSZ], sum1[FASCONV_BASE_VECSZ], sum2[FASCONV_BASE_VECSZ]; - sum0[0] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs00, zero, vlm1)); - sum0[1] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs01, zero, vlm1)); - sum0[2] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs02, zero, vlm1)); - sum0[3] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs03, zero, vlm1)); - sum0[4] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs04, zero, vlm1)); - sum0[5] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs05, zero, vlm1)); - sum0[6] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs06, zero, vlm1)); - sum0[7] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs07, zero, vlm1)); - sum1[0] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs10, zero, vlm1)); - sum1[1] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs11, zero, vlm1)); - sum1[2] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs12, zero, vlm1)); - sum1[3] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs13, zero, vlm1)); - sum1[4] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs14, zero, vlm1)); - sum1[5] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs15, zero, vlm1)); - sum1[6] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs16, zero, vlm1)); - sum1[7] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs17, zero, vlm1)); - sum2[0] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs20, zero, vlm1)); - sum2[1] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs21, zero, vlm1)); - sum2[2] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs22, zero, vlm1)); - sum2[3] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs23, zero, vlm1)); - sum2[4] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs24, zero, vlm1)); - sum2[5] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs25, zero, vlm1)); - sum2[6] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs26, zero, vlm1)); - sum2[7] = vfmv_f_s_f32m1_f32(vfredosum_vs_f32m1_f32m1(zero, vs27, zero, vlm1)); - - // if VLEN = 128, so LMUL = 2 for unroll_tail(vl) = 8. - // otherwise, VLEN >=256, we only use fist 8 element of the vReg. - vfloat32m2_t s0, s1, s2; - if( initOutput ) - { - s0 = vfmv_v_f_f32m2(bias0, unroll_tail); - s1 = vfmv_v_f_f32m2(bias1, unroll_tail); - s2 = vfmv_v_f_f32m2(bias2, unroll_tail); - } - else - { - s0 = vle32_v_f32m2(outptr0 + j, unroll_tail); - s1 = vle32_v_f32m2(outptr1 + j, unroll_tail); - s2 = vle32_v_f32m2(outptr2 + j, unroll_tail); - } - s0 = vfadd_vv_f32m2(vle32_v_f32m2(sum0, unroll_tail), s0, unroll_tail); - s1 = vfadd_vv_f32m2(vle32_v_f32m2(sum1, unroll_tail), s1, unroll_tail); - s2 = vfadd_vv_f32m2(vle32_v_f32m2(sum2, unroll_tail), s2, unroll_tail); - - if( relu ) - { - float r0 = relu[i], r1 = relu[i+1], r2 = relu[i+2]; - if( i+2 >= outCn ) - { - r2 = r1; - if( i+1 >= outCn ) - r2 = r1 = r0; - } - vbool16_t m0 = vmfgt_vf_f32m2_b16(s0, 0, unroll_tail); - vbool16_t m1 = vmfgt_vf_f32m2_b16(s1, 0, unroll_tail); - vbool16_t m2 = vmfgt_vf_f32m2_b16(s2, 0, unroll_tail); - s0 = vmerge_vvm_f32m2(m0, vfmul_vf_f32m2(s0, r0, unroll_tail), s0, unroll_tail); - s1 = vmerge_vvm_f32m2(m1, vfmul_vf_f32m2(s1, r1, unroll_tail), s1, unroll_tail); - s2 = vmerge_vvm_f32m2(m2, vfmul_vf_f32m2(s2, r2, unroll_tail), s2, unroll_tail); - } - - vse32_v_f32m2(outptr0 + j, s0, unroll_tail); - vse32_v_f32m2(outptr1 + j, s1, unroll_tail); - vse32_v_f32m2(outptr2 + j, s2, unroll_tail); - } - } -} - /* Example for load_deinterleave: input: ptr[16] = {1,2,3, ... ,14,15,16} @@ -1343,5 +848,373 @@ void fastDepthwiseConv( const float* wptr, #endif // CV_RVV +#if !defined(CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY) && CV_LASX + +static inline void _v256_load_deinterleave(const float* ptr, __m256& a, __m256& b) +{ + __m256 t0 = (__m256)__lasx_xvld(ptr, 0); + __m256 t1 = (__m256)__lasx_xvld(ptr, 8*4); + + __m256 lo = (__m256)__lasx_xvpermi_q(t0, t1, 2+0*16); + __m256 hi = (__m256)__lasx_xvpermi_q(t0, t1, 3+1*16); + + a = (__m256)__lasx_xvpermi_w(hi, lo, 0x88); + b = (__m256)__lasx_xvpermi_w(hi, lo, 0xdd); +} + +void fastDepthwiseConv( const float* wptr, + int kernel_h, int kernel_w, + int stride_h, int stride_w, + int dilation_h, int dilation_w, + int pad_t, int pad_l, + const float* biasptr, const float* relu, + const float* inptr_, + int height, int width, + float* outptr_, + int out_d, int outH, int outW ) +{ + const float w00_ = wptr[0], w01_ = wptr[1], w02_ = wptr[2], + w10 = wptr[3], w11 = wptr[4], w12 = wptr[5], + w20_ = wptr[6], w21_ = wptr[7], w22_ = wptr[8]; + int outW1 = min(outW, (width - dilation_w*(kernel_w - 1) + pad_l)/stride_w); + float relu_coeff = relu ? relu[out_d] : 1.f, bias = biasptr[out_d]; + + for (int out_i = 0; out_i < outH; out_i++) + { + int in_i = out_i * stride_h - pad_t, out_j = 0; + const float* imgptr0 = inptr_ + in_i*width; + const float* imgptr1 = imgptr0 + dilation_h*width; + const float* imgptr2 = imgptr0 + (dilation_h*2)*width; + float out, w00 = w00_, w01 = w01_, w02 = w02_; + float w20 = w20_, w21 = w21_, w22 = w22_; + if (in_i < 0) + { + w00 = w01 = w02 = 0.f; + imgptr0 = imgptr1; + } + else if (in_i + dilation_h*(kernel_h-1) >= height) + { + w20 = w21 = w22 = 0.f; + imgptr2 = imgptr1; + } + float* outptr = outptr_ + out_i*outW; + if (pad_l > 0) + { + out = imgptr0[0]*w01 + imgptr0[dilation_w]*w02 + + imgptr1[0]*w11 + imgptr1[dilation_w]*w12 + + imgptr2[0]*w21 + imgptr2[dilation_w]*w22 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[0] = out; + out_j = 1; + } + + if (stride_w == 1 || (stride_w == 2 && dilation_w == 1)) + { + const int VECSZ = 8; + __m256 vw00 = _v256_setall_ps(w00), vw01 = _v256_setall_ps(w01), vw02 = _v256_setall_ps(w02), + vw10 = _v256_setall_ps(w10), vw11 = _v256_setall_ps(w11), vw12 = _v256_setall_ps(w12), + vw20 = _v256_setall_ps(w20), vw21 = _v256_setall_ps(w21), vw22 = _v256_setall_ps(w22); + __m256 z = (__m256)__lasx_xvxor_v((__m256i)vw00, (__m256i)vw00), + vbias = _v256_setall_ps(bias), vrc = _v256_setall_ps(relu_coeff); + + if( stride_w == 1 ) + for( ; out_j < outW1; out_j += VECSZ ) + { + if (out_j + VECSZ > outW1 && out_j > pad_l) + out_j = outW1 - VECSZ; + int in_j = out_j * stride_w - pad_l; + __m256 v00 = (__m256)__lasx_xvld(imgptr0 + in_j, 0), + v01 = (__m256)__lasx_xvld(imgptr0 + in_j + dilation_w, 0), + v02 = (__m256)__lasx_xvld(imgptr0 + in_j + dilation_w*2, 0), + v10 = (__m256)__lasx_xvld(imgptr1 + in_j, 0), + v11 = (__m256)__lasx_xvld(imgptr1 + in_j + dilation_w, 0), + v12 = (__m256)__lasx_xvld(imgptr1 + in_j + dilation_w*2, 0), + v20 = (__m256)__lasx_xvld(imgptr2 + in_j, 0), + v21 = (__m256)__lasx_xvld(imgptr2 + in_j + dilation_w, 0), + v22 = (__m256)__lasx_xvld(imgptr2 + in_j + dilation_w*2, 0); + + __m256 vout0 = __lasx_xvfmadd_s(v00, vw00, vbias); + __m256 vout1 = __lasx_xvfmul_s(v01, vw01); + __m256 vout2 = __lasx_xvfmul_s(v02, vw02); + + vout0 = __lasx_xvfmadd_s(v10, vw10, vout0); + vout1 = __lasx_xvfmadd_s(v11, vw11, vout1); + vout2 = __lasx_xvfmadd_s(v12, vw12, vout2); + + vout0 = __lasx_xvfmadd_s(v20, vw20, vout0); + vout1 = __lasx_xvfmadd_s(v21, vw21, vout1); + vout2 = __lasx_xvfmadd_s(v22, vw22, vout2); + + vout0 = __lasx_xvfadd_s(__lasx_xvfadd_s(vout0, vout1), vout2); + if (relu) + { + __m256i m = __lasx_xvfcmp_clt_s(z, vout0); + vout0 = (__m256)__lasx_xvbitsel_v((__m256i)__lasx_xvfmul_s(vout0, vrc), (__m256i)vout0, m); + } + __lasx_xvst(vout0, outptr + out_j, 0); + } + else + for( ; out_j < outW1; out_j += VECSZ ) + { + if (out_j + VECSZ > outW1 && out_j > pad_l) + out_j = outW1 - VECSZ; + int in_j = out_j * stride_w - pad_l; + __m256 v00, v01, v02, v10, v11, v12, v20, v21, v22, unused; + _v256_load_deinterleave(imgptr0 + in_j, v00, v01); + _v256_load_deinterleave(imgptr0 + in_j + 2, v02, unused); + _v256_load_deinterleave(imgptr1 + in_j, v10, v11); + _v256_load_deinterleave(imgptr1 + in_j + 2, v12, unused); + _v256_load_deinterleave(imgptr2 + in_j, v20, v21); + _v256_load_deinterleave(imgptr2 + in_j + 2, v22, unused); + + __m256 vout0 = __lasx_xvfmadd_s(v00, vw00, vbias); + __m256 vout1 = __lasx_xvfmul_s(v01, vw01); + __m256 vout2 = __lasx_xvfmul_s(v02, vw02); + + vout0 = __lasx_xvfmadd_s(v10, vw10, vout0); + vout1 = __lasx_xvfmadd_s(v11, vw11, vout1); + vout2 = __lasx_xvfmadd_s(v12, vw12, vout2); + + vout0 = __lasx_xvfmadd_s(v20, vw20, vout0); + vout1 = __lasx_xvfmadd_s(v21, vw21, vout1); + vout2 = __lasx_xvfmadd_s(v22, vw22, vout2); + + vout0 = __lasx_xvfadd_s(__lasx_xvfadd_s(vout0, vout1), vout2); + if (relu) + { + __m256i m = __lasx_xvfcmp_clt_s(z, vout0); + vout0 = (__m256)__lasx_xvbitsel_v((__m256i)__lasx_xvfmul_s(vout0, vrc), (__m256i)vout0, m); + } + __lasx_xvst(vout0, outptr + out_j, 0); + } + } + + for (; out_j < outW1; out_j++) + { + int in_j = out_j * stride_w - pad_l; + out = imgptr0[in_j]*w00 + imgptr0[in_j + dilation_w]*w01 + imgptr0[in_j + dilation_w*2]*w02 + + imgptr1[in_j]*w10 + imgptr1[in_j + dilation_w]*w11 + imgptr1[in_j + dilation_w*2]*w12 + + imgptr2[in_j]*w20 + imgptr2[in_j + dilation_w]*w21 + imgptr2[in_j + dilation_w*2]*w22 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[out_j] = out; + } + + for (; out_j < outW; out_j++ ) + { + int in_j0 = out_j * stride_w - pad_l, in_j1 = in_j0 + dilation_w, in_j2 = in_j0 + dilation_w*2; + float s0 = 1.f, s1 = 1.f, s2 = 1.f; + if (in_j0 >= width) + { + in_j0 = 0; + s0 = 0.f; + } + if (in_j1 >= width) + { + in_j1 = 0; + s1 = 0.f; + } + if (in_j2 >= width) + { + in_j2 = 0; + s2 = 0.f; + } + out = imgptr0[in_j0]*w00*s0 + imgptr0[in_j1]*w01*s1 + imgptr0[in_j2]*w02*s2 + + imgptr1[in_j0]*w10*s0 + imgptr1[in_j1]*w11*s1 + imgptr1[in_j2]*w12*s2 + + imgptr2[in_j0]*w20*s0 + imgptr2[in_j1]*w21*s1 + imgptr2[in_j2]*w22*s2 + bias; + if (relu) + out = out > 0.f ? out : out*relu_coeff; + outptr[out_j] = out; + } + } +} + +// dst = vec * weights^t + bias +void fastGEMM1T( const float* vec, const float* weights, + size_t wstep, const float* bias, + float* dst, int nvecs, int vecsize ) +{ + int i = 0; + __m256i v256_tmp; + + for( ; i <= nvecs - 8; i += 8 ) + { + const float* wptr = weights + i*wstep; + __m256 vs0 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), vs1 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), + vs2 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), vs3 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), + vs4 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), vs5 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), + vs6 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), vs7 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp); + + for( int k = 0; k < vecsize; k += 8, wptr += 8 ) + { + __m256 v = (__m256)__lasx_xvld(vec + k, 0); + + vs0 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr, 0), v, vs0); + vs1 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr + wstep, 0), v, vs1); + vs2 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr + wstep*2, 0), v, vs2); + vs3 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr + wstep*3, 0), v, vs3); + vs4 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr + wstep*4, 0), v, vs4); + vs5 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr + wstep*5, 0), v, vs5); + vs6 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr + wstep*6, 0), v, vs6); + vs7 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr + wstep*7, 0), v, vs7); + } + + /*s0*/ + __m256 vs00_perm = (__m256)__lasx_xvpermi_d(vs0, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs00_add_2w = __lasx_xvfadd_s(vs0, vs00_perm); + __m256 tmp00_srl = (__m256)__lasx_xvsrli_d(vs00_add_2w, 32); + __m256 vs00_add_4w = __lasx_xvfadd_s(vs00_add_2w, tmp00_srl); + + __m256 vs01_perm = (__m256)__lasx_xvpermi_d(vs1, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs01_add_2w = __lasx_xvfadd_s(vs1, vs01_perm); + __m256 tmp01_srl = (__m256)__lasx_xvsrli_d(vs01_add_2w, 32); + __m256 vs01_add_4w = __lasx_xvfadd_s(vs01_add_2w, tmp01_srl); + + __m256 vs02_perm = (__m256)__lasx_xvpermi_d(vs2, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs02_add_2w = __lasx_xvfadd_s(vs2, vs02_perm); + __m256 tmp02_srl = (__m256)__lasx_xvsrli_d(vs02_add_2w, 32); + __m256 vs02_add_4w = __lasx_xvfadd_s(vs02_add_2w, tmp02_srl); + + __m256 vs03_perm = (__m256)__lasx_xvpermi_d(vs3, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs03_add_2w = __lasx_xvfadd_s(vs3, vs03_perm); + __m256 tmp03_srl = (__m256)__lasx_xvsrli_d(vs03_add_2w, 32); + __m256 vs03_add_4w = __lasx_xvfadd_s(vs03_add_2w, tmp03_srl); + + __m256i vs01_vs00 = __lasx_xvpackev_w((__m256i)vs01_add_4w, (__m256i)vs00_add_4w); + __m256i vs03_vs02 = __lasx_xvpackev_w((__m256i)vs03_add_4w, (__m256i)vs02_add_4w); + __m256 s0 = (__m256)__lasx_xvpackev_d(vs03_vs02, vs01_vs00); + + /*s1*/ + __m256 vs10_perm = (__m256)__lasx_xvpermi_d(vs4, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs10_add_2w = __lasx_xvfadd_s(vs4, vs10_perm); + __m256 tmp10_srl = (__m256)__lasx_xvsrli_d(vs10_add_2w, 32); + __m256 vs10_add_4w = __lasx_xvfadd_s(vs10_add_2w, tmp10_srl); + + __m256 vs11_perm = (__m256)__lasx_xvpermi_d(vs5, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs11_add_2w = __lasx_xvfadd_s(vs5, vs11_perm); + __m256 tmp11_srl = (__m256)__lasx_xvsrli_d(vs11_add_2w, 32); + __m256 vs11_add_4w = __lasx_xvfadd_s(vs11_add_2w, tmp11_srl); + + __m256 vs12_perm = (__m256)__lasx_xvpermi_d(vs6, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs12_add_2w = __lasx_xvfadd_s(vs6, vs12_perm); + __m256 tmp12_srl = (__m256)__lasx_xvsrli_d(vs12_add_2w, 32); + __m256 vs12_add_4w = __lasx_xvfadd_s(vs12_add_2w, tmp12_srl); + + __m256 vs13_perm = (__m256)__lasx_xvpermi_d(vs7, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs13_add_2w = __lasx_xvfadd_s(vs7, vs13_perm); + __m256 tmp13_srl = (__m256)__lasx_xvsrli_d(vs13_add_2w, 32); + __m256 vs13_add_4w = __lasx_xvfadd_s(vs13_add_2w, tmp13_srl); + + __m256i vs11_vs10 = __lasx_xvpackev_w((__m256i)vs11_add_4w, (__m256i)vs10_add_4w); + __m256i vs13_vs12 = __lasx_xvpackev_w((__m256i)vs13_add_4w, (__m256i)vs12_add_4w); + __m256 s1 = (__m256)__lasx_xvpackev_d(vs13_vs12, vs11_vs10); + + s0 = __lasx_xvfadd_s(s0, (__m256)__lasx_xvpermi_q(s0, s0, 1)); + s1 = __lasx_xvfadd_s(s1, (__m256)__lasx_xvpermi_q(s1, s1, 1)); + + s0 = __lasx_xvfadd_s(s0, (__m256)__lasx_xvld(bias + i, 0)); + s1 = __lasx_xvfadd_s(s1, (__m256)__lasx_xvld(bias + i, 4*4)); + + __lsx_vst(*(__m128*)&s0, dst + i, 0); + __lsx_vst(*(__m128*)&s1, dst + i, 4*4); + } + + float temp = 0.f; + for( ; i < nvecs; i++ ) + { + const float* wptr = weights + i*wstep; + __m256 vs0 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp); + + for( int k = 0; k < vecsize; k += 8, wptr += 8 ) + { + __m256 v = (__m256)__lasx_xvld(vec + k, 0); + vs0 = __lasx_xvfmadd_s((__m256)__lasx_xvld(wptr, 0), v, vs0); + } + + __m256i vs0_perm = __lasx_xvpermi_d(vs0, (2<<6) + (3<<4) + (0<<2) + 1); + __m256 vs0_add_2w = __lasx_xvfadd_s(vs0, (__m256)vs0_perm); + __m256i tmp_srl = __lasx_xvsrli_d(vs0_add_2w, 32); + __m256 vs0_add_4w = __lasx_xvfadd_s(vs0_add_2w, (__m256)tmp_srl); + temp = ((v8f32)vs0_add_4w)[0] + ((v8f32)vs0_add_4w)[4]; + dst[i] = temp + bias[i]; + } +} + + +void fastGEMM( const float* aptr, size_t astep, const float* bptr, + size_t bstep, float* cptr, size_t cstep, + int ma, int na, int nb ) +{ + int n = 0; + + for( ; n <= nb - 16; n += 16 ) + { + for( int m = 0; m < ma; m += 4 ) + { + const float* aptr0 = aptr + astep*m; + const float* aptr1 = aptr + astep*std::min(m+1, ma-1); + const float* aptr2 = aptr + astep*std::min(m+2, ma-1); + const float* aptr3 = aptr + astep*std::min(m+3, ma-1); + + float* cptr0 = cptr + cstep*m; + float* cptr1 = cptr + cstep*std::min(m+1, ma-1); + float* cptr2 = cptr + cstep*std::min(m+2, ma-1); + float* cptr3 = cptr + cstep*std::min(m+3, ma-1); + + __m256i v256_tmp; + __m256 d00 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), d01 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp); + __m256 d10 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), d11 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp); + __m256 d20 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), d21 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp); + __m256 d30 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp), d31 = (__m256)__lasx_xvxor_v(v256_tmp, v256_tmp); + + for( int k = 0; k < na; k++ ) + { + __m256 a0 = _v256_setall_ps(aptr0[k]); + __m256 a1 = _v256_setall_ps(aptr1[k]); + __m256 a2 = _v256_setall_ps(aptr2[k]); + __m256 a3 = _v256_setall_ps(aptr3[k]); + + __m256 b0 = (__m256)__lasx_xvld(bptr + k*bstep + n, 0); + __m256 b1 = (__m256)__lasx_xvld(bptr + k*bstep + n + 8, 0); + d00 = __lasx_xvfmadd_s(a0, b0, d00); + d01 = __lasx_xvfmadd_s(a0, b1, d01); + d10 = __lasx_xvfmadd_s(a1, b0, d10); + d11 = __lasx_xvfmadd_s(a1, b1, d11); + d20 = __lasx_xvfmadd_s(a2, b0, d20); + d21 = __lasx_xvfmadd_s(a2, b1, d21); + d30 = __lasx_xvfmadd_s(a3, b0, d30); + d31 = __lasx_xvfmadd_s(a3, b1, d31); + } + + __lasx_xvst(d00, cptr0 + n, 0); + __lasx_xvst(d01, cptr0 + n, 8*4); + __lasx_xvst(d10, cptr1 + n, 0); + __lasx_xvst(d11, cptr1 + n, 8*4); + __lasx_xvst(d20, cptr2 + n, 0); + __lasx_xvst(d21, cptr2 + n, 8*4); + __lasx_xvst(d30, cptr3 + n, 0); + __lasx_xvst(d31, cptr3 + n, 8*4); + } + } + + for( ; n < nb; n++ ) + { + for( int m = 0; m < ma; m++ ) + { + const float* aptr0 = aptr + astep*m; + float* cptr0 = cptr + cstep*m; + float d0 = 0.f; + + for( int k = 0; k < na; k++ ) + d0 += aptr0[k]*bptr[k*bstep + n]; + + cptr0[n] = d0; + } + } +} + +#endif // CV_LASX + CV_CPU_OPTIMIZATION_NAMESPACE_END }} // namespace diff --git a/modules/dnn/src/layers/lrn_layer.cpp b/modules/dnn/src/layers/lrn_layer.cpp index 6c3a654159..f012a91730 100644 --- a/modules/dnn/src/layers/lrn_layer.cpp +++ b/modules/dnn/src/layers/lrn_layer.cpp @@ -47,6 +47,7 @@ #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" #include "../op_vkcom.hpp" +#include "../op_cann.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/dnn/shape_utils.hpp" @@ -106,7 +107,8 @@ public: return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE || - (backendId == DNN_BACKEND_VKCOM && haveVulkan() && (size % 2 == 1) && (type == CHANNEL_NRM)); + (backendId == DNN_BACKEND_VKCOM && haveVulkan() && (size % 2 == 1) && (type == CHANNEL_NRM)) || + backendId == DNN_BACKEND_CANN; } #ifdef HAVE_OPENCL @@ -442,6 +444,38 @@ public: #endif // HAVE_HALIDE } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + + // create operator + std::string op_name = cv::format("lrn_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_depth_radius(size); + op->set_attr_bias(bias); + op->set_attr_alpha(alpha); + op->set_attr_beta(beta); + op->set_attr_norm_region("ACROSS_CHANNELS"); + if (type == SPATIAL_NRM) + op->set_attr_norm_region("WITHIN_CHANNEL"); + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + // set outputs + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, const std::vector >& nodes) CV_OVERRIDE diff --git a/modules/dnn/src/layers/nary_eltwise_layers.cpp b/modules/dnn/src/layers/nary_eltwise_layers.cpp index db37f16060..3f43c024c7 100644 --- a/modules/dnn/src/layers/nary_eltwise_layers.cpp +++ b/modules/dnn/src/layers/nary_eltwise_layers.cpp @@ -4,12 +4,21 @@ #include "../precomp.hpp" #include "layers_common.hpp" +#include "../op_cuda.hpp" +#include "../op_cann.hpp" +#include "../ie_ngraph.hpp" + #include #include #include #include +#ifdef HAVE_CUDA +#include "../cuda4dnn/primitives/eltwise.hpp" +using namespace cv::dnn::cuda4dnn; +#endif + namespace cv { namespace dnn @@ -51,11 +60,11 @@ public: op = OPERATION::EQUAL; else if (operation == "greater") op = OPERATION::GREATER; - else if (operation == "greater_equal") + else if (operation == "greaterorequal") op = OPERATION::GREATER_EQUAL; else if (operation == "less") op = OPERATION::LESS; - else if (operation == "less_equal") + else if (operation == "lessorequal") op = OPERATION::LESS_EQUAL; else if (operation == "pow") op = OPERATION::POW; @@ -91,6 +100,20 @@ public: virtual bool supportBackend(int backendId) CV_OVERRIDE { +#ifdef HAVE_CANN + if (backendId == DNN_BACKEND_CANN) + return op == OPERATION::ADD || op == OPERATION::PROD || op == OPERATION::DIV || + op == OPERATION::DIV || op == OPERATION::MAX || op == OPERATION::MIN; +#endif + if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) + return (op == OPERATION::ADD || + op == OPERATION::PROD || + op == OPERATION::GREATER_EQUAL || + op == OPERATION::LESS_EQUAL + ); + if (op == OPERATION::MAX || op == OPERATION::MIN || op == OPERATION::SUM || + op == OPERATION::PROD || op == OPERATION::DIV) + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; return backendId == DNN_BACKEND_OPENCV; } @@ -641,6 +664,80 @@ public: }; } +#ifdef HAVE_CUDA + Ptr initCUDA( + void *context_, + const std::vector>& inputs, + const std::vector>& outputs + ) override + { + auto context = reinterpret_cast(context_); + + auto input_wrapper = inputs[0].dynamicCast(); + for (int i = 1; i < inputs.size(); i++) + { + auto from_wrapper = inputs[i].dynamicCast(); + if (input_wrapper->getShape() != from_wrapper->getShape()) + return Ptr(); + } + + auto op_ = [this] { + switch (op) { + case OPERATION::MAX: return cuda4dnn::EltwiseOpType::MAX; + case OPERATION::MIN: return cuda4dnn::EltwiseOpType::MIN; + case OPERATION::SUM: return cuda4dnn::EltwiseOpType::SUM; + case OPERATION::PROD: return cuda4dnn::EltwiseOpType::PRODUCT; + case OPERATION::DIV: return cuda4dnn::EltwiseOpType::DIV; + default: CV_Error(Error::StsNotImplemented, "Other operators except MAX, MIN, SUM, PRODUCT and DIV are not supported with cuda."); + } + }(); + + return make_cuda_node(preferableTarget, std::move(context->stream), op_, std::vector()); + } +#endif + +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + CV_Assert(inputsWrapper.size() == 2); + CV_Assert(nodes.size() == 2); + + auto op_x1 = nodes[0].dynamicCast()->getOp(); + auto x1 = inputsWrapper[0].dynamicCast(); + auto x1_desc = x1->getTensorDesc(); + auto op_x2 = nodes[1].dynamicCast()->getOp(); + auto x2 = inputsWrapper[1].dynamicCast(); + auto x2_desc = x2->getTensorDesc(); + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::shared_ptr eltwise_operator = nullptr; + // add, mul, div, max, min + switch (op) + { +#define BUILD_CANN_ELTWISE_OP(op_type, class_name, op_name) \ + case op_type: { \ + auto eltwise_op = \ + std::make_shared(op_name); \ + eltwise_op->set_input_x1_by_name(*op_x1, "y"); \ + eltwise_op->set_input_x2_by_name(*op_x2, "y"); \ + eltwise_op->update_input_desc_x1(*x1_desc); \ + eltwise_op->update_input_desc_x2(*x2_desc); \ + eltwise_op->update_output_desc_y(*output_desc); \ + eltwise_operator = eltwise_op; \ + } break; + BUILD_CANN_ELTWISE_OP(OPERATION::ADD, Add, cv::format("add_%d", index)); + BUILD_CANN_ELTWISE_OP(OPERATION::PROD, Mul, cv::format("mul_%d", index)); + BUILD_CANN_ELTWISE_OP(OPERATION::DIV, Xdivy, cv::format("div_%d", index)); + BUILD_CANN_ELTWISE_OP(OPERATION::MAX, Maximum, cv::format("max_%d", index)); + BUILD_CANN_ELTWISE_OP(OPERATION::MIN, Minimum, cv::format("min_%d", index)); +#undef BUILD_CANN_ELTWISE_OP + default: CV_Error(Error::StsNotImplemented, "Unsupported eltwise operation"); + } + + return Ptr(new CannBackendNode(eltwise_operator)); + } +#endif // HAVE_CANN + virtual bool tryQuantize(const std::vector > &scales, const std::vector > &zeropoints, LayerParams& params) CV_OVERRIDE { @@ -653,6 +750,37 @@ public: CV_Assert(inputs.size()); return inputs.size() * total(outputs[0]); } + +#ifdef HAVE_DNN_NGRAPH + virtual Ptr initNgraph(const std::vector >& inputs, const std::vector >& nodes) CV_OVERRIDE + { + CV_Assert(inputs.size() == 2); + auto& inp0 = nodes[0].dynamicCast()->node; + auto& inp1 = nodes[1].dynamicCast()->node; + + if (inp0->get_element_type() != inp1->get_element_type()) { + auto dtype = preferableTarget == DNN_TARGET_OPENCL_FP16 || preferableTarget == DNN_TARGET_MYRIAD ? + ngraph::element::f16 : ngraph::element::f32; + if (inp0->get_element_type() != dtype) + inp0 = std::make_shared(inp0, dtype); + if (inp1->get_element_type() != dtype) + inp1 = std::make_shared(inp1, dtype); + } + + std::shared_ptr node; + if (op == OPERATION::ADD) + node = std::make_shared(inp0, inp1); + else if (op == OPERATION::PROD) + node = std::make_shared(inp0, inp1); + else if (op == OPERATION::GREATER_EQUAL) + node = std::make_shared(inp0, inp1); + else if (op == OPERATION::LESS_EQUAL) + node = std::make_shared(inp0, inp1); + else + CV_Error(Error::StsNotImplemented, "Operation is not implemented for nGraph backend"); + return Ptr(new InfEngineNgraphNode(node)); + } +#endif }; Ptr NaryEltwiseLayer::create(const LayerParams& params) diff --git a/modules/dnn/src/layers/normalize_bbox_layer.cpp b/modules/dnn/src/layers/normalize_bbox_layer.cpp index 2017d76801..f0ad6e6f61 100644 --- a/modules/dnn/src/layers/normalize_bbox_layer.cpp +++ b/modules/dnn/src/layers/normalize_bbox_layer.cpp @@ -215,8 +215,8 @@ public: const float* inpData = inp0.ptr(); float* outData = outputs[0].ptr(); - size_t num = total(shape(inp0.size), 0, startAxis); - size_t numPlanes = total(shape(inp0.size), startAxis, endAxis + 1); + size_t num = total(inp0, 0, startAxis); + size_t numPlanes = total(inp0, startAxis, endAxis + 1); CV_Assert(num * numPlanes != 0); size_t planeSize = inp0.total() / (num * numPlanes); for (size_t n = 0; n < num; ++n) diff --git a/modules/dnn/src/layers/padding_layer.cpp b/modules/dnn/src/layers/padding_layer.cpp index aea8ab3168..359c82a1a3 100644 --- a/modules/dnn/src/layers/padding_layer.cpp +++ b/modules/dnn/src/layers/padding_layer.cpp @@ -15,6 +15,7 @@ Implementation of padding layer, which adds paddings to input blob. #include "../op_halide.hpp" #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" +#include "../op_cann.hpp" #include @@ -113,7 +114,8 @@ public: #endif return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - (backendId == DNN_BACKEND_HALIDE && haveHalide() && dstRanges.size() == 4); + (backendId == DNN_BACKEND_HALIDE && haveHalide() && dstRanges.size() == 4) || + backendId == DNN_BACKEND_CANN; } void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE @@ -219,6 +221,50 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + + // create operator + std::string op_name = cv::format("pad_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_mode(paddingType.c_str()); + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + // set inputs : paddings + std::vector pads; + for (int i = 0; i < paddings.size(); i++) + { + pads.push_back(paddings[i].first); + pads.push_back(paddings[i].second); + } + std::vector pads_shape{(int)pads.size()}; + Mat paddings_mat(pads_shape, CV_32S, &pads[0]); + auto op_const_paddings = std::make_shared(paddings_mat.data, paddings_mat.type(), pads_shape, cv::format("%s_paddings", op_name.c_str())); + op->set_input_paddings(*(op_const_paddings->getOp())); + op->update_input_desc_paddings(*(op_const_paddings->getTensorDesc())); + // set inputs : constant_values + std::vector constant_values_shape{1}; + Mat constant_values_mat(1, 1, CV_32F, Scalar(paddingValue)); + auto op_const_constant_values = std::make_shared(constant_values_mat.data, constant_values_mat.type(), constant_values_shape, cv::format("%s_constant_values", op_name.c_str())); + op->set_input_constant_values(*(op_const_constant_values->getOp())); + op->update_input_desc_constant_values(*(op_const_constant_values->getTensorDesc())); + + // set outputs + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, diff --git a/modules/dnn/src/layers/permute_layer.cpp b/modules/dnn/src/layers/permute_layer.cpp index cce36b951f..1aee12d7ae 100644 --- a/modules/dnn/src/layers/permute_layer.cpp +++ b/modules/dnn/src/layers/permute_layer.cpp @@ -48,6 +48,7 @@ #include "../op_vkcom.hpp" #include "../op_webnn.hpp" #include "../op_timvx.hpp" +#include "../op_cann.hpp" #include #include @@ -143,7 +144,8 @@ public: return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_WEBNN || - (backendId == DNN_BACKEND_VKCOM && haveVulkan()); + (backendId == DNN_BACKEND_VKCOM && haveVulkan()) || + backendId == DNN_BACKEND_CANN; } bool getMemoryShapes(const std::vector &inputs, @@ -438,6 +440,34 @@ public: } } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + + // create operator + std::string op_name = cv::format("permute_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_order(ge::Operator::OpListInt( + _order.begin(), _order.end() + )); + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + // set outputs + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, diff --git a/modules/dnn/src/layers/pooling_layer.cpp b/modules/dnn/src/layers/pooling_layer.cpp index 6c584bf2dd..9b9ced468f 100644 --- a/modules/dnn/src/layers/pooling_layer.cpp +++ b/modules/dnn/src/layers/pooling_layer.cpp @@ -47,6 +47,7 @@ #include "../op_halide.hpp" #include "../op_inf_engine.hpp" #include "../op_webnn.hpp" +#include "../op_cann.hpp" #ifdef HAVE_DNN_NGRAPH #include "../ie_ngraph.hpp" @@ -199,6 +200,12 @@ public: { return type == MAX || type == AVE || type == ROI; } +#ifdef HAVE_CANN + if (backendId == DNN_BACKEND_CANN) + { + return type == MAX || type == AVE; + } +#endif #ifdef HAVE_INF_ENGINE if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { @@ -540,6 +547,82 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + auto op_x = nodes[0].dynamicCast()->getOp(); + auto x_desc = x->getTensorDesc(); + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::string op_name_base = cv::format("pooling_%d", index); + if (type == MAX) + { + std::string op_name = cv::format("max_%s", op_name_base.c_str()); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_ksize(ge::Operator::OpListInt( + {1, 1, (int64_t)kernel_size[0], (int64_t)kernel_size[1]} + )); + op->set_attr_strides(ge::Operator::OpListInt( + {1, 1, (int64_t)strides[0], (int64_t)strides[1]} + )); + std::string cann_pad_mode{"CALCULATED"}; + if (padMode == "SAME" || padMode == "VALID") + cann_pad_mode = padMode; + op->set_attr_padding_mode(cann_pad_mode.c_str()); + op->set_attr_pads(ge::Operator::OpListInt( + {(int64_t)pads_begin[0], (int64_t)pads_end[0], (int64_t)pads_begin[1], (int64_t)pads_end[1]} + )); + op->set_attr_data_format("NCHW"); + op->set_attr_global_pooling(globalPooling); + op->set_attr_ceil_mode(ceilMode); + + // set inputs + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + // set outputs + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } + else if (type == AVE) + { + std::string op_name = cv::format("avg_%s", op_name_base.c_str()); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_ksize(ge::Operator::OpListInt( + {1, 1, (int64_t)kernel_size[0], (int64_t)kernel_size[1]} + )); + op->set_attr_strides(ge::Operator::OpListInt( + {1, 1, (int64_t)strides[0], (int64_t)strides[1]} + )); + std::string cann_pad_mode{"CALCULATED"}; + if (padMode == "SAME" || padMode == "VALID") + cann_pad_mode = padMode; + op->set_attr_padding_mode(cann_pad_mode.c_str()); + op->set_attr_pads(ge::Operator::OpListInt( + {(int64_t)pads_begin[0], (int64_t)pads_end[0], (int64_t)pads_begin[1], (int64_t)pads_end[1]} + )); + op->set_attr_global_pooling(globalPooling); + op->set_attr_ceil_mode(ceilMode); + auto cann_exclusive = !avePoolPaddedArea; + op->set_attr_exclusive(cann_exclusive); + + // set inputs + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + // set outputs + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } + else + CV_Error(Error::StsNotImplemented, "Unsupported pooling type"); + } +#endif #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, @@ -1173,6 +1256,12 @@ public: // Halide::argmax returns tuple (r.x, r.y, max). Halide::Tuple res = argmax(inputBuffer(kx, ky, c, n)); + if (!computeMaxIdx) + { + top(x, y, c, n) = res[2]; + return Ptr(new HalideBackendNode(top)); + } + // Compute offset from argmax in range [0, kernel_size). Halide::Expr max_index; if(paddingLeft || paddingTop) diff --git a/modules/dnn/src/layers/reshape_layer.cpp b/modules/dnn/src/layers/reshape_layer.cpp index 290effd380..3ff8a225b7 100644 --- a/modules/dnn/src/layers/reshape_layer.cpp +++ b/modules/dnn/src/layers/reshape_layer.cpp @@ -47,6 +47,7 @@ #include "../ie_ngraph.hpp" #include "../op_webnn.hpp" #include "../op_timvx.hpp" +#include "../op_cann.hpp" #include @@ -163,8 +164,8 @@ public: ReshapeLayerImpl(const LayerParams& params) { setParamsFrom(params); - int axis = params.get("axis", 0); - int numAxes = params.get("num_axes", -1); + axis = params.get("axis", 0); + numAxes = params.get("num_axes", -1); hasDynamicShapes = params.get("has_dynamic_shapes", false); shapesInitialized = !hasDynamicShapes; @@ -224,7 +225,8 @@ public: #endif return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || - backendId == DNN_BACKEND_WEBNN; + backendId == DNN_BACKEND_WEBNN || + backendId == DNN_BACKEND_CANN; } bool getMemoryShapes(const std::vector &inputs, @@ -324,6 +326,39 @@ public: } } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + + // create operator + std::string op_name = cv::format("reshape_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_axis(axis); + op->set_attr_num_axes(numAxes); + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + // set inputs : shape + std::vector shape_of_shape{(int)newShapeDesc.size()}; + Mat shape_mat(shape_of_shape, CV_32S, newShapeDesc.data()); + auto op_const_shape = std::make_shared(shape_mat.data, shape_mat.type(), shape_of_shape, cv::format("%s_shape", op_name.c_str())); + op->set_input_shape(*(op_const_shape->getOp())); + op->update_input_desc_shape(*(op_const_shape->getTensorDesc())); + + // set outputs + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, @@ -464,6 +499,8 @@ public: } private: + int axis; + int numAxes; std::vector outShapes; std::vector dynamicShapes; // Which axes shapes are dynamic and require reinitialization with new input std::vector inputIndices; // Which axes from input are needed to compute correct output shape diff --git a/modules/dnn/src/layers/resize_layer.cpp b/modules/dnn/src/layers/resize_layer.cpp index ab640dbf3f..4342b51b78 100644 --- a/modules/dnn/src/layers/resize_layer.cpp +++ b/modules/dnn/src/layers/resize_layer.cpp @@ -8,6 +8,7 @@ #include "layers_common.hpp" #include "../op_cuda.hpp" #include "../op_inf_engine.hpp" +#include "../op_cann.hpp" #include #ifdef HAVE_DNN_NGRAPH @@ -77,6 +78,9 @@ public: if (backendId == DNN_BACKEND_CUDA) return interpolation == "nearest" || interpolation == "bilinear" || interpolation == "opencv_linear"; + if (backendId == DNN_BACKEND_CANN) + return interpolation == "nearest" || interpolation == "bilinear" || interpolation == "opencv_linear"; + #ifdef HAVE_INF_ENGINE if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { @@ -307,6 +311,67 @@ public: CV_Error(Error::StsNotImplemented, "Unknown interpolation: " + interpolation); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + auto x_desc = x->getTensorDesc(); + auto op_x = nodes[0].dynamicCast()->getOp(); + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + // create operator + std::string op_name = cv::format("resize_%d", index); + + if (interpolation == "nearest") + { + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_align_corners(alignCorners); + op->set_attr_half_pixel_centers(halfPixelCenters); + + // set inputs : x + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + // set inputs : size + std::vector shape_of_size_mat{2}; + Mat size_mat(2, 1, CV_32S, Scalar(outHeight, outWidth)); + auto op_const_size = std::make_shared(size_mat.data, size_mat.type(), shape_of_size_mat, cv::format("%s_size", op_name.c_str())); + op->set_input_size(*(op_const_size->getOp())); + op->update_input_desc_size(*(op_const_size->getTensorDesc())); + + // set outputs + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } + else if (interpolation == "opencv_linear" || interpolation == "bilinear") + { + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_align_corners(alignCorners); + op->set_attr_half_pixel_centers(halfPixelCenters); + + // set inputs : x + op->set_input_x_by_name(*op_x, "y"); + op->update_input_desc_x(*x_desc); + // set inputs : size + std::vector shape_of_size_mat{2}; + Mat size_mat(2, 1, CV_32S, Scalar(outHeight, outWidth)); + auto op_const_size = std::make_shared(size_mat.data, size_mat.type(), shape_of_size_mat, cv::format("%s_size", op_name.c_str())); + op->set_input_size(*(op_const_size->getOp())); + op->update_input_desc_size(*(op_const_size->getTensorDesc())); + + // set outputs + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } + else + CV_Error(Error::StsNotImplemented, "Unsupported interpolation by CANN backend: " + interpolation); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, @@ -336,6 +401,24 @@ public: #else ngraph::op::v4::Interpolate::InterpolateAttrs attrs; +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + if (interpolation == "nearest") { + attrs.mode = ngraph::op::v4::Interpolate::InterpolateMode::NEAREST; + attrs.coordinate_transformation_mode = ngraph::op::v4::Interpolate::CoordinateTransformMode::HALF_PIXEL; + } else if (interpolation == "bilinear") { + attrs.mode = ngraph::op::v4::Interpolate::InterpolateMode::LINEAR_ONNX; + attrs.coordinate_transformation_mode = ngraph::op::v4::Interpolate::CoordinateTransformMode::ASYMMETRIC; + } else { + CV_Error(Error::StsNotImplemented, format("Unsupported interpolation: %s", interpolation.c_str())); + } + attrs.shape_calculation_mode = ngraph::op::v4::Interpolate::ShapeCalcMode::SIZES; + + if (alignCorners) { + attrs.coordinate_transformation_mode = ngraph::op::v4::Interpolate::CoordinateTransformMode::ALIGN_CORNERS; + } + + attrs.nearest_mode = ngraph::op::v4::Interpolate::NearestMode::ROUND_PREFER_FLOOR; +#else if (interpolation == "nearest") { attrs.mode = ngraph::op::v4::Interpolate::InterpolateMode::nearest; attrs.coordinate_transformation_mode = ngraph::op::v4::Interpolate::CoordinateTransformMode::half_pixel; @@ -352,6 +435,7 @@ public: } attrs.nearest_mode = ngraph::op::v4::Interpolate::NearestMode::round_prefer_floor; +#endif // OpenVINO >= 2022.1 std::vector shape = {outHeight, outWidth}; auto out_shape = std::make_shared(ngraph::element::i64, ngraph::Shape{2}, shape.data()); diff --git a/modules/dnn/src/layers/scatterND_layer.cpp b/modules/dnn/src/layers/scatterND_layer.cpp new file mode 100644 index 0000000000..648d35fc0c --- /dev/null +++ b/modules/dnn/src/layers/scatterND_layer.cpp @@ -0,0 +1,202 @@ +// 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 "layers_common.hpp" + +#include // for std::max & std::min + +namespace cv { namespace dnn { + +class ScatterNDLayerImpl CV_FINAL : public ScatterNDLayer +{ +public: + enum class REDUCTION + { + NONE = 1, + ADD, + MUL, + MAX, + MIN + } reduction; + + ScatterNDLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + + String reduction_name = toLowerCase(params.get("reduction", "none")); + if (reduction_name == "none") + reduction = REDUCTION::NONE; + else if (reduction_name == "add") + reduction = REDUCTION::ADD; + else if (reduction_name == "mul") + reduction = REDUCTION::MUL; + else if (reduction_name == "max") + reduction = REDUCTION::MAX; + else if (reduction_name == "min") + reduction = REDUCTION::MIN; + else + CV_Error(cv::Error::StsBadArg, "Unkown reduction \"" + reduction_name + "\""); + } + + virtual bool supportBackend(int backendId) CV_OVERRIDE + { + return backendId == DNN_BACKEND_OPENCV; + } + + virtual bool getMemoryShapes(const std::vector &inputs, + const int requiredOutputs, + std::vector &outputs, + std::vector &internals) const CV_OVERRIDE + { + CV_CheckEQ(inputs.size(), 3ull, "ScatterND: require three inputs."); + + size_t r = inputs[0].size(), q = inputs[1].size(), p = inputs[2].size(), k = inputs[1].back(); + CV_CheckEQ(r + q - inputs[1].back() - 1, p, "ScatterND: updates should have rank of data.dims + indices.dims - indices.size[-1] - 1"); + CV_CheckLE(k, r, "ScatterND: indices.shape[-1] must be less than (or equal to) the rank of input data."); + + for (int i = 0; i < q - 1; i++) // np.ndindex(indices.shape[-1]) + { + CV_CheckEQ(inputs[2][i], inputs[1][i], "ScatterND: updates.shape[0 : rank(indices)-1] must equal to indices.shape[0 : rank(indices)-1]."); + } + for (int i = q - 1, j = k, m = 0; i + m < p; m++) + { + CV_CheckEQ(inputs[2][i + m], inputs[0][j + m], "ScatterND: updates.shape[rank(indices)-1 : ] must equal to data[indices.shape[-1] : rank(data)-1]."); + } + + outputs.assign(1, inputs[0]); + return false; + } + + void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(name, "name", name.c_str()); + + std::vector inputs, outputs; + inputs_arr.getMatVector(inputs); + outputs_arr.getMatVector(outputs); + + const Mat& data = inputs[0]; + const Mat& indices = inputs[1]; + const Mat& updates = inputs[2]; + Mat& out = outputs[0]; + + typeDispatch(outputs[0].type(), data, indices, updates, out); + } + + // NOTE: This impl does not check whether indices have duplicate entries. + // The last duplicate entry will overwrite the previous. + template + void forward_impl(const Functor& rd, const Mat& data, const Mat& indices, const Mat& updates, Mat& out) + { + data.copyTo(out); + + const int* shape = data.size.p; + const size_t* step = data.step.p; + + const int ind_ndims = indices.dims; + const int* ind_shape = indices.size.p; + const T* p_indices = indices.ptr(); + + const int upd_ndims = updates.dims; + const int* upd_shape = updates.size.p; + const T* p_updates = updates.ptr(); + + T* p_out = out.ptr(); + + int k = ind_shape[ind_ndims - 1]; // last dim of indices + size_t total = (size_t)(indices.total() / k); + + size_t updates_size = 1; + for (int i = ind_ndims - 1; i < upd_ndims; i++) + updates_size *= upd_shape[i]; + + size_t inp_start_offset = 0; + size_t ind_start_offset = 0; + size_t upd_start_offset = 0; + for (size_t i = 0; i < total; i++, ind_start_offset += k, upd_start_offset += updates_size) + { + const T* tmp_p_indices = p_indices + ind_start_offset; + inp_start_offset = 0; + for (int j = 0; j < k; j++) + { + CV_Assert(tmp_p_indices[j] < shape[j] && tmp_p_indices[j] > -shape[j]); + inp_start_offset += (((int)tmp_p_indices[j] + shape[j]) % shape[j]) * step[j]; + } + inp_start_offset /= sizeof(T); + + const T* tmp_p_updates = p_updates + upd_start_offset; + T* tmp_p_out = p_out + inp_start_offset; + for (int j = 0; j < updates_size; j++) + tmp_p_out[j] = rd(tmp_p_out[j], tmp_p_updates[j]); + } + } + + template + inline void typeDispatch(const int type, Args&&... args) + { + switch (type) + { + case CV_8U: + reductionDispatch(std::forward(args)...); + break; + case CV_32S: + reductionDispatch(std::forward(args)...); + break; + case CV_32F: + reductionDispatch(std::forward(args)...); + break; + default: + CV_Error(cv::Error::BadDepth, "Unsupported type."); + }; + } + + template + inline void reductionDispatch(Args&&... args) + { + switch (reduction) + { + case REDUCTION::NONE: + { + auto rd = [](const T& a, const T& b) { return b; }; // a from input data, b from updates + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::ADD: + { + auto rd = [](const T& a, const T& b) { return a + b; }; + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::MUL: + { + auto rd = [](const T& a, const T& b) { return a * b; }; + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::MAX: + { + auto rd = [](const T& a, const T& b) { return std::max(a, b); }; + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::MIN: + { + auto rd = [](const T& a, const T& b) { return std::min(a, b); }; + forward_impl(rd, std::forward(args)...); + break; + } + default: + CV_Error(Error::StsBadArg, "Unsupported reduction."); + }; + } +}; + +Ptr ScatterNDLayer::create(const LayerParams& params) +{ + return makePtr(params); +} + +}} // namespace cv::dnn diff --git a/modules/dnn/src/layers/scatter_layer.cpp b/modules/dnn/src/layers/scatter_layer.cpp new file mode 100644 index 0000000000..084eecb03c --- /dev/null +++ b/modules/dnn/src/layers/scatter_layer.cpp @@ -0,0 +1,208 @@ +// 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 "layers_common.hpp" + +#include // for std::max & std::min + +namespace cv { namespace dnn { + +class ScatterLayerImpl CV_FINAL : public ScatterLayer +{ +public: + enum class REDUCTION + { + NONE = 1, + ADD, + MUL, + MAX, + MIN + } reduction; + + ScatterLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + + axis = params.get("axis", 0); + String reduction_name = toLowerCase(params.get("reduction", "none")); + if (reduction_name == "none") + reduction = REDUCTION::NONE; + else if (reduction_name == "add") + reduction = REDUCTION::ADD; + else if (reduction_name == "mul") + reduction = REDUCTION::MUL; + else if (reduction_name == "max") + reduction = REDUCTION::MAX; + else if (reduction_name == "min") + reduction = REDUCTION::MIN; + else + CV_Error(cv::Error::StsBadArg, "Unkown reduction \"" + reduction_name + "\""); + } + + virtual bool supportBackend(int backendId) CV_OVERRIDE + { + return backendId == DNN_BACKEND_OPENCV; + } + + virtual bool getMemoryShapes(const std::vector &inputs, + const int requiredOutputs, + std::vector &outputs, + std::vector &internals) const CV_OVERRIDE + { + CV_CheckEQ(inputs.size(), 3ull, "Scatter: require three inputs."); + CV_CheckEQ(inputs[0].size(), inputs[1].size(), "Scatter: input data should have the same ndim with indices."); + CV_CheckEQ(inputs[0].size(), inputs[2].size(), "Scatter: input data should have the same ndim with updates."); + for (size_t i = 0; i < inputs[0].size(); i++) + { + CV_CheckGE(inputs[0][i], inputs[1][i], "Scatter: each dim of input data should be greater than (or equal to) indices'."); + CV_CheckEQ(inputs[1][i], inputs[2][i], "Scatter: each dim of indices should be equal to updates'."); + } + outputs.assign(1, inputs[0]); + return false; + } + + void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(name, "name", name.c_str()); + + std::vector inputs, outputs; + inputs_arr.getMatVector(inputs); + outputs_arr.getMatVector(outputs); + + const Mat& data = inputs[0]; + const Mat& indices = inputs[1]; + const Mat& updates = inputs[2]; + Mat& out = outputs[0]; + + typeDispatch(outputs[0].type(), data, indices, updates, out); + } + + template + void forward_impl(const Functor& rd, const Mat& data, const Mat& indices, const Mat& updates, Mat& out) + { + data.copyTo(out); + + const int ndims = data.dims; + const int* shape = data.size.p; + const size_t* step = data.step.p; + + const int* ind_shape = indices.size.p; + const size_t* ind_step = indices.step.p; + + size_t inp_offset = 0; + size_t ind_offset = 0; + const T* p_index = indices.ptr(); + const T* p_update = updates.ptr(); + T* p_out = out.ptr(); + + size_t total = indices.total(); + + int j, offset_at_idx, index; + size_t t, idx; + for (size_t i = 0; i < total; i++) + { + t = i; + inp_offset = 0; + ind_offset = 0; + int offset_at_axis = 0; + for (j = ndims - 1; j >= 0; j--) + { + idx = t / ind_shape[j]; + offset_at_idx = (int)(t - idx * ind_shape[j]); + ind_offset += offset_at_idx * ind_step[j]; + inp_offset += offset_at_idx * step[j]; + t = idx; + if (j == axis) + { + offset_at_axis = offset_at_idx * step[j]; + } + } + ind_offset /= sizeof(T); + + // get index and overwrite current indices + const T* tmp_p_index = p_index + ind_offset; + index = (int)(*tmp_p_index); + CV_Assert(index < shape[axis] && index > -shape[axis]); + + inp_offset = inp_offset - offset_at_axis + ((index + shape[axis]) % shape[axis]) * step[axis]; + inp_offset /= sizeof(T); + + const T* tmp_p_update = p_update + ind_offset; + T* tmp_p_out = p_out + inp_offset; + *tmp_p_out = rd(*tmp_p_out, *tmp_p_update); + } + } + + template + inline void typeDispatch(const int type, Args&&... args) + { + switch (type) + { + case CV_8U: + reductionDispatch(std::forward(args)...); + break; + case CV_32S: + reductionDispatch(std::forward(args)...); + break; + case CV_32F: + reductionDispatch(std::forward(args)...); + break; + default: + CV_Error(cv::Error::BadDepth, "Unsupported type."); + }; + } + + template + inline void reductionDispatch(Args&&... args) + { + switch (reduction) + { + case REDUCTION::NONE: + { + auto rd = [](const T& a, const T& b) { return b; }; // a from input data, b from updates + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::ADD: + { + auto rd = [](const T& a, const T& b) { return a + b; }; + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::MUL: + { + auto rd = [](const T& a, const T& b) { return a * b; }; + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::MAX: + { + auto rd = [](const T& a, const T& b) { return std::max(a, b); }; + forward_impl(rd, std::forward(args)...); + break; + } + case REDUCTION::MIN: + { + auto rd = [](const T& a, const T& b) { return std::min(a, b); }; + forward_impl(rd, std::forward(args)...); + break; + } + default: + CV_Error(Error::StsBadArg, "Unsupported reduction."); + }; + } + +private: + // Attributes + int axis; +}; + +Ptr ScatterLayer::create(const LayerParams& params) +{ + return makePtr(params); +} + +}} // namespace cv::dnn diff --git a/modules/dnn/src/layers/slice_layer.cpp b/modules/dnn/src/layers/slice_layer.cpp index aa44e4a5b9..bea497badd 100644 --- a/modules/dnn/src/layers/slice_layer.cpp +++ b/modules/dnn/src/layers/slice_layer.cpp @@ -44,6 +44,7 @@ #include "../op_cuda.hpp" #include "../op_inf_engine.hpp" #include "../ie_ngraph.hpp" +#include "../op_cann.hpp" #include "layers_common.hpp" #include @@ -83,6 +84,34 @@ Range normalizeRange(const Range& input_range, int n) return range; } +// TODO: support cv::Range with steps and negative steps to get rid of this transformation +void tranformForNegSteps(const MatShape& inpShape, std::vector >& sliceRanges, std::vector >& sliceSteps) +{ + // in case of negative steps, + // x of shape [5, 10], x[5:0:-1, 10:1:-3] <=> np.flip(x[1:5:1, 2:10:3], aixs=(0, 1)) + // new_end_i = start_i + 1 > dim_i ? dim_i : start_i + 1 + // new_start_i = end + 1 + // new_start_i = new_end_i - 1 - ((new_end_i - 1 - new_start_i) / abs(step_i)) * abs(step_i) + int start, end, new_start, new_end, step; + for (int i = 0; i < sliceSteps[0].size(); ++i) + { + step = sliceSteps[0][i]; + if (step > 0) + continue; + + step = -step; + start = sliceRanges[0][i].start; + end = sliceRanges[0][i].end; + new_end = start >= inpShape[i] ? inpShape[i] : start + 1; + new_start = end + 1; + new_start = new_end - 1 - ((new_end - 1 - new_start) / step) * step; + + sliceSteps[0][i] = step; + sliceRanges[0][i].start = new_start; + sliceRanges[0][i].end = new_end; + } +} + std::vector > finalizeSliceRange(const MatShape& inpShape, int& axis, const std::vector >& inputSliceRanges) { @@ -148,6 +177,24 @@ public: const DictValue &sizesOrEnds = params.has("size") ? params.get("size") : params.get("end"); CV_Assert(begins.size() == sizesOrEnds.size()); + if (params.has("steps")) + { + const DictValue &steps = params.get("steps"); + sliceSteps.resize(1); + sliceSteps[0].resize(steps.size()); + + for (int i = 0; i < steps.size(); ++i) + { + int step = steps.get(i); + CV_Assert(step != 0); + if (step < 0) + neg_step_dims.push_back(i); + if (std::abs(step) > 1) + hasSteps = true; + sliceSteps[0][i] = step; + } + } + sliceRanges.resize(1); sliceRanges[0].resize(begins.size(), Range::all()); for (int i = 0; i < begins.size(); ++i) @@ -165,26 +212,13 @@ public: else { int end = sizeOrEnd; - CV_Assert(end < 0 || end > start); // End index is excluded. + if (hasSteps && !neg_step_dims.empty() && sliceSteps[0][i] < 0) + CV_Assert(end < 0 || end != start); // if current step is negative, end < start is allowed. + else + CV_Assert(end < 0 || end > start); // End index is excluded. sliceRanges[0][i].end = end; // We'll finalize a negative value later. } } - - if (params.has("steps")) - { - const DictValue &steps = params.get("steps"); - sliceSteps.resize(1); - sliceSteps[0].resize(steps.size()); - - for (int i = 0; i < steps.size(); ++i) - { - int step = steps.get(i); - CV_Assert(step >= 1); - if (step > 1) - hasSteps = true; - sliceSteps[0][i] = step; - } - } } } @@ -192,13 +226,13 @@ public: { #ifdef HAVE_INF_ENGINE if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) - return sliceRanges.size() == 1 && !hasSteps; + return sliceRanges.size() == 1 && !hasSteps && neg_step_dims.empty(); #endif #ifdef HAVE_CUDA if (backendId == DNN_BACKEND_CUDA) - return !hasSteps; + return !hasSteps && neg_step_dims.empty(); #endif - return backendId == DNN_BACKEND_OPENCV; + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CANN; } bool getMemoryShapes(const std::vector &inputs, @@ -209,8 +243,13 @@ public: CV_Assert(inputs.size() == 1); MatShape inpShape = inputs[0]; + std::vector > sliceSteps_ = sliceSteps; + std::vector > sliceRanges_ = sliceRanges; + if (hasSteps && !neg_step_dims.empty()) + tranformForNegSteps(inpShape, sliceRanges_, sliceSteps_); + int axis_rw = axis; - std::vector > sliceRanges_rw = finalizeSliceRange(inpShape, axis_rw, sliceRanges); + std::vector > sliceRanges_rw = finalizeSliceRange(inpShape, axis_rw, sliceRanges_); if (!sliceRanges_rw.empty()) { @@ -223,8 +262,8 @@ public: if (shapesInitialized || inpShape[j] > 0) outputs[i][j] = normalizeRange(sliceRanges_rw[i][j], inpShape[j]).size(); - if (!sliceSteps.empty() && (i < sliceSteps.size()) && (j < sliceSteps[i].size()) && (sliceSteps[i][j] > 1)) - outputs[i][j] = (outputs[i][j] + sliceSteps[i][j] - 1) / sliceSteps[i][j]; + if (!sliceSteps_.empty() && (i < sliceSteps_.size()) && (j < sliceSteps_[i].size()) && (sliceSteps_[i][j] > 1)) + outputs[i][j] = (outputs[i][j] + sliceSteps_[i][j] - 1) / sliceSteps_[i][j]; } } } @@ -256,7 +295,10 @@ public: outputs_arr.getMatVector(outputs); CV_Assert(inputs.size() == 1); - const MatSize& inpShape = inputs[0].size; + MatShape inpShape = shape(inputs[0]); + + if (hasSteps && !neg_step_dims.empty()) + tranformForNegSteps(inpShape, sliceRanges, sliceSteps); finalSliceRanges = finalizeSliceRange(shape(inputs[0]), axis, sliceRanges); @@ -279,9 +321,9 @@ public: for (int i = 0; i < outputs.size(); ++i) { - CV_Assert(finalSliceRanges[i].size() <= inpShape.dims()); + CV_Assert(finalSliceRanges[i].size() <= inpShape.size()); // Fill the rest of ranges. - for (int j = finalSliceRanges[i].size(); j < inpShape.dims(); ++j) + for (int j = finalSliceRanges[i].size(); j < inpShape.size(); ++j) { finalSliceRanges[i].push_back(Range::all()); } @@ -585,10 +627,71 @@ public: getSliceRecursive(inpMat, inpIdx, finalSliceRanges[i], sliceSteps[i], 0, dimsNum, outputs[i], outIdx); else getSliceRecursive(inpMat, inpIdx, finalSliceRanges[i], sliceSteps[i], 0, dimsNum, outputs[i], outIdx); + // flip for negative steps + flip(outputs[i]); } } } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + CV_Assert(sliceRanges.size() == 1); + CV_Assert(sliceSteps.size() == 1); + CV_Assert(sliceRanges[0].size() == sliceSteps[0].size()); + + auto x = inputsWrapper[0].dynamicCast(); + const int dims = x->host->dims; + + // create operator + std::string op_name = cv::format("slice_%d", index); + auto op = std::make_shared(op_name); + + // retrieve begins, ends, axes and steps + std::vector begins, ends, axes, steps; + for (int i = 0; i < sliceRanges[0].size(); i++) + { + begins.push_back(sliceRanges[0][i].start); + ends.push_back(sliceRanges[0][i].end); + axes.push_back(i); + steps.push_back(sliceSteps[0][i]); + } + std::vector shape_{dims}; + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + // set inputs : begin + Mat begin_mat(shape_, CV_32S, &begins[0]); + auto op_const_begin = std::make_shared(begin_mat.data, begin_mat.type(), shape_, cv::format("%s_begin", op_name.c_str())); + op->set_input_begin(*(op_const_begin->getOp())); + op->update_input_desc_begin(*(op_const_begin->getTensorDesc())); + // set inputs : end + Mat end_mat(shape_, CV_32S, &ends[0]); + auto op_const_end = std::make_shared(end_mat.data, end_mat.type(), shape_, cv::format("%s_end", op_name.c_str())); + op->set_input_end(*(op_const_end->getOp())); + op->update_input_desc_end(*(op_const_end->getTensorDesc())); + // set inputs : axes + Mat axes_mat(shape_, CV_32S, &axes[0]); + auto op_const_axes = std::make_shared(axes_mat.data, axes_mat.type(), shape_, cv::format("%s_axes", op_name.c_str())); + op->set_input_axes(*(op_const_axes->getOp())); + op->update_input_desc_axes(*(op_const_axes->getTensorDesc())); + // set inputs : strides + Mat strides_mat(shape_, CV_32S, &steps[0]); + auto op_const_strides = std::make_shared(strides_mat.data, strides_mat.type(), shape_, cv::format("%s_strides", op_name.c_str())); + op->set_input_strides(*(op_const_strides->getOp())); + op->update_input_desc_strides(*(op_const_strides->getTensorDesc())); + + // set outputs + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, @@ -678,9 +781,15 @@ private: } } + void flip(Mat& output) // break if 1d tensor? + { + for (int i = 0; i < neg_step_dims.size(); ++i) + cv::flipND(output, output, neg_step_dims[i]); + } protected: // The actual non-negative values determined from @p sliceRanges depends on input size. std::vector > finalSliceRanges; + std::vector neg_step_dims; bool hasDynamicShapes; bool shapesInitialized; bool hasSteps; diff --git a/modules/dnn/src/layers/softmax_layer.cpp b/modules/dnn/src/layers/softmax_layer.cpp index b10aef3453..c1ea4d2297 100644 --- a/modules/dnn/src/layers/softmax_layer.cpp +++ b/modules/dnn/src/layers/softmax_layer.cpp @@ -48,6 +48,7 @@ #include "../ie_ngraph.hpp" #include "../op_vkcom.hpp" #include "../op_webnn.hpp" +#include "../op_cann.hpp" #include #include @@ -116,7 +117,8 @@ public: return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || (backendId == DNN_BACKEND_HALIDE && haveHalide() && axisRaw == 1) || - (backendId == DNN_BACKEND_VKCOM && haveVulkan()); + (backendId == DNN_BACKEND_VKCOM && haveVulkan()) || + backendId == DNN_BACKEND_CANN; } #ifdef HAVE_OPENCL @@ -362,6 +364,34 @@ public: return Ptr(); } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputsWrapper, const int index, const std::vector >& nodes) CV_OVERRIDE + { + auto x = inputsWrapper[0].dynamicCast(); + + // create operator + std::string op_name = cv::format("softmax_%d", index); + auto op = std::make_shared(op_name); + + // set attributes + op->set_attr_axes(ge::Operator::OpListInt( + {(int64_t)axisRaw} + )); + + // set inputs + // set inputs : x + auto op_x = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*op_x, "y"); + auto x_desc = x->getTensorDesc(); + op->update_input_desc_x(*x_desc); + + // set outputs + auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_y_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN #ifdef HAVE_DNN_NGRAPH virtual Ptr initNgraph(const std::vector >& inputs, diff --git a/modules/dnn/src/layers/tile_layer.cpp b/modules/dnn/src/layers/tile_layer.cpp new file mode 100644 index 0000000000..abaf96bd4a --- /dev/null +++ b/modules/dnn/src/layers/tile_layer.cpp @@ -0,0 +1,97 @@ +// 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 "layers_common.hpp" + +#include + +namespace cv { namespace dnn { + +class TileLayerImpl CV_FINAL : public TileLayer +{ +public: + TileLayerImpl(const LayerParams& params) + { + setParamsFrom(params); + if (params.has("repeats")) + { + DictValue param_repeats = params.get("repeats"); + int n_repeats = param_repeats.size(); + + CV_Assert(n_repeats > 0); + repeats.resize(n_repeats); + for (int i = 0; i < n_repeats; i++) + repeats[i] = param_repeats.get(i); + } + else + CV_Error(Error::StsNotImplemented, "Tile: repeats needs to be treated as parameter but it is missing."); + } + + virtual bool supportBackend(int backendId) CV_OVERRIDE + { + return backendId == DNN_BACKEND_OPENCV; + } + + virtual bool getMemoryShapes(const std::vector &inputs, + const int requiredOutputs, + std::vector &outputs, + std::vector &internals) const CV_OVERRIDE + { + CV_CheckEQ(inputs.size(), 1ull, "Tile: one input is expected"); + + // repeats must have the same length as input's dimension number + // FIXIT: it breaks when the input is 1d tensor (represented as 2d mat with size=2 in opencv dnn) + CV_CheckEQ(inputs[0].size(), repeats.size(), "Tile: repeats must be a 1D tensor of the same length as input's dimension number"); + + outputs.assign(1, inputs[0]); + for (int i = 0; i < repeats.size(); i++) + { + outputs[0][i] *= repeats[i]; + } + return false; + } + + void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr, OutputArrayOfArrays internals_arr) CV_OVERRIDE + { + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(name, "name", name.c_str()); + + std::vector inputs, outputs; + inputs_arr.getMatVector(inputs); + outputs_arr.getMatVector(outputs); + + const Mat& data = inputs[0]; + Mat& out = outputs[0]; + + Mat tmp = data.clone(); + MatShape tmp_shape = shape(tmp); + MatShape out_shape = shape(out); + int rep_i, ndims = data.dims; + int dims = 1; + for (int i = 0; i < ndims; i++) + { + rep_i = repeats[i]; + if (rep_i != 1) + { + tmp = tmp.reshape(0, dims); + tmp = cv::repeat(tmp, 1, rep_i); + dims *= out_shape[i]; + } + } + tmp = tmp.reshape(0, out_shape); + + tmp.copyTo(out); + } + +private: + std::vector repeats; +}; + +Ptr TileLayer::create(const LayerParams& params) +{ + return makePtr(params); +} + +}} // namespace cv::dnn diff --git a/modules/dnn/src/legacy_backend.cpp b/modules/dnn/src/legacy_backend.cpp index fa9407aacd..b092fb057c 100644 --- a/modules/dnn/src/legacy_backend.cpp +++ b/modules/dnn/src/legacy_backend.cpp @@ -13,6 +13,7 @@ #include "op_cuda.hpp" #include "op_webnn.hpp" #include "op_timvx.hpp" +#include "op_cann.hpp" namespace cv { namespace dnn { @@ -75,11 +76,7 @@ Ptr wrapMat(int backendId, int targetId, cv::Mat& m) } else if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { -#ifdef HAVE_DNN_NGRAPH - return Ptr(new NgraphBackendWrapper(targetId, m)); -#else - CV_Error(Error::StsNotImplemented, "This OpenCV version is built without support of OpenVINO / Inference Engine + nGraph"); -#endif + CV_Assert(0 && "Internal error: DNN_BACKEND_INFERENCE_ENGINE_NGRAPH must be implemented through inheritance"); } else if (backendId == DNN_BACKEND_WEBNN) { @@ -119,6 +116,10 @@ Ptr wrapMat(int backendId, int targetId, cv::Mat& m) return Ptr(new TimVXBackendWrapper(m)); #endif // HAVE_TIMVX } + else if (backendId == DNN_BACKEND_CANN) + { + CV_Assert(0 && "Internal error: DNN_BACKEND_CANN must be implemented through inheritance"); + } else CV_Error(Error::StsNotImplemented, "Unknown backend identifier"); return Ptr(); // TODO Error? diff --git a/modules/dnn/src/net.cpp b/modules/dnn/src/net.cpp index 33f22744b8..3b200a108e 100644 --- a/modules/dnn/src/net.cpp +++ b/modules/dnn/src/net.cpp @@ -120,7 +120,7 @@ Net Net::quantize(InputArrayOfArrays calibData, int inputsDtype, int outputsDtyp CV_TRACE_FUNCTION(); CV_Assert(impl); CV_Assert(!empty()); - return impl->quantize(calibData, inputsDtype, outputsDtype, perChannel); + return impl->quantize(*this, calibData, inputsDtype, outputsDtype, perChannel); } // FIXIT drop from inference API @@ -146,7 +146,7 @@ void Net::setPreferableBackend(int backendId) CV_TRACE_FUNCTION(); CV_TRACE_ARG(backendId); CV_Assert(impl); - return impl->setPreferableBackend(backendId); + return impl->setPreferableBackend(*this, backendId); } void Net::setPreferableTarget(int targetId) @@ -395,6 +395,13 @@ void Net::enableFusion(bool fusion) return impl->enableFusion(fusion); } +void Net::enableWinograd(bool useWinograd) +{ + CV_TRACE_FUNCTION(); + CV_Assert(impl); + return impl->enableWinograd(useWinograd); +} + void Net::setHalideScheduler(const String& scheduler) { CV_TRACE_FUNCTION(); diff --git a/modules/dnn/src/net_cann.cpp b/modules/dnn/src/net_cann.cpp new file mode 100644 index 0000000000..62d45d85c5 --- /dev/null +++ b/modules/dnn/src/net_cann.cpp @@ -0,0 +1,348 @@ +// 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 + +#include "net_impl.hpp" + +namespace cv { namespace dnn { +CV__DNN_INLINE_NS_BEGIN + +#ifdef HAVE_CANN + +static std::shared_ptr compileCannGraph(std::shared_ptr graph); + +class NetImplCann CV_FINAL : public Net::Impl +{ +public: + typedef Net::Impl Base; + + bool newWasSupported, netWasConverted; + + explicit NetImplCann(const Ptr& basePtr) + : Net::Impl() + { + CV_LOG_INFO(NULL, "Initializing NetImplCann"); + basePtr_ = basePtr; + newWasSupported = true; + netWasConverted = false; + + init(); + + CV_LOG_INFO(NULL, "Finished initializing NetImplCann"); + } + + void init() + { + CV_TRACE_FUNCTION(); + CV_Assert(basePtr_); + Net::Impl& base = *basePtr_; + CV_Assert(!base.netWasAllocated); + CV_Assert(!base.netWasQuantized); // does not support quantized net for now + netInputLayer = base.netInputLayer; + blobsToKeep = base.blobsToKeep; + layers = base.layers; + for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++) + { + LayerData& ld = it->second; + ld.resetAllocation(); + } + layerNameToId = base.layerNameToId; + outputNameToId = base.outputNameToId; + preferableBackend = DNN_BACKEND_CANN; + preferableTarget = DNN_TARGET_NPU; // force using NPU + hasDynamicShapes = base.hasDynamicShapes; + CV_Assert(base.backendWrappers.empty()); //backendWrappers = base.backendWrappers; + lastLayerId = base.lastLayerId; + netWasAllocated = base.netWasAllocated; + netWasQuantized = base.netWasQuantized; + fusion = base.fusion; + } + + bool empty() const override + { + return Base::empty(); + } + + void setPreferableBackend(Net& net, int backendId) override + { + if (backendId == preferableBackend) + return; // no-op + else + CV_Error(Error::StsError, "DNN: Can't switch backend from CANN to other"); + Ptr& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net); + impl_ptr_ref = basePtr_; + basePtr_->setPreferableBackend(net, backendId); + } + + void setPreferableTarget(int targetId) override + { + if (targetId != preferableTarget) + { + CV_Error(Error::StsError, "DNN: Can't switch target from NPU to other"); + } + } + + Ptr wrap(Mat& host) override + { + return Ptr(new CannBackendWrapper(host)); + } + + // void fuseLayers(const std::vector& blobsToKeep_); // fusion is done in the CANN graph engine + + void initBackend(const std::vector& blobsToKeep_) override; + + void forwardLayer(LayerData& ld) override; +}; + +void NetImplCann::initBackend(const std::vector& blobsToKeep_) +{ + CV_TRACE_FUNCTION(); + CV_CheckEQ(preferableBackend, DNN_BACKEND_CANN, ""); + + // netWasAllocated turns to false if requested output is changed or input shape changes + if (netWasConverted && netWasAllocated) + return; + + if (!netWasConverted) + { + newWasSupported = true; + for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it) + { + auto& ld = it->second; + auto layer = ld.layerInstance; + if (ld.id != 0 && !layer->supportBackend(preferableBackend)) + { + newWasSupported = false; + CV_LOG_INFO(NULL, "DNN/CANN: layer (name=" << ld.name << ", type=" << ld.type << ") is not supported by CANN backend. Going back to CPU backend"); + } + } + } + if (!newWasSupported) + return ; + + // convert layers to CANN operators, + // collect graph input and output operators, + // collect and input and output wrappers + int firstOutputLayerId = -1; + std::vector > netInputNodes; + std::vector graphInputOps, graphOutputOps; + std::vector> graphInputWrappers, graphOutputWrappers; + CV_LOG_INFO(NULL, "DNN/CANN: converting layers to CANN operators"); + for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); ++it) + { + LayerData& ld = it->second; + auto layer = ld.layerInstance; + + if (ld.id == 0) + { + for (int i = 0; i < ld.outputBlobsWrappers.size(); i++) + { + std::string inputName = netInputLayer->outNames.empty() ? cv::format("%s_%d", ld.name.c_str(), i) : netInputLayer->outNames[i]; + auto inputOp = std::make_shared(inputName); + + // retrieve tensor description + auto wrapper = ld.outputBlobsWrappers[i]; + graphInputWrappers.push_back(wrapper); + auto cannWrapper = wrapper.dynamicCast(); + CV_Assert(!cannWrapper.empty()); + + inputOp->update_input_desc_x(*(cannWrapper->desc_)); + inputOp->update_output_desc_y(*(cannWrapper->desc_)); + + graphInputOps.push_back(*inputOp); + netInputNodes.push_back(Ptr(new CannBackendNode(inputOp))); + } + } + else + { + ld.skip = true; // skip all cann operators + + std::vector > layerInputNodes; + for (int i = 0; i < ld.inputBlobsId.size(); i++) + { + int layerInputLid = ld.inputBlobsId[i].lid; + int layerInputOid = ld.inputBlobsId[i].oid; + if (layerInputLid == 0) + { + layerInputNodes.push_back(netInputNodes[layerInputOid]); + } + else // here we do not consider an op with multiple outputs + { + layerInputNodes.push_back(layers[layerInputLid].backendNodes[preferableBackend]); + } + } + + CV_LOG_INFO(NULL, "DNN/CANN: converting layer " << ld.name << "@" << ld.type << "@" << ld.id << " to CANN operator"); + auto backendNode = layer->initCann(ld.inputBlobsWrappers, ld.id, layerInputNodes); + + // collect outputs + bool isOutputNode = ld.consumers.size() == 0 ? true : false; + if (isOutputNode) + { + if (firstOutputLayerId < 0) + firstOutputLayerId = ld.id; + auto cannNode = backendNode.dynamicCast(); + graphOutputOps.push_back(*(cannNode->getOp())); + // assume cann graph outputs and dnn net outputs have the same order + for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) + { + graphOutputWrappers.push_back(ld.outputBlobsWrappers[i]); + } + } + + ld.backendNodes[preferableBackend] = backendNode; + } + } + CV_LOG_INFO(NULL, "DNN/CANN: done converting layers to CANN operators"); + + // build graph from collected graph inputs and outputs + CV_LOG_INFO(NULL, "DNN/CANN: building ge::Graph"); + std::string graphName = cv::format("graph_%d", 0); + std::shared_ptr graph = std::make_shared(graphName.c_str()); + (void)graph->SetInputs(graphInputOps); + (void)graph->SetOutputs(graphOutputOps); + CV_LOG_INFO(NULL, "DNN/CANN: done building ge::Graph"); + + // convert ge::Graph to OM buffer + CV_LOG_INFO(NULL, "DNN/CANN: converting ge::Graph to OM buffer"); + std::shared_ptr modelBuffer = compileCannGraph(graph); + CV_LOG_INFO(NULL, "DNN/CANN: OM buffer size = " << modelBuffer->length); + CV_LOG_INFO(NULL, "DNN/CANN: done building ge::Graph to OM buffer"); + + // keep net in the first output node and mark the node runnable + auto& ld = layers[firstOutputLayerId]; + auto cannNode = ld.backendNodes[preferableBackend].dynamicCast(); + std::shared_ptr net = std::shared_ptr(new CannNet()); + net->loadModelBuffer(modelBuffer); + net->bindInputWrappers(graphInputWrappers); + net->bindOutputWrappers(graphOutputWrappers); + cannNode->net = net; + ld.skip = false; + + netWasConverted = true; +} + +void NetImplCann::forwardLayer(LayerData& ld) +{ + CV_TRACE_FUNCTION(); + + auto layer = ld.layerInstance; + + if (!ld.skip) + { + auto it = ld.backendNodes.find(preferableBackend); + if (ld.id == 0 || it == ld.backendNodes.end()) // input layer + { + return Base::forwardLayer(ld); + } + + CV_Assert(it != ld.backendNodes.end()); + const Ptr& node = it->second; + CV_Assert(!node.empty()); + auto cannNode = node.dynamicCast(); + CV_Assert(!cannNode.empty()); + CV_Assert(cannNode->net); + + TickMeter tm; + tm.start(); + + cannNode->net->forward(); + + tm.stop(); + int64_t t = tm.getTimeTicks(); + layersTimings[ld.id] = (t > 0) ? t : 1; + } + else + { + layersTimings[ld.id] = 0; + } + + ld.flag = 1; +} + +std::shared_ptr compileCannGraph(std::shared_ptr graph) +{ + const size_t hdrsize = 32; + std::shared_ptr out_buffer = std::make_shared(); + size_t buf_size = (1 << 27), model_size; // default buf_size 128 MB + for (int iter = 0; iter < 2; ++iter) + { + size_t* shared_buf = (size_t*)mmap(NULL, buf_size + hdrsize, PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_ANONYMOUS, -1, 0); + uint8_t* model_data = (uint8_t*)(shared_buf + 1); + pid_t child; + int childstate = 0; + bool ok; + if ((child=fork()) == 0) + { + // initialize engine + std::map options = { + {ge::AscendString(ge::ir_option::SOC_VERSION), ge::AscendString("Ascend310")}, + }; + ACL_CHECK_GRAPH_RET(ge::aclgrphBuildInitialize(options)); + + // build + std::shared_ptr om_model = std::make_shared(); + std::map build_options; + ACL_CHECK_GRAPH_RET(aclgrphBuildModel(*graph, build_options, *om_model)); + +#if 0 + // (optional). Dump model + AscendString graph_name; + graph.GetName(graph_name); + aclgrphDumpGraph(graph, graph_name.GetString(), 7); + // (optional). Save model + aclgrphSaveModel(graph_name.GetString(), *om_model); +#endif + + // finalize engine + ge::aclgrphBuildFinalize(); + + // send model from child to parent + size_t model_size = om_model->length; + *shared_buf = model_size; + if (model_size > buf_size) + { + exit(1); + } + else + { + memcpy(model_data, om_model->data.get(), model_size); + exit(0); + } + } + waitpid (child, &childstate, 0); + model_size = *shared_buf; + ok = WIFEXITED(childstate) && WEXITSTATUS(childstate) == 0; + if (ok) + { + CV_LOG_INFO(NULL, "Compile success, model size = " << model_size); + out_buffer->data = std::shared_ptr(new uint8_t[model_size]); + memcpy(out_buffer->data.get(), model_data, model_size); + out_buffer->length = model_size; + } + munmap(shared_buf, buf_size + hdrsize); + if (ok) break; + buf_size = model_size; + } + return out_buffer; +} + +void switchToCannBackend(Net& net) +{ + CV_TRACE_FUNCTION(); + Ptr& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net); + CV_Assert(impl_ptr_ref); + CV_LOG_INFO(NULL, "DNN: switching to CANN backend... (networkID=" << impl_ptr_ref->networkId << ")"); + Ptr impl_ptr_cann = makePtr(impl_ptr_ref); + impl_ptr_ref = impl_ptr_cann; +} + +#endif // HAVE_CANN + +CV__DNN_INLINE_NS_END +}} // namespace cv::dnn diff --git a/modules/dnn/src/net_impl.cpp b/modules/dnn/src/net_impl.cpp index 24fb31f03e..dc0c53191f 100644 --- a/modules/dnn/src/net_impl.cpp +++ b/modules/dnn/src/net_impl.cpp @@ -30,6 +30,12 @@ std::string detail::NetImplBase::getDumpFileNameBase() const } +Net::Impl::~Impl() +{ + // nothing +} + + Net::Impl::Impl() { // allocate fake net input layer @@ -46,10 +52,10 @@ Net::Impl::Impl() netWasQuantized = false; fusion = true; isAsync = false; - preferableBackend = DNN_BACKEND_DEFAULT; + preferableBackend = (Backend)getParam_DNN_BACKEND_DEFAULT(); preferableTarget = DNN_TARGET_CPU; - skipInfEngineInit = false; hasDynamicShapes = false; + useWinograd = true; } @@ -86,22 +92,10 @@ void Net::Impl::clear() } -void Net::Impl::setUpNet(const std::vector& blobsToKeep_) +void Net::Impl::validateBackendAndTarget() { CV_TRACE_FUNCTION(); - if (dumpLevel && networkDumpCounter == 0) - { - dumpNetworkToFile(); - } - - if (preferableBackend == DNN_BACKEND_DEFAULT) - preferableBackend = (Backend)getParam_DNN_BACKEND_DEFAULT(); -#ifdef HAVE_INF_ENGINE - if (preferableBackend == DNN_BACKEND_INFERENCE_ENGINE) - preferableBackend = DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; // = getInferenceEngineBackendTypeParam(); -#endif - CV_Assert(preferableBackend != DNN_BACKEND_OPENCV || preferableTarget == DNN_TARGET_CPU || preferableTarget == DNN_TARGET_OPENCL || @@ -109,19 +103,6 @@ void Net::Impl::setUpNet(const std::vector& blobsToKeep_) CV_Assert(preferableBackend != DNN_BACKEND_HALIDE || preferableTarget == DNN_TARGET_CPU || preferableTarget == DNN_TARGET_OPENCL); -#ifdef HAVE_INF_ENGINE - if (preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) - { - CV_Assert( - (preferableTarget == DNN_TARGET_CPU && (!isArmComputePlugin() || preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)) || - preferableTarget == DNN_TARGET_OPENCL || - preferableTarget == DNN_TARGET_OPENCL_FP16 || - preferableTarget == DNN_TARGET_MYRIAD || - preferableTarget == DNN_TARGET_HDDL || - preferableTarget == DNN_TARGET_FPGA - ); - } -#endif #ifdef HAVE_WEBNN if (preferableBackend == DNN_BACKEND_WEBNN) { @@ -136,6 +117,20 @@ void Net::Impl::setUpNet(const std::vector& blobsToKeep_) CV_Assert(preferableBackend != DNN_BACKEND_TIMVX || preferableTarget == DNN_TARGET_NPU); + CV_Assert(preferableBackend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && "Inheritance internal error"); +} + +void Net::Impl::setUpNet(const std::vector& blobsToKeep_) +{ + CV_TRACE_FUNCTION(); + + if (dumpLevel && networkDumpCounter == 0) + { + dumpNetworkToFile(); + } + + validateBackendAndTarget(); + if (!netWasAllocated || this->blobsToKeep != blobsToKeep_) { if (preferableBackend == DNN_BACKEND_OPENCV && IS_DNN_OPENCL_TARGET(preferableTarget)) @@ -228,14 +223,14 @@ void Net::Impl::setUpNet(const std::vector& blobsToKeep_) Ptr Net::Impl::getLayer(int layerId) const { LayerData& ld = getLayerData(layerId); - return ld.getLayerInstance(); + return getLayerInstance(ld); } Ptr Net::Impl::getLayer(const LayerId& layerId) const { LayerData& ld = getLayerData(layerId); - return ld.getLayerInstance(); + return getLayerInstance(ld); } @@ -327,7 +322,7 @@ int Net::Impl::resolvePinOutputName(LayerData& ld, const String& outName) const { if (outName.empty()) return 0; - return ld.getLayerInstance()->outputNameToIndex(outName); + return getLayerInstance(ld)->outputNameToIndex(outName); } @@ -523,12 +518,12 @@ void Net::Impl::allocateLayer(int lid, const LayersShapesMap& layersShapes) for (int i = 0; i < ld.outputBlobs.size(); ++i) ld.outputBlobsWrappers[i] = wrap(ld.outputBlobs[i]); - /* CUDA backend has its own system for internal blobs; we don't need these */ - ld.internalBlobsWrappers.resize((preferableBackend == DNN_BACKEND_CUDA || preferableBackend == DNN_BACKEND_TIMVX) ? 0 : ld.internals.size()); + /* CUDA & CANN backend has its own system for internal blobs; we don't need these */ + ld.internalBlobsWrappers.resize((preferableBackend == DNN_BACKEND_CUDA || preferableBackend == DNN_BACKEND_TIMVX || preferableBackend == DNN_BACKEND_CANN) ? 0 : ld.internals.size()); for (int i = 0; i < ld.internalBlobsWrappers.size(); ++i) ld.internalBlobsWrappers[i] = wrap(ld.internals[i]); - Ptr layerPtr = ld.getLayerInstance(); + Ptr layerPtr = getLayerInstance(ld); { std::vector inps(ld.inputBlobs.size()); for (int i = 0; i < ld.inputBlobs.size(); ++i) @@ -813,12 +808,10 @@ void Net::Impl::forwardLayer(LayerData& ld) { forwardHalide(ld.outputBlobsWrappers, node); } -#ifdef HAVE_INF_ENGINE else if (preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { - forwardNgraph(ld.outputBlobsWrappers, node, isAsync); + CV_Assert(preferableBackend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && "Inheritance internal error"); } -#endif else if (preferableBackend == DNN_BACKEND_WEBNN) { forwardWebnn(ld.outputBlobsWrappers, node, isAsync); @@ -844,7 +837,7 @@ void Net::Impl::forwardLayer(LayerData& ld) #endif else { - CV_Error(Error::StsNotImplemented, "Unknown backend identifier"); + CV_Error(Error::StsNotImplemented, cv::format("Unknown backend identifier: %d", preferableBackend)); } } @@ -1156,7 +1149,7 @@ void Net::Impl::getLayerShapesRecursively(int id, LayersShapesMap& inOutShapes) ShapesVec& os = layerShapes.out; ShapesVec& ints = layerShapes.internal; int requiredOutputs = layerData.requiredOutputs.size(); - Ptr l = layerData.getLayerInstance(); + const Ptr& l = getLayerInstance(layerData); CV_Assert(l); bool layerSupportInPlace = false; try @@ -1310,7 +1303,7 @@ void Net::Impl::updateLayersShapes() const MatShape& shape = layersShapes[inputLayerId].out[inputPin.oid]; layerShapes.in.push_back(shape); } - layerData.getLayerInstance()->updateMemoryShapes(layerShapes.in); + getLayerInstance(layerData)->updateMemoryShapes(layerShapes.in); } CV_LOG_DEBUG(NULL, "Layer " << layerId << ": " << toString(layerShapes.in, "input shapes")); CV_LOG_IF_DEBUG(NULL, !layerShapes.out.empty(), "Layer " << layerId << ": " << toString(layerShapes.out, "output shapes")); @@ -1369,30 +1362,7 @@ Mat Net::Impl::getBlob(String outputName) const AsyncArray Net::Impl::getBlobAsync(const LayerPin& pin) { CV_TRACE_FUNCTION(); -#ifdef HAVE_INF_ENGINE - if (!pin.valid()) - CV_Error(Error::StsObjectNotFound, "Requested blob not found"); - - LayerData& ld = layers[pin.lid]; - if ((size_t)pin.oid >= ld.outputBlobs.size()) - { - CV_Error(Error::StsOutOfRange, format("Layer \"%s\" produce only %d outputs, " - "the #%d was requested", - ld.name.c_str(), (int)ld.outputBlobs.size(), (int)pin.oid)); - } - if (preferableTarget != DNN_TARGET_CPU) - { - CV_Assert(!ld.outputBlobsWrappers.empty() && !ld.outputBlobsWrappers[pin.oid].empty()); - // Transfer data to CPU if it's require. - ld.outputBlobsWrappers[pin.oid]->copyToHost(); - } - CV_Assert(preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH); - - Ptr wrapper = ld.outputBlobsWrappers[pin.oid].dynamicCast(); - return std::move(wrapper->futureMat); -#else CV_Error(Error::StsNotImplemented, "DNN: OpenVINO/nGraph backend is required"); -#endif // HAVE_INF_ENGINE } @@ -1482,7 +1452,7 @@ void Net::Impl::setInput(InputArray blob, const String& name, double scalefactor Mat Net::Impl::getParam(int layer, int numParam) const { LayerData& ld = getLayerData(layer); - std::vector& layerBlobs = ld.getLayerInstance()->blobs; + std::vector& layerBlobs = getLayerInstance(ld)->blobs; CV_Assert(numParam < (int)layerBlobs.size()); return layerBlobs[numParam]; } @@ -1491,7 +1461,8 @@ void Net::Impl::setParam(int layer, int numParam, const Mat& blob) { LayerData& ld = getLayerData(layer); - std::vector& layerBlobs = ld.getLayerInstance()->blobs; + // FIXIT we should not modify "execution" instance + std::vector& layerBlobs = getLayerInstance(ld)->blobs; CV_Assert(numParam < (int)layerBlobs.size()); // we don't make strong checks, use this function carefully layerBlobs[numParam] = blob; @@ -1595,6 +1566,7 @@ string Net::Impl::dump(bool forceAllocation) const case DNN_BACKEND_CUDA: backend = "CUDA/"; break; case DNN_BACKEND_WEBNN: backend = "WEBNN/"; break; case DNN_BACKEND_TIMVX: backend = "TIMVX/"; break; + case DNN_BACKEND_CANN: backend = "CANN/"; break; // don't use default: } out << "digraph G {\n"; @@ -1958,7 +1930,7 @@ int64 Net::Impl::getFLOPS(const std::vector& netInputShapes) /*const*/ for (int i = 0; i < ids.size(); i++) { - flops += layers[ids[i]].getLayerInstance()->getFLOPS(inShapes[i], outShapes[i]); + flops += getLayerInstance(layers[ids[i]])->getFLOPS(inShapes[i], outShapes[i]); } return flops; @@ -1975,7 +1947,7 @@ int64 Net::Impl::getFLOPS( LayerShapes shapes; getLayerShapes(netInputShapes, layerId, shapes); - return const_cast(layer->second).getLayerInstance()->getFLOPS(shapes.in, shapes.out); + return getLayerInstance(const_cast(layer->second))->getFLOPS(shapes.in, shapes.out); } @@ -2068,6 +2040,37 @@ void Net::Impl::getMemoryConsumption( } } +void Net::Impl::enableWinograd(bool useWinograd_) +{ + if (useWinograd != useWinograd_) + { + useWinograd = useWinograd_; + + for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); it++) + { + int lid = it->first; + LayerData &ld = layers[lid]; + Ptr& currLayer = ld.layerInstance; + + if (ld.type == "Convolution") + { + ld.params.set("use_winograd", useWinograd_); + Ptr convLayer = ld.layerInstance.dynamicCast(); + if (!convLayer.empty()) + convLayer->useWinograd = useWinograd_; + } + + if (ld.type == "ConvolutionInt8") + { + Ptr convLayer = currLayer.dynamicCast(); + ld.params.set("use_winograd", useWinograd_); + if (!convLayer.empty()) + convLayer->useWinograd = useWinograd_; + } + } + } +} + // TODO drop? void Net::Impl::getLayerTypes(std::vector& layersTypes) const diff --git a/modules/dnn/src/net_impl.hpp b/modules/dnn/src/net_impl.hpp index 5f0563d3c3..6eb06aa5b7 100644 --- a/modules/dnn/src/net_impl.hpp +++ b/modules/dnn/src/net_impl.hpp @@ -12,6 +12,7 @@ #include "op_cuda.hpp" #include "op_webnn.hpp" #include "op_timvx.hpp" +#include "op_cann.hpp" #include #include @@ -38,7 +39,12 @@ struct Net::Impl : public detail::NetImplBase typedef std::map LayersShapesMap; typedef std::map MapIdToLayerData; + virtual ~Impl(); Impl(); + Impl(const Impl&) = delete; + + // Inheritance support + Ptr basePtr_; Ptr netInputLayer; std::vector blobsToKeep; @@ -49,7 +55,6 @@ struct Net::Impl : public detail::NetImplBase int preferableBackend; int preferableTarget; String halideConfigFile; - bool skipInfEngineInit; bool hasDynamicShapes; // Map host data to backend specific wrapper. std::map> backendWrappers; @@ -59,23 +64,53 @@ struct Net::Impl : public detail::NetImplBase bool netWasAllocated; bool netWasQuantized; bool fusion; - bool isAsync; + bool isAsync; // FIXIT: drop + bool useWinograd; std::vector layersTimings; - bool empty() const; - void setPreferableBackend(int backendId); - void setPreferableTarget(int targetId); + virtual bool empty() const; + virtual void setPreferableBackend(Net& net, int backendId); + virtual void setPreferableTarget(int targetId); // FIXIT use inheritance - Ptr wrap(Mat& host); + virtual Ptr wrap(Mat& host); - void clear(); + virtual void clear(); + + + virtual void validateBackendAndTarget(); void setUpNet(const std::vector& blobsToKeep_ = std::vector()); + virtual Ptr createLayerInstance(const LayerData& ld) const + { + return LayerFactory::createLayerInstance(ld.type, const_cast(ld.params)); + } + Ptr getLayerInstance(LayerData& ld) const + { + CV_TRACE_FUNCTION(); + CV_TRACE_ARG_VALUE(type, "type", ld.type.c_str()); + + if (ld.layerInstance) + return ld.layerInstance; + + ld.layerInstance = createLayerInstance(ld); + if (!ld.layerInstance && basePtr_) + { + ld.layerInstance = basePtr_->createLayerInstance(ld); + CV_LOG_IF_DEBUG(NULL, ld.layerInstance, "Created layer \"" + ld.name + "\" of type \"" + ld.type + "\" from upstream layers registry"); + } + if (!ld.layerInstance) + { + CV_Error(Error::StsError, "Can't create layer \"" + ld.name + "\" of type \"" + ld.type + "\""); + } + + return ld.layerInstance; + } + Ptr getLayer(int layerId) const; Ptr getLayer(const LayerId& layerId) const; @@ -118,7 +153,7 @@ struct Net::Impl : public detail::NetImplBase void setInputsNames(const std::vector& inputBlobNames); void setInputShape(const String& inputName, const MatShape& shape); - void setInput(InputArray blob, const String& name, double scalefactor, const Scalar& mean); + virtual void setInput(InputArray blob, const String& name, double scalefactor, const Scalar& mean); Mat getParam(int layer, int numParam) const; void setParam(int layer, int numParam, const Mat& blob); std::vector> getLayerInputs(int layerId) const; @@ -130,8 +165,7 @@ struct Net::Impl : public detail::NetImplBase int getLayersCount(const String& layerType) const; - // FIXIT use inheritance - void initBackend(const std::vector& blobsToKeep_); + virtual void initBackend(const std::vector& blobsToKeep_); void setHalideScheduler(const String& scheduler); #ifdef HAVE_HALIDE @@ -139,11 +173,6 @@ struct Net::Impl : public detail::NetImplBase void initHalideBackend(); #endif -#ifdef HAVE_DNN_NGRAPH - void addNgraphOutputs(LayerData& ld); - void initNgraphBackend(const std::vector& blobsToKeep_); -#endif - #ifdef HAVE_WEBNN void addWebnnOutputs(LayerData& ld); void initWebnnBackend(const std::vector& blobsToKeep_); @@ -183,11 +212,12 @@ struct Net::Impl : public detail::NetImplBase // TODO add getter void enableFusion(bool fusion_); - void fuseLayers(const std::vector& blobsToKeep_); + virtual void fuseLayers(const std::vector& blobsToKeep_); + void enableWinograd(bool useWinograd_); void allocateLayers(const std::vector& blobsToKeep_); - void forwardLayer(LayerData& ld); + virtual void forwardLayer(LayerData& ld); void forwardToLayer(LayerData& ld, bool clearFlags = true); @@ -243,22 +273,17 @@ struct Net::Impl : public detail::NetImplBase Mat getBlob(String outputName) const; #ifdef CV_CXX11 - AsyncArray getBlobAsync(const LayerPin& pin); + virtual AsyncArray getBlobAsync(const LayerPin& pin); AsyncArray getBlobAsync(String outputName); #endif // CV_CXX11 -#ifdef HAVE_INF_ENGINE - static - Net createNetworkFromModelOptimizer(InferenceEngine::CNNNetwork& ieNet); -#endif - string dump(bool forceAllocation = false) const; void dumpNetworkToFile() const; // FIXIT drop from inference API - Net quantize(InputArrayOfArrays calibData, int inputsDtype, int outputsDtype, bool perChannel) /*const*/; + Net quantize(Net& net, InputArrayOfArrays calibData, int inputsDtype, int outputsDtype, bool perChannel) /*const*/; void getInputDetails(std::vector& scales, std::vector& zeropoints) /*const*/; void getOutputDetails(std::vector& scales, std::vector& zeropoints) /*const*/; diff --git a/modules/dnn/src/net_impl_backend.cpp b/modules/dnn/src/net_impl_backend.cpp index e26126d86c..ef816be66d 100644 --- a/modules/dnn/src/net_impl_backend.cpp +++ b/modules/dnn/src/net_impl_backend.cpp @@ -7,6 +7,9 @@ #include "net_impl.hpp" #include "legacy_backend.hpp" +#include "backend.hpp" +#include "factory.hpp" + namespace cv { namespace dnn { CV__DNN_INLINE_NS_BEGIN @@ -82,6 +85,10 @@ Ptr Net::Impl::wrap(Mat& host) return Ptr(new TimVXBackendWrapper(baseBuffer, host)); #endif } + else if (preferableBackend == DNN_BACKEND_CANN) + { + CV_Assert(0 && "Internal error: DNN_BACKEND_CANN must be implemented through inheritance"); + } else CV_Error(Error::StsNotImplemented, "Unknown backend identifier"); } @@ -109,11 +116,7 @@ void Net::Impl::initBackend(const std::vector& blobsToKeep_) } else if (preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { -#ifdef HAVE_DNN_NGRAPH - initNgraphBackend(blobsToKeep_); -#else - CV_Error(Error::StsNotImplemented, "This OpenCV version is built without support of OpenVINO"); -#endif + CV_Assert(0 && "Inheritance must be used with OpenVINO backend"); } else if (preferableBackend == DNN_BACKEND_WEBNN) { @@ -147,6 +150,10 @@ void Net::Impl::initBackend(const std::vector& blobsToKeep_) CV_Error(Error::StsNotImplemented, "This OpenCV version is built without support of TimVX"); #endif } + else if (preferableBackend == DNN_BACKEND_CANN) + { + CV_Assert(0 && "Internal error: DNN_BACKEND_CANN must be implemented through inheritance"); + } else { CV_Error(Error::StsNotImplemented, cv::format("Unknown backend identifier: %d", preferableBackend)); @@ -154,26 +161,46 @@ void Net::Impl::initBackend(const std::vector& blobsToKeep_) } -void Net::Impl::setPreferableBackend(int backendId) +void Net::Impl::setPreferableBackend(Net& net, int backendId) { if (backendId == DNN_BACKEND_DEFAULT) backendId = (Backend)getParam_DNN_BACKEND_DEFAULT(); + if (backendId == DNN_BACKEND_INFERENCE_ENGINE) + backendId = DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; // = getInferenceEngineBackendTypeParam(); + if (netWasQuantized && backendId != DNN_BACKEND_OPENCV && backendId != DNN_BACKEND_TIMVX) { CV_LOG_WARNING(NULL, "DNN: Only default and TIMVX backends support quantized networks"); backendId = DNN_BACKEND_OPENCV; } -#ifdef HAVE_INF_ENGINE - if (backendId == DNN_BACKEND_INFERENCE_ENGINE) - backendId = DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; -#endif - if (preferableBackend != backendId) { - preferableBackend = backendId; clear(); + if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) + { +#if defined(HAVE_INF_ENGINE) + switchToOpenVINOBackend(net); +#elif defined(ENABLE_PLUGINS) + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + networkBackend.switchBackend(net); +#else + CV_Error(Error::StsNotImplemented, "OpenVINO backend is not available in the current OpenCV build"); +#endif + } + else if (backendId == DNN_BACKEND_CANN) + { +#ifdef HAVE_CANN + switchToCannBackend(net); +#else + CV_Error(Error::StsNotImplemented, "CANN backend is not availlable in the current OpenCV build"); +#endif + } + else + { + preferableBackend = backendId; + } } } diff --git a/modules/dnn/src/net_impl_fuse.cpp b/modules/dnn/src/net_impl_fuse.cpp index 753c00de90..79365d0411 100644 --- a/modules/dnn/src/net_impl_fuse.cpp +++ b/modules/dnn/src/net_impl_fuse.cpp @@ -162,6 +162,178 @@ void Net::Impl::fuseLayers(const std::vector& blobsToKeep_) break; } + // CPU: fuse Convolution 2D layer followed by Add + activation. + while (nextData && (IS_DNN_CPU_TARGET(preferableTarget)) && ld.layerInstance->type == "Convolution") + { + // Note that we can only deal with conv + Add + activ here. + // To avoid the order like: conv + activ + add, if we found the conv has been fused with activ, we break. + Ptr convLayer = ld.layerInstance.dynamicCast(); + + // Only Conv2D without fusion Activation supports this fusion, other-wise, we skip. + if (!convLayer->isConv2D || convLayer->fusedActivation) + break; + + // For now, there are currently two layers in OpenCV that run the Add operator. + Ptr nextNaryEltwiseLayer = nextData->layerInstance.dynamicCast(); + Ptr nextEltwiseLayer = nextData->layerInstance.dynamicCast(); + if (nextNaryEltwiseLayer.empty() && nextEltwiseLayer.empty()) + break; + + if (nextData->inputBlobsId.size() != 2) + break; + + if (!nextData->params.has("operation") || toLowerCase(nextData->params.get("operation")) != "add") + { + CV_LOG_DEBUG(NULL, "DNN/CPU: fusion with NaryEltwise or Eltwise Layer operation is not supported: " + << nextData->params.get("operation")); + break; + } + + // This optimization is for cases like + // some_layer conv + // | | + // +-- eltwise or (naryEltwise) --+ + // | + // activ + // This way all the element-wise computations + // (i.e. some_layer+conv) would be done at [conv] layer. + // So we need to replace [conv]'s output blob to [eltwise]'s one + // considering that [activ] is an in-place layer. + // Also we need to move all the consumers' references. + // To prevent memory collisions (i.e. when input of + // [conv] and output of [eltwise or naryEltwise] is the same blob) + // we allocate a new blob. + { + LayerData *naryOrEltwiseData = nextData; + + // Eltwise or NaryEltwise layer has two inputs. We need to determine which + // is a base convolution layer and which could be used as it's bias. + LayerData* biasLayerData = 0; + for (int i = 0; i < 2; ++i) + { + LayerData *downLayerData = &layers[naryOrEltwiseData->inputBlobsId[i].lid]; + CV_Assert(downLayerData); + // If the current downLayerData is skip, it means it is fused into the parent node. + while (downLayerData->skip) + { + if (downLayerData->inputBlobsId.size() == 1) + downLayerData = &layers[downLayerData->inputBlobsId[0].lid]; + else + { + downLayerData = 0; + break; + } + } + + if (downLayerData && ld.id == downLayerData->id) + { + biasLayerData = &layers[naryOrEltwiseData->inputBlobsId[1 - i].lid]; + break; + } + } + + // We check if biasLayerData is expected layer. + if (!biasLayerData) + break; + + // We check if the bias output shape and the ld output shape are the same. + MatShape biasOutShape = shape(biasLayerData->outputBlobs[0]); + MatShape ldOutShape = shape(ld.outputBlobs[0]); + if (biasOutShape != ldOutShape) + break; + + CV_Assert(biasLayerData); + { + // fuse naryEltwise layer + // bias must already be computed to fuse => bias layer must appear before convolution + if (biasLayerData->id < ld.id) + { + // conv + naryEltwise. + CV_Assert_N(biasLayerData->outputBlobs.size() == 1, ld.inputBlobs.size() == 1); + CV_Assert_N(biasLayerData->outputBlobsWrappers.size() == 1, ld.inputBlobsWrappers.size() == 1); + + printf_(("\tfused with %s\n", nextNaryEltwiseLayer->name.c_str())); + naryOrEltwiseData->skip = true; + + + CV_Assert_N(ld.outputBlobs.size() == 1, ld.outputBlobsWrappers.size() == 1); + // Note: Here's a trick. We set the output of conv as the output of biasLayer. + ld.outputBlobs[0] = ld.outputBlobs[0].clone(); + ld.outputBlobsWrappers[0] = wrap(ld.outputBlobs[0]); + + // Recursively modifies the output data of biasLayerData and its parent. + std::vector skipDataList; + skipDataList.push_back(biasLayerData); + + while (!skipDataList.empty()) + { + LayerData* skipData = skipDataList.back(); + skipDataList.pop_back(); + + CV_Assert(skipData->outputBlobs.size() == 1); + skipData->outputBlobs[0] = ld.outputBlobs[0]; + skipData->outputBlobsWrappers[0] = ld.outputBlobsWrappers[0]; + if (skipData->skip) + { + for (auto& inputLayerId : skipData->inputLayersId) + { + LayerData* inputld = &layers[inputLayerId]; + + if (inputld && inputld->outputBlobs.size() == 1) + skipDataList.push_back(inputld); + } + } + } + + naryOrEltwiseData->outputBlobs = ld.outputBlobs; + naryOrEltwiseData->outputBlobsWrappers = ld.outputBlobsWrappers; + + // set the fusedAdd flag in [Conv]; + convLayer->fusedAdd = true; + LayerData* finalData = naryOrEltwiseData; + /* After fused Conv + naryEltwise or eltwise, we can fuse activation if: + * => activation layer that follows is the only consumer of eltwise output + * => activation layer does not process multiple inputs + * => we do not require to keep the output of eltwise + */ + if (naryOrEltwiseData->consumers.size() == 1) + { + Ptr nextFusabeleActivLayer; + LayerData* nextAct = &layers[naryOrEltwiseData->consumers[0].lid]; + + if (nextData->outputBlobs.size() == 1) + nextFusabeleActivLayer = nextAct->layerInstance.dynamicCast(); + + if (!nextFusabeleActivLayer.empty()) + { + convLayer->setActivation(nextFusabeleActivLayer); + nextAct->skip = true; + + nextAct->outputBlobs = ld.outputBlobs; + nextAct->outputBlobsWrappers = ld.outputBlobsWrappers; + } + } + + // Move references of finalData (eltwise or activation) layer consumers to the newly allocated blob. + for (int i = 0; i < finalData->consumers.size(); ++i) + { + LayerData& consumer = layers[finalData->consumers[i].lid]; + for (int j = 0; j < consumer.inputBlobsId.size(); ++j) + { + if (consumer.inputBlobsId[j].lid == finalData->id) + { + consumer.inputBlobs[j] = &ld.outputBlobs[0]; + consumer.inputBlobsWrappers[j] = ld.outputBlobsWrappers[0]; + break; + } + } + } + } + } + } + break; + } + // OpenCL: fuse convolution layer followed by eltwise + relu // CUDA: fuse convolution layer followed by eltwise (and optional activation) while (nextData && @@ -398,7 +570,7 @@ void Net::Impl::fuseLayers(const std::vector& blobsToKeep_) // (i.e. some_layer+conv or some_layer*conv) // would be done at [conv] layer. So we need to // replace [conv]'s output blob to [eltwise]'s one. - // Also we need to move all the consumers' references. + // Also, we need to move all the consumers' references. // To prevent memory collisions (i.e. when input of // [conv] and output of [eltwise] is the same blob) // we allocate a new blob. @@ -462,7 +634,7 @@ void Net::Impl::fuseLayers(const std::vector& blobsToKeep_) pin = inp_i_data->inputBlobsId[0]; inp_i_data = &layers[pin.lid]; } - conv_layer = conv_layer && (inp_i_data->getLayerInstance()->type == "Convolution"); + conv_layer = conv_layer && (getLayerInstance(*inp_i_data)->type == "Convolution"); } if (!conv_layer) continue; diff --git a/modules/dnn/src/net_openvino.cpp b/modules/dnn/src/net_openvino.cpp index a546b0237d..5704cb9b64 100644 --- a/modules/dnn/src/net_openvino.cpp +++ b/modules/dnn/src/net_openvino.cpp @@ -11,17 +11,217 @@ #include "net_impl.hpp" +#include "backend.hpp" +#include "factory.hpp" + namespace cv { namespace dnn { CV__DNN_INLINE_NS_BEGIN #ifdef HAVE_INF_ENGINE +// TODO: use "string" target specifier +class NetImplOpenVINO CV_FINAL : public Net::Impl +{ +public: + typedef Net::Impl Base; + + // this default constructor is used with OpenVINO native loader + // TODO: dedicated Impl? + NetImplOpenVINO() + : Net::Impl() + { + preferableBackend = DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + } + + // constructor to derive execution implementation from the loaded network + explicit NetImplOpenVINO(const Ptr& basePtr) + : Net::Impl() + { + basePtr_ = basePtr; + init(); + } + + void init() + { + CV_TRACE_FUNCTION(); + CV_Assert(basePtr_); + Net::Impl& base = *basePtr_; + CV_Assert(!base.netWasAllocated); + CV_Assert(!base.netWasQuantized); + netInputLayer = base.netInputLayer; + blobsToKeep = base.blobsToKeep; + layers = base.layers; + for (MapIdToLayerData::iterator it = layers.begin(); it != layers.end(); it++) + { + LayerData& ld = it->second; + ld.resetAllocation(); + } + layerNameToId = base.layerNameToId; + outputNameToId = base.outputNameToId; + //blobManager = base.blobManager; + preferableBackend = DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; //base.preferableBackend; + preferableTarget = base.preferableTarget; + hasDynamicShapes = base.hasDynamicShapes; + CV_Assert(base.backendWrappers.empty()); //backendWrappers = base.backendWrappers; + lastLayerId = base.lastLayerId; + netWasAllocated = base.netWasAllocated; + netWasQuantized = base.netWasQuantized; + fusion = base.fusion; + } + + + //bool isAsync; // FIXIT: drop + + + bool empty() const override + { + return Base::empty(); + } + void setPreferableBackend(Net& net, int backendId) override + { + if (backendId == DNN_BACKEND_INFERENCE_ENGINE || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) + return; // no-op + if (!basePtr_) + CV_Error(Error::StsError, "DNN: Can't switch backend of network created by OpenVINO native loader"); + Ptr& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net); + impl_ptr_ref = basePtr_; + basePtr_->setPreferableBackend(net, backendId); + } + + void setPreferableTarget(int targetId) override + { + if (preferableTarget != targetId) + { + preferableTarget = targetId; + clear(); + } + } + + Ptr wrap(Mat& host) override + { + return Ptr(new NgraphBackendWrapper(preferableTarget, host)); + } + + + void clear() override + { + Base::clear(); + } + + void validateBackendAndTarget() override + { + CV_TRACE_FUNCTION(); + + CV_Assert(preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH); + CV_Check((int)preferableTarget, + preferableTarget == DNN_TARGET_CPU || + preferableTarget == DNN_TARGET_OPENCL || + preferableTarget == DNN_TARGET_OPENCL_FP16 || + preferableTarget == DNN_TARGET_MYRIAD || + preferableTarget == DNN_TARGET_HDDL || + preferableTarget == DNN_TARGET_FPGA, + "Unknown OpenVINO target" + ); + } + + Ptr createLayerInstance(const LayerData& ld) const override + { + // try to create layer instance from backend-specific pool (e.g., plugin) + Ptr instance = LayerFactory::createLayerInstance(ld.type, const_cast(ld.params)); + if (!instance) + instance = Base::createLayerInstance(ld); + return instance; + } + + void addNgraphOutputs(LayerData& ld); + + void initBackend(const std::vector& blobsToKeep_) override; + + void fuseLayers(const std::vector& blobsToKeep_) override; + + void forwardLayer(LayerData& ld) override; + + AsyncArray getBlobAsync(const LayerPin& pin) override; + + //string dump(bool forceAllocation = false) const override; + + static + Net createNetworkFromModelOptimizer(InferenceEngine::CNNNetwork& ieNet); + +}; // NetImplOpenVINO + + +void NetImplOpenVINO::forwardLayer(LayerData& ld) +{ + CV_TRACE_FUNCTION(); + + Ptr layer = ld.layerInstance; + + if (!ld.skip) + { + auto it = ld.backendNodes.find(preferableBackend); + if (ld.id == 0 || // input layer + it == ld.backendNodes.end() // non-supported layer or its mode + ) + { + return Base::forwardLayer(ld); + } + + CV_Assert(it != ld.backendNodes.end()); + const Ptr& node = it->second; + CV_Assert(!node.empty()); + Ptr ieNode = node.dynamicCast(); + CV_Assert(!ieNode.empty()); + CV_Assert(ieNode->net); + + TickMeter tm; + tm.start(); + + ieNode->net->forward(ld.outputBlobsWrappers, isAsync); + + tm.stop(); + int64 t = tm.getTimeTicks(); + layersTimings[ld.id] = (t > 0) ? t : 1; // zero for skipped layers only + } + else + { + layersTimings[ld.id] = 0; + } + + ld.flag = 1; +} + +AsyncArray NetImplOpenVINO::getBlobAsync(const LayerPin& pin) +{ + CV_TRACE_FUNCTION(); + if (!pin.valid()) + CV_Error(Error::StsObjectNotFound, "Requested blob not found"); + + LayerData& ld = layers[pin.lid]; + if ((size_t)pin.oid >= ld.outputBlobs.size()) + { + CV_Error(Error::StsOutOfRange, format("Layer \"%s\" produce only %d outputs, " + "the #%d was requested", + ld.name.c_str(), (int)ld.outputBlobs.size(), (int)pin.oid)); + } + if (preferableTarget != DNN_TARGET_CPU) + { + CV_Assert(!ld.outputBlobsWrappers.empty() && !ld.outputBlobsWrappers[pin.oid].empty()); + // Transfer data to CPU if it's require. + ld.outputBlobsWrappers[pin.oid]->copyToHost(); + } + CV_Assert(preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH); + + Ptr wrapper = ld.outputBlobsWrappers[pin.oid].dynamicCast(); + return std::move(wrapper->futureMat); +} + /** mark input pins as outputs from other subnetworks * FIXIT must be done by DNN engine not ngraph. */ -void Net::Impl::addNgraphOutputs(LayerData& ld) +void NetImplOpenVINO::addNgraphOutputs(LayerData& ld) { CV_TRACE_FUNCTION(); @@ -59,7 +259,7 @@ void Net::Impl::addNgraphOutputs(LayerData& ld) } } -void Net::Impl::initNgraphBackend(const std::vector& blobsToKeep_) +void NetImplOpenVINO::initBackend(const std::vector& blobsToKeep_) { CV_TRACE_FUNCTION(); CV_CheckEQ(preferableBackend, DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, ""); @@ -75,24 +275,22 @@ void Net::Impl::initNgraphBackend(const std::vector& blobsToKeep_) (netInputLayer->outNames.size() == ld.outputBlobsWrappers.size())); for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { - InferenceEngine::DataPtr dataPtr = ngraphDataNode(ld.outputBlobsWrappers[i]); std::string outputName = netInputLayer->outNames.empty() ? ld.name : netInputLayer->outNames[i]; outputName = ld.outputBlobsWrappers.size() > 1 ? (outputName + "." + std::to_string(i)) : outputName; - dataPtr->setName(outputName); + ld.outputBlobsWrappers[i].dynamicCast()->name = outputName; } } else { for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) { - InferenceEngine::DataPtr dataPtr = ngraphDataNode(ld.outputBlobsWrappers[i]); std::string outputName = ld.outputBlobsWrappers.size() > 1 ? (ld.name + "." + std::to_string(i)) : ld.name; - dataPtr->setName(outputName); + ld.outputBlobsWrappers[i].dynamicCast()->name = outputName; } } } - if (skipInfEngineInit) + if (!basePtr_) // model is loaded by OpenVINO { Ptr node = layers[lastLayerId].backendNodes[preferableBackend]; CV_Assert(!node.empty()); @@ -111,26 +309,7 @@ void Net::Impl::initNgraphBackend(const std::vector& blobsToKeep_) { for (int i = 0; i < ld.inputBlobsWrappers.size(); ++i) { - InferenceEngine::DataPtr dataPtr = ngraphDataNode(ld.inputBlobsWrappers[i]); - dataPtr->setName(netInputLayer->outNames[i]); - } - } - else - { - for (int i = 0; i < ld.outputBlobsWrappers.size(); ++i) - { - auto it = ienet.outputsDesc.find(ld.name); - if (it != ienet.outputsDesc.end()) - { - const InferenceEngine::TensorDesc& descriptor = it->second; - InferenceEngine::DataPtr dataPtr = ngraphDataOutputNode(ld.outputBlobsWrappers[i], descriptor, ld.name); - dataPtr->setName(ld.name); - } - else - { - InferenceEngine::DataPtr dataPtr = ngraphDataNode(ld.outputBlobsWrappers[i]); - dataPtr->setName(ld.name); - } + ld.inputBlobsWrappers[i].dynamicCast()->name = netInputLayer->outNames[i]; } } ienet.addBlobs(ld.inputBlobsWrappers); @@ -256,10 +435,10 @@ void Net::Impl::initNgraphBackend(const std::vector& blobsToKeep_) dynamicCast(); CV_Assert(!inpWrapper.empty()); auto iter = std::find(inputNames.begin(), inputNames.end(), - inpWrapper->dataPtr->getName()); + inpWrapper->name); if (iter == inputNames.end()) { - inputNames.push_back(inpWrapper->dataPtr->getName()); + inputNames.push_back(inpWrapper->name); inputs.push_back(inpLd.outputBlobs[cons_inp]); } curr_pos = cons + 1; @@ -305,7 +484,12 @@ void Net::Impl::initNgraphBackend(const std::vector& blobsToKeep_) CV_LOG_DEBUG(NULL, "DNN/IE: bind output port " << lid << ":" << oid << " (" << ngraph_input_node->get_friendly_name() << ":" << ngraph_input_node->get_type_info().name << ")"); // Handle parameters from other subnets. Output port is not used in this case +#if INF_ENGINE_VER_MAJOR_GT(INF_ENGINE_RELEASE_2020_4) if ((ngraph::op::is_parameter(ngraph_input_node) || ngraph::op::is_constant(ngraph_input_node)) && +#else + if ((ngraph_input_node->is_parameter() || ngraph_input_node->is_constant()) && +#endif + ngraph_input_node->get_output_size() == 1) { inputNodes[i] = Ptr(new InfEngineNgraphNode(ngraph_input_node)); @@ -399,25 +583,142 @@ void Net::Impl::initNgraphBackend(const std::vector& blobsToKeep_) } } -//} // Net::Impl + +#if 0 +#define printf_(args) printf args +#else +#define printf_(args) +#endif + +void NetImplOpenVINO::fuseLayers(const std::vector& blobsToKeep_) +{ + CV_TRACE_FUNCTION(); + + if(!fusion) + return; + + CV_Check((int)preferableBackend, preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, ""); + +#if 0 // FIXIT mode without fusion is broken due to unsupported layers and handling of "custom" nodes + return; +#endif + + // scan through all the layers. If there is convolution layer followed by the activation layer, + // we try to embed this activation into the convolution and disable separate execution of the activation + + // FIXIT replace by layersToKeep to avoid hacks like "LayerPin(lid, 0)" + std::set pinsToKeep(blobsToKeep_.begin(), + blobsToKeep_.end()); + for (MapIdToLayerData::const_iterator it = layers.begin(); it != layers.end(); it++) + { + int lid = it->first; + LayerData& ld = layers[lid]; + if (ld.skip) + { + printf_(("skipped %s: %s\n", ld.layerInstance->name.c_str(), ld.layerInstance->type.c_str())); + continue; + } + printf_(("analyzing %s: %s\n", ld.layerInstance->name.c_str(), ld.layerInstance->type.c_str())); + + // the optimization #1. try to fuse batch norm, scaling and/or activation layers + // with the current layer if they follow it. Normally, the are fused with the convolution layer, + // but some of them (like activation) may be fused with fully-connected, elemwise (+) and + // some other layers. + Ptr& currLayer = ld.layerInstance; + if (ld.consumers.size() == 1 && pinsToKeep.count(LayerPin(lid, 0)) == 0) + { + LayerData* nextData = &layers[ld.consumers[0].lid]; + LayerPin lpNext(ld.consumers[0].lid, 0); + while (nextData) + { + if (preferableBackend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && pinsToKeep.count(lpNext) != 0) + { + CV_LOG_DEBUG(NULL, "DNN/IE: skip fusing with 'output' node: " << nextData->name << "@" << nextData->type); + break; + } + + /* we use `tryFuse` member of convolution layer to fuse eltwise later + * it's not intended to be fused here; hence, we stop when we encounter eltwise + */ + Ptr nextLayer = nextData->layerInstance; + if (currLayer->tryFuse(nextLayer)) + { + printf_(("\tfused with %s\n", nextLayer->name.c_str())); + nextData->skip = true; + ld.outputBlobs = layers[lpNext.lid].outputBlobs; + ld.outputBlobsWrappers = layers[lpNext.lid].outputBlobsWrappers; + if (nextData->consumers.size() == 1) + { + int nextLayerId = nextData->consumers[0].lid; + nextData = &layers[nextLayerId]; + lpNext = LayerPin(nextLayerId, 0); + } + else + { + nextData = 0; + break; + } + } + else + break; + } + } + } +} + + + +void switchToOpenVINOBackend(Net& net) +{ + CV_TRACE_FUNCTION(); + Ptr& impl_ptr_ref = accessor::DnnNetAccessor::getImplPtrRef(net); + CV_Assert(impl_ptr_ref); + CV_LOG_INFO(NULL, "DNN: switching to OpenVINO backend... (networkID=" << impl_ptr_ref->networkId << ")"); + Ptr openvino_impl_ptr = makePtr(impl_ptr_ref); + impl_ptr_ref = openvino_impl_ptr; +} + /*static*/ -Net Net::Impl::createNetworkFromModelOptimizer(InferenceEngine::CNNNetwork& ieNet) +Net NetImplOpenVINO::createNetworkFromModelOptimizer(InferenceEngine::CNNNetwork& ieNet) { CV_TRACE_FUNCTION(); CV_TRACE_REGION("register_inputs"); + auto ngraphFunction = ieNet.getFunction(); + CV_Assert(ngraphFunction); + std::vector inputsNames; std::vector inp_shapes; - for (auto& it : ieNet.getInputsInfo()) + for (auto& it : ngraphFunction->get_parameters()) { - inputsNames.push_back(it.first); - std::vector dims = it.second->getTensorDesc().getDims(); + inputsNames.push_back(it->get_friendly_name()); + std::vector dims = it->get_shape(); inp_shapes.push_back(std::vector(dims.begin(), dims.end())); } + // nGraph models produce output "Result" layers which have "/sink_port" suffix in their names. + // Their inputs are actual model outputs and we change friendly name to it. + // By this workaround, we produce similar outputs names comparing to ieNet.getOutputsInfo() + for (int i = 0; i < ngraphFunction->get_output_size(); ++i) { + auto res = ngraphFunction->output(i); +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + const std::string& name = res.get_any_name(); +#else + auto out = res.get_node()->input(0).get_source_output(); + std::string name = out.get_node()->get_friendly_name(); + if (out.get_node()->get_output_size() > 1) + name += "." + std::to_string(out.get_index()); +#endif + if (res.get_node()->get_friendly_name() != name) + res.get_node()->set_friendly_name(name); + } Net cvNet; + Ptr openvino_impl_ptr = makePtr(); + NetImplOpenVINO& openvino_impl = *openvino_impl_ptr; + accessor::DnnNetAccessor::getImplPtrRef(cvNet) = openvino_impl_ptr; + cvNet.setInputsNames(inputsNames); // set empty input to determine input shapes @@ -432,25 +733,23 @@ Net Net::Impl::createNetworkFromModelOptimizer(InferenceEngine::CNNNetwork& ieNe { auto fake_node = std::make_shared(ngraph::element::f32, ngraph::Shape {}); Ptr backendNodeNGraph(new InfEngineNgraphNode(fake_node)); - backendNodeNGraph->net = Ptr(new InfEngineNgraphNet(*(cvNet.impl), ieNet)); + backendNodeNGraph->net = Ptr(new InfEngineNgraphNet(openvino_impl, ieNet)); backendNode = backendNodeNGraph; } CV_TRACE_REGION_NEXT("register_outputs"); - auto ngraphFunction = ieNet.getFunction(); - CV_Assert(ngraphFunction); std::vector> ngraphOperations = ngraphFunction->get_ops(); - for (auto& it : ieNet.getOutputsInfo()) + for (auto& it : ngraphFunction->get_results()) { CV_TRACE_REGION("output"); - const auto& outputName = it.first; + const auto& outputName = it->get_friendly_name(); LayerParams lp; - int lid = cvNet.addLayer(it.first, "", lp); + int lid = cvNet.addLayer(outputName, "", lp); - LayerData& ld = cvNet.impl->layers[lid]; + LayerData& ld = openvino_impl.layers[lid]; { Ptr cvLayer(new NgraphBackendLayer(ieNet)); @@ -498,26 +797,77 @@ Net Net::Impl::createNetworkFromModelOptimizer(InferenceEngine::CNNNetwork& ieNe cvNet.setPreferableBackend(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH); - cvNet.impl->skipInfEngineInit = true; return cvNet; } + + +static +Net openvino_readNetwork(const String& modelPath, const String& binPath) +{ + FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; + + InferenceEngine::Core& ie = getCore(""); + InferenceEngine::CNNNetwork ieNet; + try + { + ieNet = ie.ReadNetwork(modelPath, binPath); + } + catch (const std::exception& e) + { + CV_Error(Error::StsError, std::string("DNN: OpenVINO failed to read model '") + modelPath + "': " + e.what()); + } + + return NetImplOpenVINO::createNetworkFromModelOptimizer(ieNet); +} + + +static +Net openvino_readNetwork( + const uchar* bufferModelConfigPtr, size_t bufferModelConfigSize, + const uchar* bufferWeightsPtr, size_t bufferWeightsSize +) +{ + FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; + + InferenceEngine::Core& ie = getCore(""); + + std::string model; model.assign((char*)bufferModelConfigPtr, bufferModelConfigSize); + + InferenceEngine::CNNNetwork ieNet; + try + { +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + ov::Tensor weights_blob(ov::element::u8, {bufferWeightsSize}, (void*)bufferWeightsPtr); + ieNet = ie.read_model(model, weights_blob); +#else + InferenceEngine::TensorDesc tensorDesc(InferenceEngine::Precision::U8, { bufferWeightsSize }, InferenceEngine::Layout::C); + InferenceEngine::Blob::CPtr weights_blob = InferenceEngine::make_shared_blob(tensorDesc, (uint8_t*)bufferWeightsPtr, bufferWeightsSize); + + ieNet = ie.ReadNetwork(model, weights_blob); +#endif + } + catch (const std::exception& e) + { + CV_Error(Error::StsError, std::string("DNN: OpenVINO failed to read model: ") + e.what()); + } + + return NetImplOpenVINO::createNetworkFromModelOptimizer(ieNet); +} + #endif // HAVE_INF_ENGINE Net Net::readFromModelOptimizer(const String& xml, const String& bin) { CV_TRACE_FUNCTION(); -#ifndef HAVE_INF_ENGINE +#if defined(HAVE_INF_ENGINE) + return openvino_readNetwork(xml, bin); +#elif defined(ENABLE_PLUGINS) + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + return networkBackend.readNetwork(std::string(), xml, bin); +#else CV_UNUSED(xml); CV_UNUSED(bin); CV_Error(Error::StsError, "Build OpenCV with Inference Engine to enable loading models from Model Optimizer."); -#else - - FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; - - InferenceEngine::Core& ie = getCore(""); - InferenceEngine::CNNNetwork ieNet = ie.ReadNetwork(xml, bin); - - return Impl::createNetworkFromModelOptimizer(ieNet); -#endif // HAVE_INF_ENGINE +#endif } Net Net::readFromModelOptimizer(const std::vector& bufferModelConfig, const std::vector& bufferWeights) @@ -535,34 +885,112 @@ Net Net::readFromModelOptimizer( ) { CV_TRACE_FUNCTION(); -#ifndef HAVE_INF_ENGINE +#if defined(HAVE_INF_ENGINE) + return openvino_readNetwork(bufferModelConfigPtr, bufferModelConfigSize, bufferWeightsPtr, bufferWeightsSize); +#elif defined(ENABLE_PLUGINS) + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + return networkBackend.readNetwork(std::string(), bufferModelConfigPtr, bufferModelConfigSize, bufferWeightsPtr, bufferWeightsSize); +#else CV_UNUSED(bufferModelConfigPtr); CV_UNUSED(bufferWeightsPtr); CV_UNUSED(bufferModelConfigSize); CV_UNUSED(bufferModelConfigSize); CV_Error(Error::StsError, "Build OpenCV with Inference Engine to enable loading models from Model Optimizer."); -#else - - FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; - - InferenceEngine::Core& ie = getCore(""); - - std::string model; model.assign((char*)bufferModelConfigPtr, bufferModelConfigSize); - - InferenceEngine::CNNNetwork ieNet; - try - { - InferenceEngine::TensorDesc tensorDesc(InferenceEngine::Precision::U8, { bufferWeightsSize }, InferenceEngine::Layout::C); - InferenceEngine::Blob::CPtr weights_blob = InferenceEngine::make_shared_blob(tensorDesc, (uint8_t*)bufferWeightsPtr, bufferWeightsSize); - - ieNet = ie.ReadNetwork(model, weights_blob); - } - catch (const std::exception& e) - { - CV_Error(Error::StsError, std::string("DNN: IE failed to load model: ") + e.what()); - } - - return Impl::createNetworkFromModelOptimizer(ieNet); -#endif // HAVE_INF_ENGINE +#endif } + CV__DNN_INLINE_NS_END }} // namespace cv::dnn + + + +#ifdef BUILD_PLUGIN + +#define ABI_VERSION 0 +#define API_VERSION 0 +#include "plugin_api.hpp" + + +namespace cv { namespace dnn_backend { + +using namespace cv::dnn; + +class NetworkBackendOpenVINO : public NetworkBackend +{ +public: + void switchBackend(Net& net) CV_OVERRIDE + { + cv::dnn::switchToOpenVINOBackend(net); + } + Net readNetwork(const std::string& loaderID, const std::string& model, const std::string& config) CV_OVERRIDE + { + if (!loaderID.empty()) // only auto ("") is supported + { + CV_Error(Error::StsError, "DNN/OpenVINO: unsupported network loader ID: " + loaderID); + } + return openvino_readNetwork(model, config); + } + Net readNetwork( + const std::string& loaderID, + const uchar* bufferModelConfigPtr, size_t bufferModelConfigSize, + const uchar* bufferWeightsPtr, size_t bufferWeightsSize + ) CV_OVERRIDE + { + if (!loaderID.empty()) // only auto ("") is supported + { + CV_Error(Error::StsError, "DNN/OpenVINO: unsupported network loader ID: " + loaderID); + } + return openvino_readNetwork(bufferModelConfigPtr, bufferModelConfigSize, bufferWeightsPtr, bufferWeightsSize); + } + bool checkTarget(Target target) CV_OVERRIDE + { + return openvino::checkTarget(target); + } +}; + +static +std::shared_ptr& getInstanceNetworkBackendOpenVINO() +{ + static std::shared_ptr g_instance = std::make_shared(); + return g_instance; +} + + +}} // namespace + + +static +CvResult cv_getInstanceNetworkBackend(CV_OUT CvPluginDNNNetworkBackend* handle) CV_NOEXCEPT +{ + try + { + if (!handle) + return CV_ERROR_FAIL; + *handle = cv::dnn_backend::getInstanceNetworkBackendOpenVINO().get(); + return CV_ERROR_OK; + } + catch (...) + { + return CV_ERROR_FAIL; + } +} + +static const OpenCV_DNN_Plugin_API plugin_api = +{ + { + sizeof(OpenCV_DNN_Plugin_API), ABI_VERSION, API_VERSION, + CV_VERSION_MAJOR, CV_VERSION_MINOR, CV_VERSION_REVISION, CV_VERSION_STATUS, + "OpenVINO OpenCV DNN plugin (" CVAUX_STR(INF_ENGINE_RELEASE) ")" + }, + { + /* 1*/cv_getInstanceNetworkBackend + } +}; + +const OpenCV_DNN_Plugin_API* CV_API_CALL opencv_dnn_plugin_init_v0(int requested_abi_version, int requested_api_version, void* /*reserved=NULL*/) CV_NOEXCEPT +{ + if (requested_abi_version == ABI_VERSION && requested_api_version <= API_VERSION) + return &plugin_api; + return NULL; +} + +#endif // BUILD_PLUGIN diff --git a/modules/dnn/src/net_quantization.cpp b/modules/dnn/src/net_quantization.cpp index 8316687412..803a240770 100644 --- a/modules/dnn/src/net_quantization.cpp +++ b/modules/dnn/src/net_quantization.cpp @@ -33,7 +33,7 @@ void getQuantizationParams(const Mat& src, std::vector& scales, std::vect } // FIXIT drop from inference API -Net Net::Impl::quantize(InputArrayOfArrays calibData, int inputsDtype, int outputsDtype, bool perChannel) +Net Net::Impl::quantize(Net& net, InputArrayOfArrays calibData, int inputsDtype, int outputsDtype, bool perChannel) { // Net can be quantized only once. if (netWasQuantized) @@ -47,9 +47,11 @@ Net Net::Impl::quantize(InputArrayOfArrays calibData, int inputsDtype, int outpu int prefTarget = preferableTarget; // Disable fusions and use CPU backend to quantize net - setPreferableBackend(DNN_BACKEND_OPENCV); + // FIXIT: we should not modify original network! + setPreferableBackend(net, DNN_BACKEND_OPENCV); setPreferableTarget(DNN_TARGET_CPU); enableFusion(false); + enableWinograd(false); if (calibData.isMat()) { @@ -163,7 +165,7 @@ Net Net::Impl::quantize(InputArrayOfArrays calibData, int inputsDtype, int outpu Net::Impl& dstNet = *(dstNet_.impl); dstNet.netWasQuantized = true; dstNet.setInputsNames(netInputLayer->outNames); - dstNet.setPreferableBackend(prefBackend); + dstNet.setPreferableBackend(dstNet_, prefBackend); dstNet.setPreferableTarget(prefTarget); dstNet.enableFusion(originalFusion); @@ -253,7 +255,7 @@ Net Net::Impl::quantize(InputArrayOfArrays calibData, int inputsDtype, int outpu } } // Restore FP32 Net's backend, target and fusion - setPreferableBackend(prefBackend); + setPreferableBackend(net, prefBackend); setPreferableTarget(prefTarget); enableFusion(originalFusion); return dstNet_; diff --git a/modules/dnn/src/nms.cpp b/modules/dnn/src/nms.cpp index d321cfed88..c51a630558 100644 --- a/modules/dnn/src/nms.cpp +++ b/modules/dnn/src/nms.cpp @@ -58,6 +58,61 @@ void NMSBoxes(const std::vector& bboxes, const std::vector& NMSFast_(bboxes, scores, score_threshold, nms_threshold, eta, top_k, indices, rotatedRectIOU); } +template +static inline void NMSBoxesBatchedImpl(const std::vector& bboxes, + const std::vector& scores, const std::vector& class_ids, + const float score_threshold, const float nms_threshold, + std::vector& indices, const float eta, const int top_k) +{ + double x1, y1, x2, y2, max_coord = 0; + for (int i = 0; i < bboxes.size(); i++) + { + x1 = bboxes[i].x; + y1 = bboxes[i].y; + x2 = x1 + bboxes[i].width; + y2 = y1 + bboxes[i].height; + + max_coord = std::max(x1, max_coord); + max_coord = std::max(y1, max_coord); + max_coord = std::max(x2, max_coord); + max_coord = std::max(y2, max_coord); + } + + // calculate offset and add offset to each bbox + std::vector bboxes_offset; + double offset; + for (int i = 0; i < bboxes.size(); i++) + { + offset = class_ids[i] * (max_coord + 1); + bboxes_offset.push_back( + Rect_t(bboxes[i].x + offset, bboxes[i].y + offset, + bboxes[i].width, bboxes[i].height) + ); + } + + NMSFast_(bboxes_offset, scores, score_threshold, nms_threshold, eta, top_k, indices, rectOverlap); +} + +void NMSBoxesBatched(const std::vector& bboxes, + const std::vector& scores, const std::vector& class_ids, + const float score_threshold, const float nms_threshold, + std::vector& indices, const float eta, const int top_k) +{ + CV_Assert_N(bboxes.size() == scores.size(), scores.size() == class_ids.size(), nms_threshold >= 0, eta > 0); + + NMSBoxesBatchedImpl(bboxes, scores, class_ids, score_threshold, nms_threshold, indices, eta, top_k); +} + +void NMSBoxesBatched(const std::vector& bboxes, + const std::vector& scores, const std::vector& class_ids, + const float score_threshold, const float nms_threshold, + std::vector& indices, const float eta, const int top_k) +{ + CV_Assert_N(bboxes.size() == scores.size(), scores.size() == class_ids.size(), nms_threshold >= 0, eta > 0); + + NMSBoxesBatchedImpl(bboxes, scores, class_ids, score_threshold, nms_threshold, indices, eta, top_k); +} + void softNMSBoxes(const std::vector& bboxes, const std::vector& scores, std::vector& updated_scores, diff --git a/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp b/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp index bf5fba71a1..b965ba4af1 100644 --- a/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp +++ b/modules/dnn/src/ocl4dnn/include/ocl4dnn.hpp @@ -55,17 +55,18 @@ struct OCL4DNNConvConfig { OCL4DNNConvConfig() : kernel(1, 1), - pad(0, 0), stride(1, 1), dilation(1, 1), group(1), bias_term(false), use_half(false) - {} + { + pads = {0, 0, 0, 0}; + } MatShape in_shape; MatShape out_shape; Size kernel; - Size pad; + std::vector pads; // [pad_top, pad_bottom, pad_left, pad_right] Size stride; Size dilation; int group; // = 1; diff --git a/modules/dnn/src/ocl4dnn/src/ocl4dnn_conv_spatial.cpp b/modules/dnn/src/ocl4dnn/src/ocl4dnn_conv_spatial.cpp index e2129be536..90cc2108d6 100644 --- a/modules/dnn/src/ocl4dnn/src/ocl4dnn_conv_spatial.cpp +++ b/modules/dnn/src/ocl4dnn/src/ocl4dnn_conv_spatial.cpp @@ -181,8 +181,11 @@ OCL4DNNConvSpatial::OCL4DNNConvSpatial(OCL4DNNConvConfig config) // assumption: spatial dimension is 2. kernel_h_ = config.kernel.height; kernel_w_ = config.kernel.width; - pad_h_ = config.pad.height; - pad_w_ = config.pad.width; + // pads: [pad_top, pad_bottom, pad_left, pad_right] + pad_h_ = config.pads[0]; // pad_top + pad_bottom_ = config.pads[1]; + pad_w_ = config.pads[2]; // pad_left + pad_right_ = config.pads[3]; stride_h_ = config.stride.height; stride_w_ = config.stride.width; dilation_h_ = config.dilation.height; @@ -194,12 +197,6 @@ OCL4DNNConvSpatial::OCL4DNNConvSpatial(OCL4DNNConvConfig config) output_w_ = config.out_shape[dims - spatial_dims + 1]; bottom_dim_ = channels_ * width_ * height_; top_dim_ = num_output_ * output_w_ * output_h_; - int Ph = (output_h_ - 1) * stride_h_ + (dilation_h_ * (kernel_h_ - 1) + 1) - height_; - int Pw = (output_w_ - 1) * stride_w_ + (dilation_w_ * (kernel_w_ - 1) + 1) - width_; - Ph = (Ph > 0) ? Ph : 0; - Pw = (Pw > 0) ? Pw : 0; - pad_right_ = (Pw + 1) / 2; - pad_bottom_ = (Ph + 1) / 2; cache_path_ = utils::getConfigurationParameterString("OPENCV_OCL4DNN_CONFIG_PATH", ""); dwconv_ = (num_output_ == channels_ && channels_ == group_); diff --git a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp index 091d2d4ae9..730c08b25c 100644 --- a/modules/dnn/src/onnx/onnx_graph_simplifier.cpp +++ b/modules/dnn/src/onnx/onnx_graph_simplifier.cpp @@ -798,11 +798,67 @@ Mat getMatFromTensor(const opencv_onnx::TensorProto& tensor_proto) Mat(sizes, CV_32FC1, val).copyTo(blob); } } + else if (datatype == opencv_onnx::TensorProto_DataType_FLOAT16) + { + // FIXME, for now, we only load FP16 Tensor as FP32 Mat, full support for FP16 is required in the future. + CV_LOG_ONCE_WARNING(NULL, "DNN: load FP16 model as FP32 model, and it takes twice the FP16 RAM requirement."); + + // ONNX saves float 16 data in two format: int32 and raw_data. + // Link: https://github.com/onnx/onnx/issues/4460#issuecomment-1224373746 + if (!tensor_proto.int32_data().empty()) + { + int offset = 0; +#ifdef WORDS_BIGENDIAN + offset = 1; +#endif + const ::google::protobuf::RepeatedField field = tensor_proto.int32_data(); + + AutoBuffer aligned_val; + size_t sz = tensor_proto.int32_data().size(); + aligned_val.allocate(sz); + float16_t* bufPtr = aligned_val.data(); + + float16_t *fp16Ptr = (float16_t *)field.data(); + for (int i = 0; i < sz; i++) + { + bufPtr[i] = fp16Ptr[i*2 + offset]; + } + Mat(sizes, CV_16FC1, bufPtr).convertTo(blob, CV_32FC1); + } + else + { + char* val = const_cast(tensor_proto.raw_data().c_str()); +#if CV_STRONG_ALIGNMENT + // Aligned pointer is required. + AutoBuffer aligned_val; + if (!isAligned(val)) + { + size_t sz = tensor_proto.raw_data().size(); + aligned_val.allocate(divUp(sz, sizeof(float16_t))); + memcpy(aligned_val.data(), val, sz); + val = (char*)aligned_val.data(); + } +#endif + Mat(sizes, CV_16FC1, val).convertTo(blob, CV_32FC1); + } + } else if (datatype == opencv_onnx::TensorProto_DataType_DOUBLE) { const ::google::protobuf::RepeatedField field = tensor_proto.double_data(); CV_Assert(!field.empty()); - Mat(sizes, CV_64FC1, (void*)field.data()).convertTo(blob, CV_32FC1); + char* val = (char *)field.data(); +#if CV_STRONG_ALIGNMENT + // Aligned pointer is required. + AutoBuffer aligned_val; + if (!isAligned(val)) + { + size_t sz = tensor_proto.raw_data().size(); + aligned_val.allocate(divUp(sz, sizeof(double))); + memcpy(aligned_val.data(), val, sz); + val = (char*)aligned_val.data(); + } +#endif + Mat(sizes, CV_64FC1, val).convertTo(blob, CV_32FC1); } else if (datatype == opencv_onnx::TensorProto_DataType_INT32) { diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 1d6ce96612..fe4d4660f3 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -52,6 +52,13 @@ extern bool DNN_DIAGNOSTICS_RUN; class ONNXLayerHandler; +template +static T getScalarFromMat(Mat m) +{ + CV_Assert(m.total() == 1); + return m.at(0); +} + class ONNXImporter { FPDenormalsIgnoreHintScope fp_denormals_ignore_scope; @@ -60,7 +67,9 @@ class ONNXImporter struct LayerInfo { int layerId; int outputId; - LayerInfo(int _layerId = 0, int _outputId = 0) : layerId(_layerId), outputId(_outputId) {} + int depth; + LayerInfo(int _layerId = 0, int _outputId = 0, int _depth = CV_32F) + :layerId(_layerId), outputId(_outputId), depth(_depth) {} }; struct TensorInfo { @@ -80,12 +89,10 @@ class ONNXImporter void addConstant(const std::string& name, const Mat& blob); void addLayer(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); - void handleQuantizedNode(LayerParams& layerParams, - const opencv_onnx::NodeProto& node_proto); + void setParamsDtype(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void expandMid(const std::string& prefix, opencv_onnx::NodeProto& node_proto, const std::string& input, size_t n); - void addNegation(const LayerParams& layerParams, opencv_onnx::NodeProto& node_proto, int input_id); void lstm_extractConsts(LayerParams& layerParams, const opencv_onnx::NodeProto& lstm_proto, size_t idx, int* blobShape_, int size); void lstm_add_reshape(const std::string& input_name, const std::string& output_name, int* layerShape, size_t n); std::string lstm_add_slice(int index, const std::string& input_name, int* begin, int* end, size_t n); @@ -180,6 +187,9 @@ private: void parseCumSum (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseElementWise (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseDepthToSpace (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseRange (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseScatter (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseTile (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseSimpleLayers (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); // Domain: com.microsoft @@ -192,6 +202,7 @@ private: void parseQSigmoid (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseQAvgPool (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); void parseQConcat (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); + void parseQGemm (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); // '???' domain or '???' layer type void parseCustomLayer (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto); @@ -370,7 +381,10 @@ void runLayer(LayerParams& params, const std::vector& inputs, inpShapes[i] = shape(inputs[i]); if (i > 0 && ddepth != inputs[i].depth()) CV_Error(Error::StsNotImplemented, "Mixed input data types."); - ddepth = inputs[i].depth(); + + // Quantize and Dequantize layer have different output type than input. + if (params.type != "Quantize" && params.type != "Dequantize") + ddepth = inputs[i].depth(); } std::vector outShapes, internalShapes; @@ -606,7 +620,7 @@ void ONNXImporter::addLayer(LayerParams& layerParams, const std::string& output_name = node_proto.output(i); if (!output_name.empty()) { - layer_id.insert(std::make_pair(output_name, LayerInfo(id, i))); + layer_id.insert(std::make_pair(output_name, LayerInfo(id, i, depth))); } } @@ -669,32 +683,6 @@ void ONNXImporter::expandMid(const std::string& prefix, opencv_onnx::NodeProto& } } -/** @brief Multiply one of node_proto inputs by -1 - * @param layerParams parameters of the node - * @param node_proto node which input will be replaced - * @param input_id id of input to be multiplied by -1 - */ -void ONNXImporter::addNegation(const LayerParams& layerParams, opencv_onnx::NodeProto& node_proto, int input_id) -{ - LayerParams powerParams; - powerParams.name = layerParams.name + "/neg"; - powerParams.type = "Power"; - powerParams.set("scale", -1.f); - - //Create Power layer - int id = dstNet.addLayer(powerParams.name, powerParams.type, powerParams); - //Connect to input - IterLayerId_t layerId = layer_id.find(node_proto.input(input_id)); - CV_Assert(layerId != layer_id.end()); - dstNet.connect(layerId->second.layerId, layerId->second.outputId, id, 0); - //Add shape - layer_id.insert(std::make_pair(powerParams.name, LayerInfo(id, 0))); - outShapes[powerParams.name] = outShapes[node_proto.input(input_id)]; - - //Replace input to Power - node_proto.set_input(input_id, powerParams.name); -} - void ONNXImporter::addConstant(const std::string& name, const Mat& blob) { CV_LOG_DEBUG(NULL, "DNN/ONNX: add constant '" << name << "' shape=" << toString(shape(blob)) << ": " << toString(blob)); @@ -753,49 +741,71 @@ void ONNXImporter::parseOperatorSet() } } -void ONNXImporter::handleQuantizedNode(LayerParams& layerParams, - const opencv_onnx::NodeProto& node_proto) +static bool ifInt8Output(const String& layerType) { - // Quantized nodes have output names ending with 'quantized' - std::string outName = node_proto.output(0); - int len = outName.length(); - if (len <= 9) - return; + // Contains all node types whose output should be int8 when it get int8 input. + // ai.onnx opset 15 + static std::vector input8output8List = { + "QuantizeLinear", + "QLinearAdd", + "QLinearMul", + "QLinearAveragePool", + "QLinearGlobalAveragePool", + "QLinearLeakyRelu", + "QLinearSigmoid", + "QLinearConcat", + "QGemm", + "QLinearConv", + "QLinearMatMul", + "MaxPool", + "ReduceMax", + "ReduceMin", + "Split", + "Clip", + "Abs", + "Transpose", + "Squeeze", + "Flatten", + "Unsqueeze", + "Expand", + "Reshape", + "Pad", + "Gather", + "Concat", + "Resize", + "SpaceToDepth", + "DepthToSpace", + "Pow", + "Add", + "Sub", + "Mul", + "Div" + }; + auto layerIt = std::find(input8output8List.begin(), input8output8List.end(), layerType); + return layerIt != input8output8List.end(); +} - if (outName.substr(len - 9) == "quantized") +void ONNXImporter::setParamsDtype(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + // If the current layer should output the same data type as the input, and it's input type is int8, we think the current + // layer should also output int8. + + // Check if the layer has int8 input. + const std::string& layer_type = node_proto.op_type(); + for (int i = 0; i < node_proto.input_size(); ++i) { - outName = outName.substr(0, len - 9); - Mat scale, zeropoint; - - if (constBlobs.find(outName + "scale") != constBlobs.end() && - constBlobs.find(outName + "zero_point") != constBlobs.end()) + if (layer_id.find(node_proto.input(i)) != layer_id.end()) { - scale = getBlob(outName + "scale"); - zeropoint = getBlob(outName + "zero_point"); - } - else - { - std::string inpName = node_proto.input(0); - inpName = inpName.substr(0, inpName.length() - 9); - scale = getBlob(inpName + "scale"); - zeropoint = getBlob(inpName + "zero_point"); + LayerInfo layerInfo = layer_id.find(node_proto.input(i))->second; - for (int i = 0; i < node_proto.output_size(); i++) + if (layerInfo.depth == CV_8S && ifInt8Output(layer_type)) { - std::string out = node_proto.output(i); - out = out.substr(0, out.length() - 9); - addConstant(out + "scale", scale); - addConstant(out + "zero_point", zeropoint); + layerParams.set("depth", CV_8S); + return; } } - - if (scale.total() != 1 || zeropoint.total() != 1) - CV_Error(Error::StsNotImplemented, "Per-channel scales/zeropoints are not supported"); - - layerParams.set("depth", CV_8S); - layerParams.set("scales", DictValue::arrayReal(scale.ptr(), 1)); - layerParams.set("zeropoints", DictValue::arrayInt(zeropoint.ptr(), 1)); } + layerParams.set("depth", CV_32F); } void ONNXImporter::populateNet() @@ -978,7 +988,7 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto) const std::string& layer_type_domain = getLayerTypeDomain(node_proto); const auto& dispatch = getDispatchMap(node_proto); - CV_LOG_DEBUG(NULL, "DNN/ONNX: processing node with " << node_proto.input_size() << " inputs and " + CV_LOG_INFO(NULL, "DNN/ONNX: processing node with " << node_proto.input_size() << " inputs and " << node_proto.output_size() << " outputs: " << cv::format("[%s]:(%s)", layer_type.c_str(), name.c_str()) << cv::format(" from %sdomain='", onnx_opset_map.count(layer_type_domain) == 1 ? "" : "undeclared ") @@ -1000,7 +1010,7 @@ void ONNXImporter::handleNode(const opencv_onnx::NodeProto& node_proto) layerParams.type = layer_type; layerParams.set("has_dynamic_shapes", hasDynamicShapes); - handleQuantizedNode(layerParams, node_proto); + setParamsDtype(layerParams, node_proto); DispatchMap::const_iterator iter = dispatch.find(layer_type); if (iter != dispatch.end()) @@ -1324,72 +1334,59 @@ void ONNXImporter::parseReduce(LayerParams& layerParams, const opencv_onnx::Node void ONNXImporter::parseSlice(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - int axis = 0; - std::vector begin; - std::vector end; + MatShape inpShape = outShapes[node_proto.input(0)]; + int dims = inpShape.size(); + std::vector begin(dims, 0); + std::vector end(dims, INT_MAX); std::vector steps; int inp_size = node_proto.input_size(); + int axis = 0; + bool has_axes = false; + DictValue starts_, ends_, axes_, steps_; + // opset = 1 if (inp_size == 1) { - if (layerParams.has("axes")) { - DictValue axes = layerParams.get("axes"); - for (int i = 1; i < axes.size(); ++i) { - CV_Assert(axes.get(i - 1) == axes.get(i) - 1); - } - axis = axes.get(0); - } - - DictValue starts = layerParams.get("starts"); - DictValue ends = layerParams.get("ends"); - CV_Assert(starts.size() == ends.size()); - - if (axis > 0) { - CV_CheckLE(axis, 1024, "Slice layer can't have more than 1024 axes"); // arbitrary limit - begin.resize(axis, 0); - end.resize(axis, INT_MAX); - } - for (int i = 0; i < starts.size(); ++i) + starts_ = layerParams.get("starts"); + ends_ = layerParams.get("ends"); + CV_Assert(starts_.size() == ends_.size()); + if (layerParams.has("axes")) { - begin.push_back(starts.get(i)); - end.push_back(ends.get(i)); + axes_ = layerParams.get("axes"); + CV_Assert(axes_.size() == starts_.size()); + axis = axes_.getIntValue(0) < 0 ? axes_.getIntValue(0) + dims : axes_.getIntValue(0); + has_axes = true; } - } else { // inp_size > 1 + } + // opset > 1 + else + { CV_Assert(inp_size >= 3); - for (int i = 1; i < inp_size; i++) { + for (int i = 1; i < inp_size; ++i) + { CV_Assert(constBlobs.find(node_proto.input(i)) != constBlobs.end()); } Mat start_blob = getBlob(node_proto, 1); - Mat end_blob = getBlob(node_proto, 2); + Mat end_blob = getBlob(node_proto, 2); CV_Assert(start_blob.total() == end_blob.total()); + starts_ = DictValue::arrayInt(start_blob.begin(), start_blob.total()); + ends_ = DictValue::arrayInt(end_blob.begin(), end_blob.total()); - if (inp_size > 3) { + if (inp_size > 3) + { Mat axes_blob = getBlob(node_proto, 3); - const int* axes = (int*)axes_blob.data; - for (int i = 1; i < axes_blob.total(); ++i) { - CV_Assert(axes[i - 1] == axes[i] - 1); - } - axis = axes[0]; + CV_Assert(axes_blob.total() == start_blob.total()); + axes_ = DictValue::arrayInt(axes_blob.begin(), axes_blob.total()); + axis = axes_.getIntValue(0) < 0 ? axes_.getIntValue(0) + dims : axes_.getIntValue(0); + has_axes = true; } - const int* starts = start_blob.ptr(); - const int* ends = end_blob.ptr(); - if (axis > 0) { - begin.resize(axis, 0); - end.resize(axis, INT_MAX); - } - std::copy(starts, starts + start_blob.total(), std::back_inserter(begin)); - std::copy(ends, ends + end_blob.total(), std::back_inserter(end)); - - if (inp_size == 5) { - CV_Assert(constBlobs.find(node_proto.input(4)) != constBlobs.end()); + if (inp_size == 5) + { Mat step_blob = getBlob(node_proto, 4); - const int* steps_ptr = step_blob.ptr(); - - if (axis > 0) - steps.resize(axis, 1); - - std::copy(steps_ptr, steps_ptr + step_blob.total(), std::back_inserter(steps)); + CV_Assert(step_blob.total() == start_blob.total()); + steps_ = DictValue::arrayInt(step_blob.begin(), step_blob.total()); + steps.resize(dims, 1); // Very strange application for Slice op with tensor reversing. // We just workaround it for 2d constants. @@ -1409,12 +1406,45 @@ void ONNXImporter::parseSlice(LayerParams& layerParams, const opencv_onnx::NodeP } } } + + if (!has_axes) + { + // make a default axes [0, 1, 2...] + Mat axes_tmp(1, starts_.size(), CV_32S); + std::iota(axes_tmp.begin(), axes_tmp.end(), 0); + axes_ = DictValue::arrayInt(axes_tmp.begin(), axes_tmp.total()); + } + + int cur_axe; + std::vector flag(dims, false); + Mat axes(1, starts_.size(), CV_32S); + auto axes_ptr = axes.ptr(); + // resize begin and end + for (int i = 0; i < axes_.size(); ++i) + { + // dims should be added to the negative axes + cur_axe = axes_.getIntValue(i) < 0 ? axes_.getIntValue(i) + dims : axes_.getIntValue(i); + CV_CheckGE(cur_axe, 0, "Axes should be grater or equal to '-dims'."); + CV_CheckLT(cur_axe, dims, "Axes should be less than 'dim'."); + CV_CheckEQ(flag[cur_axe], false, "Axes shouldn't have duplicated values."); + flag[cur_axe] = true; + // change axis to the minimum axe + if (cur_axe < axis) axis = cur_axe; + axes_ptr[i] = cur_axe; + begin[cur_axe] = starts_.getIntValue(i); + end[cur_axe] = ends_.getIntValue(i); + } + layerParams.set("begin", DictValue::arrayInt(&begin[0], begin.size())); layerParams.set("end", DictValue::arrayInt(&end[0], end.size())); layerParams.set("axis", axis); if (!steps.empty()) + { + for (int i = 0; i < axes.total(); ++i) + steps[axes_ptr[i]] = steps_.getIntValue(i); layerParams.set("steps", DictValue::arrayInt(&steps[0], steps.size())); + } if (constBlobs.find(node_proto.input(0)) != constBlobs.end()) { @@ -1971,18 +2001,9 @@ void ONNXImporter::parseGemm(LayerParams& layerParams, const opencv_onnx::NodePr { CV_Assert(node_proto.input_size() >= 2); layerParams.type = "InnerProduct"; - Mat weights = getBlob(node_proto, 1); + int transA = layerParams.get("transA", 0); + layerParams.set("transA", transA == 1); - if (!layerParams.get("transB", 0)) - { - transpose(weights, weights); - } - layerParams.blobs.push_back(weights); - - if (node_proto.input_size() == 3) { - Mat bias = getBlob(node_proto, 2); - layerParams.blobs.push_back(bias); - } if (constBlobs.find(node_proto.input(0)) != constBlobs.end()) { Mat inputBuf = getBlob(node_proto, 0); @@ -1997,66 +2018,94 @@ void ONNXImporter::parseGemm(LayerParams& layerParams, const opencv_onnx::NodePr addLayer(constParams, proto); } - layerParams.set("num_output", layerParams.blobs[0].size[0]); + int transB = layerParams.get("transB", 0); + if (constBlobs.find(node_proto.input(1)) != constBlobs.end()) + { + Mat weights = getBlob(node_proto, 1); + + if (transA == 0) // optimized barnch, for now, we can only optimize the Gemm when transA = 0. + { + if (transB == 0) + { + transpose(weights, weights); + } + layerParams.set("transB", false); + layerParams.blobs.push_back(weights); + layerParams.set("num_output", layerParams.blobs[0].size[0]); + } + else // no optimized branch, TODO! optimize when the transA==1. + { + LayerParams constParams; + constParams.name = node_proto.input(1); + constParams.type = "Const"; + constParams.blobs.push_back(weights); + + opencv_onnx::NodeProto proto; + proto.add_output(constParams.name); + addLayer(constParams, proto); + layerParams.set("transB", transB == 1); + } + } + else + layerParams.set("transB", transB == 1); + + if (node_proto.input_size() == 3) + { + Mat bias = getBlob(node_proto, 2); + layerParams.blobs.push_back(bias); + } + layerParams.set("bias_term", node_proto.input_size() == 3); addLayer(layerParams, node_proto); } -void ONNXImporter::parseMatMul(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +void ONNXImporter::parseMatMul(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_) { + opencv_onnx::NodeProto node_proto = node_proto_; CV_Assert(node_proto.input_size() == 2); layerParams.type = "InnerProduct"; layerParams.set("bias_term", false); - CV_Assert(constBlobs.find(node_proto.input(0)) == constBlobs.end()); - int firstInpDims = outShapes[node_proto.input(0)].size(); - int secondInpDims; + int firstInpDims, secondInpDims; + + if (constBlobs.find(node_proto.input(0)) != constBlobs.end()) + { + Mat blob = getBlob(node_proto, 0); + firstInpDims = blob.dims; + LayerParams constParams; + constParams.name = layerParams.name + "/const_0"; + constParams.type = "Const"; + constParams.blobs.push_back(blob); + + opencv_onnx::NodeProto tmpProto; + tmpProto.add_output(constParams.name); + addLayer(constParams, tmpProto); + + node_proto.set_input(0, constParams.name); + } + else + firstInpDims = outShapes[node_proto.input(0)].size(); if (constBlobs.find(node_proto.input(1)) != constBlobs.end()) { Mat blob = getBlob(node_proto, 1); + Mat transBlob; secondInpDims = blob.dims; - layerParams.blobs.push_back(blob.t()); - layerParams.set("num_output", layerParams.blobs[0].size[0]); - } else { + // create order transposing last 2 dimensions + std::vector order(secondInpDims); + std::iota(order.begin(), order.end(), 0); + std::swap(order[secondInpDims - 2], order[secondInpDims - 1]); + transposeND(blob, order, transBlob); + layerParams.blobs.push_back(transBlob); + int numOutput = layerParams.blobs[0].total(0, secondInpDims - 1); + layerParams.set("num_output", numOutput); + layerParams.set("is_matmul", true); + } else secondInpDims = outShapes[node_proto.input(1)].size(); - } - layerParams.set("axis", firstInpDims - secondInpDims + 1); + + layerParams.set("axis", firstInpDims - 1); addLayer(layerParams, node_proto); } -void findBroadAxis(const MatShape& broadShape, const MatShape& outShape, size_t& axis, int& broadAxis) -{ - // Currently, this function can only complete 1-dimensional expansion of broadShape. - // If there are two dimensions in broadShape that need to be expended, it will fail. - const size_t diff = outShape.size() - broadShape.size(); - - // find the first non-one element of the broadcasting shape - axis = 0; - for (; axis < broadShape.size() && broadShape[axis] == 1; ++axis) {} - - // find the last non-one element of the broadcasting shape - size_t endAxis = broadShape.size(); - for (; endAxis > axis && broadShape[endAxis - 1] == 1; --endAxis) {} - - // find one between axis and endAxis - as it needs to be broadcasted, - // dimensions from the left of axis and from the right of endAxis will be handled by Scale layer - broadAxis = -1; - for (size_t i = axis; i < endAxis; ++i) - { - size_t outAxis = i + diff; - if (outShape[outAxis] == broadShape[i]) - { - continue; - } - - // ensure we need to broadcast only 1 dimension in the middle - CV_Assert(broadShape[i] == 1 && broadAxis == -1); - broadAxis = static_cast(outAxis); - } - - axis += diff; -} - void ONNXImporter::parseConv(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_) { opencv_onnx::NodeProto node_proto = node_proto_; @@ -2071,44 +2120,6 @@ void ONNXImporter::parseConv(LayerParams& layerParams, const opencv_onnx::NodePr int outCn = layerParams.blobs.empty() ? outShapes[node_proto.input(1)][0] : layerParams.blobs[0].size[0]; layerParams.set("num_output", outCn); - // Check for asymmetric padding in Conv2D - if (layerParams.has("pad")) - { - bool asymmetricPadding = false; - DictValue pads = layerParams.get("pad"); - const int dims = pads.size() / 2; - for (int i = 0; i < dims; ++i) - { - if (pads.get(i) != pads.get(i + dims)) - { - asymmetricPadding = true; - break; - } - } - if (asymmetricPadding && pads.size() == 4) // [pad_t, pad_l, pad_b, pad_r] - { - layerParams.erase("pad"); - // No paddings required for N, C axis - std::vector paddings(4, 0); - // Add paddings for H, W axis - for (int i = 0; i < dims; ++i) - { - paddings.push_back(pads.get(i)); - paddings.push_back(pads.get(dims + i)); - } - LayerParams padLp; - padLp.name = layerParams.name + "/pad"; - padLp.type = "Padding"; - padLp.set("paddings", DictValue::arrayInt(&paddings[0], paddings.size())); - - opencv_onnx::NodeProto proto; - proto.add_input(node_proto.input(0)); - proto.add_output(padLp.name); - - addLayer(padLp, proto); - node_proto.set_input(0, padLp.name); - } - } addLayer(layerParams, node_proto); } @@ -2187,17 +2198,39 @@ void ONNXImporter::parseTranspose(LayerParams& layerParams, const opencv_onnx::N void ONNXImporter::parseSqueeze(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - CV_Assert_N(node_proto.input_size() == 1, layerParams.has("axes")); - DictValue axes_dict = layerParams.get("axes"); - MatShape inpShape = outShapes[node_proto.input(0)]; + CV_Assert(node_proto.input_size() <= 2); + MatShape inpShape = outShapes[node_proto.input(0)]; std::vector maskedAxes(inpShape.size(), false); - for (int i = 0; i < axes_dict.size(); ++i) + if (layerParams.has("axes")) { - int axis = axes_dict.getIntValue(i); - CV_CheckLE(axis, static_cast(inpShape.size()), "Squeeze axis"); - maskedAxes[axis] = inpShape[axis] == 1; + DictValue axes_dict = layerParams.get("axes"); + for (int i = 0; i < axes_dict.size(); ++i) + { + int axis = axes_dict.getIntValue(i); + CV_CheckLE(axis, static_cast(inpShape.size()), "Squeeze axis"); + maskedAxes[axis] = inpShape[axis] == 1; + } } + else if (node_proto.input_size() == 2) + { + if (constBlobs.find(node_proto.input(1)) != constBlobs.end()) + { + Mat axesMat = getBlob(node_proto, 1); + if (axesMat.depth() == CV_32F) + axesMat.convertTo(axesMat, CV_32S); + size_t axesLen = axesMat.total(); + for (int i = 0; i < axesLen; i++) + { + int axis = axesMat.at(i); + CV_CheckLE(axis, static_cast(inpShape.size()), "Squeeze axis"); + maskedAxes[axis] = inpShape[axis] == 1; + } + } + else + CV_Error(Error::StsNotImplemented, cv::format("ONNX/Squeeze: doesn't support non-constant 'axes' input")); + } + MatShape outShape; for (int i = 0; i < inpShape.size(); ++i) { @@ -2334,8 +2367,9 @@ void ONNXImporter::parseUnsqueeze(LayerParams& layerParams, const opencv_onnx::N } // CV_Assert(axes.getIntValue(axes.size()-1) <= dims.size()); for (int j = 0; j < axes.size(); j++) { - const int idx = axes.getIntValue(j); - CV_Assert(idx <= dims.size()); + int idx = axes.getIntValue(j); + idx = idx < 0 ? idx + input_dims + 1 : idx; + CV_Assert(0 <= idx && idx <= dims.size()); dims.insert(dims.begin() + idx, 1); } @@ -2352,6 +2386,7 @@ void ONNXImporter::parseUnsqueeze(LayerParams& layerParams, const opencv_onnx::N MatShape inpShape = outShapes[node_proto.input(0)]; int axis = axes.getIntValue(0); + axis = axis < 0 ? axis + (int)inpShape.size() + 1 : axis; CV_Assert(0 <= axis && axis <= inpShape.size()); std::vector outShape = inpShape; outShape.insert(outShape.begin() + axis, 1); @@ -2442,9 +2477,6 @@ void ONNXImporter::parseExpand(LayerParams& layerParams, const opencv_onnx::Node if (!haveVariables) { - if (broadcast_axes.size() > 1) - CV_Error(Error::StsNotImplemented, "Expand op doesn't support multiple axes for constant input"); - if (broadcast_axes.empty()) { addConstant(output_name, getBlob(node_proto, 0)); @@ -2452,10 +2484,15 @@ void ONNXImporter::parseExpand(LayerParams& layerParams, const opencv_onnx::Node } Mat input = getBlob(node_proto, 0); - input = input.reshape(0, total(inpShape, 0, broadcast_axes[0])); - Mat output = cv::repeat(input, 1, targetShape[broadcast_axes[0]]); - output = output.reshape(0, targetShape); - addConstant(output_name, output); + MatShape subTargetShape = inpShape; + for (auto broadcast_axis : broadcast_axes) + { + subTargetShape[broadcast_axis] = targetShape[broadcast_axis]; + input = input.reshape(0, total(inpShape, 0, broadcast_axis)); + Mat output = cv::repeat(input, 1, subTargetShape[broadcast_axis]); + input = output.reshape(0, subTargetShape); + } + addConstant(output_name, input); return; } @@ -2512,6 +2549,12 @@ void ONNXImporter::parseReshape(LayerParams& layerParams, const opencv_onnx::Nod std::vector inputs(1, getBlob(node_proto, 0)), outputs; runLayer(layerParams, inputs, outputs); addConstant(node_proto.output(0), outputs[0]); + if (constBlobsExtraInfo.find(node_proto.input(0)) != constBlobsExtraInfo.end()) + { + const int real_ndims_input0 = getBlobExtraInfo(node_proto, 0).real_ndims; + if (real_ndims_input0 == 1 && blob.total() == 1 && blob.at() == -1) // 1D tensor as input0 (data), and shape is -1 + constBlobsExtraInfo.insert(std::make_pair(node_proto.output(0), TensorInfo(1))); + } return; } } @@ -2563,7 +2606,14 @@ void ONNXImporter::parseShape(LayerParams& layerParams, const opencv_onnx::NodeP CV_Assert(shapeIt != outShapes.end()); const MatShape& inpShape = shapeIt->second; + bool isInput1D = false; + if (constBlobsExtraInfo.find(node_proto.input(0)) != constBlobsExtraInfo.end()) + if (getBlobExtraInfo(node_proto, 0).real_ndims == 1) + isInput1D = true; + int dims = static_cast(inpShape.size()); + if (isInput1D) + dims = 1; Mat shapeMat(dims, 1, CV_32S); bool isDynamicShape = false; for (int j = 0; j < dims; ++j) @@ -2637,83 +2687,57 @@ void ONNXImporter::parseConstantFill(LayerParams& layerParams, const opencv_onnx addConstant(node_proto.output(0), tensor); } -void ONNXImporter::parseGather(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_) +void ONNXImporter::parseGather(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - opencv_onnx::NodeProto node_proto = node_proto_; - CV_Assert(node_proto.input_size() == 2); - Mat indexMat = getBlob(node_proto, 1); - CV_Assert_N(indexMat.type() == CV_32S, indexMat.total() == 1); - int index = indexMat.at(0); - int axis = layerParams.get("axis", 0); + CV_CheckEQ(node_proto.input_size(), 2, ""); - if ((constBlobs.find(node_proto.input(0)) != constBlobs.end())) + // TODO: get rid of the type conversions and 1-d/0-d special-casing when the time comes + if (layer_id.find(node_proto.input(1)) == layer_id.end()) { - Mat input = getBlob(node_proto, 0); - Mat out; - std::vector ranges(input.dims, Range::all()); - ranges[axis] = Range(index, index + 1); - - out = input(ranges); - MatShape outShape = shape(out); - if (outShape.size() > 1) + int real_ndims = getBlobExtraInfo(node_proto.input(1)).real_ndims; + layerParams.set("real_ndims", real_ndims); + if (layer_id.find(node_proto.input(0)) == layer_id.end()) { - outShape.erase(outShape.begin() + axis); - out.reshape(0, outShape); - } else { - out.dims = 1; + std::vector inputs, output; + + Mat input = getBlob(node_proto, 0); + int input_real_ndims = input.dims; + int type = input.type(); + input.convertTo(input, CV_32FC1); + inputs.push_back(input); + + Mat indices = getBlob(node_proto, 1); + indices.convertTo(indices, CV_32FC1); + inputs.push_back(indices); + + runLayer(layerParams, inputs, output); + output.back().convertTo(output.back(), type); + output.back().dims = std::max(input_real_ndims - real_ndims, 1); + addConstant(node_proto.output(0), output.back()); + return; } - addConstant(node_proto.output(0), out); - return; } - else + + for (int i = 0; i < node_proto.input_size(); ++i) { - IterShape_t shapeIt = outShapes.find(node_proto.input(0)); - CV_Assert(shapeIt != outShapes.end()); - MatShape inpShape = shapeIt->second; - - LayerParams sliceLp; - sliceLp.type = "Slice"; - sliceLp.name = inpShape.size() > 1 ? layerParams.name + "/slice" : layerParams.name; - std::vector begin(inpShape.size(), 0); - std::vector end(inpShape.size(), INT_MAX); - begin[axis] = index; - end[axis] = index + 1; - - cv::dnn::DictValue paramBegin = cv::dnn::DictValue::arrayInt(begin.data(), begin.size()); - cv::dnn::DictValue paramEnd = cv::dnn::DictValue::arrayInt(end.data(), end.size()); - sliceLp.set("begin", paramBegin); - sliceLp.set("end", paramEnd); - sliceLp.set("has_dynamic_shapes", hasDynamicShapes); - - if (inpShape.size() > 1) + if (layer_id.find(node_proto.input(i)) == layer_id.end()) { - opencv_onnx::NodeProto proto; - proto.add_input(node_proto.input(0)); - proto.add_output(sliceLp.name); - addLayer(sliceLp, proto); - - inpShape.erase(inpShape.begin() + axis); - layerParams.type = "Reshape"; - layerParams.set("axis", 0); - layerParams.set("dim", DictValue::arrayInt(&inpShape[0], inpShape.size())); - if (hasDynamicShapes) + LayerParams constParams; + constParams.name = node_proto.input(i); + constParams.type = "Const"; + Mat blob = getBlob(node_proto, i); + if (i == 1) { - std::vector dynamicAxes; - std::vector inputIndices; - for (int index = 0; index < inpShape.size(); ++index) - dynamicAxes.push_back(index); - for (int index = 0; index < inpShape.size(); ++index) - inputIndices.push_back(index); - layerParams.set("dynamic_axes", DictValue::arrayInt(dynamicAxes.data(), dynamicAxes.size())); - layerParams.set("input_indices", DictValue::arrayInt(inputIndices.data(), inputIndices.size())); + blob.convertTo(blob, CV_32FC1); } - node_proto.set_input(0, sliceLp.name); - } - else - { - layerParams = sliceLp; + constParams.blobs.push_back(blob); + + opencv_onnx::NodeProto proto; + proto.add_output(constParams.name); + addLayer(constParams, proto); } } + addLayer(layerParams, node_proto); } @@ -2944,7 +2968,7 @@ void ONNXImporter::parseCumSum(LayerParams& layerParams, const opencv_onnx::Node addLayer(layerParams, node_proto); } -// "Equal" "Greater" "Less" "Pow" "Add" "Sub" "Mul" "Div" "Sum" "Min" "Max" +// "Equal" "Greater" "Less" "Pow" "Add" "Sub" "Mul" "Div" "Sum" "Min" "Max" "GreaterOrEqual" "LessOrEqual" void ONNXImporter::parseElementWise(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_) { opencv_onnx::NodeProto node_proto = node_proto_; @@ -3003,6 +3027,8 @@ void ONNXImporter::parseElementWise(LayerParams& layerParams, const opencv_onnx: LayerParams constParams; constParams.name = node_proto.input(i); constParams.type = "Const"; + // Non-constant propagated layers cannot output 1-d or 0-d tensors. + inp.dims = std::max(inp.dims, 2); constParams.blobs.push_back(inp); opencv_onnx::NodeProto proto; @@ -3095,8 +3121,191 @@ void ONNXImporter::parseDepthToSpace(LayerParams& layerParams, const opencv_onnx addLayer(layerParams, node_proto); } +// Currently we only support range with all constant inputs +void ONNXImporter::parseRange(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + CV_Assert(node_proto.input_size() == 3); // 0 - start, 1 - limit, 2 - delta + layerParams.type = "Range"; + + std::vector const_id; + for (int i = 0; i < node_proto.input_size(); i++) + if (layer_id.find(node_proto.input(i)) == layer_id.end()) + const_id.push_back(i); + + // only supports the case which all inputs are constant + CV_Assert(const_id.size() == 3); + + Mat startMat = getBlob(node_proto, 0); + CV_Assert(startMat.type() == CV_32SC1); + int start = startMat.at(0); + + Mat limitMat = getBlob(node_proto, 1); + CV_Assert(limitMat.type() == CV_32SC1); + int limit = limitMat.at(0); + + Mat deltaMat = getBlob(node_proto, 2); + CV_Assert(deltaMat.type() == CV_32SC1); + int delta = deltaMat.at(0); + + int number_of_elements = std::max(int(std::ceil((limit - start) / delta)), 0); + Mat r(number_of_elements, 1, CV_32SC1); + for (int i = 0; i < number_of_elements; i++) + { + r.at(i) = start + (i * delta); + } + addConstant(node_proto.output(0), r); + constBlobsExtraInfo.insert(std::make_pair(node_proto.output(0), TensorInfo(1))); +} + +void ONNXImporter::parseScatter(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + CV_CheckEQ(node_proto.input_size(), 3, "Scatter: three inputs are required."); + layerParams.type = "Scatter"; + if (node_proto.op_type() == "ScatterND") + layerParams.type = "ScatterND"; + + size_t consts = 0; + for (size_t i = 0; i < node_proto.input_size(); ++i) + if (layer_id.find(node_proto.input(i)) == layer_id.end()) + ++consts; + + if (consts == node_proto.input_size()) + { + std::vector inputs, output; + for (size_t i = 0; i < node_proto.input_size(); i++) + { + Mat blob = getBlob(node_proto, i); + if (i == 1) // indices + blob.convertTo(blob, CV_32F); + inputs.push_back(blob); + } + runLayer(layerParams, inputs, output); + CV_Assert(output.size() == 1); + addConstant(node_proto.output(0), output[0]); + return; + } + else if (consts > 0) + { + for (size_t i = 0; i < node_proto.input_size(); i++) + { + if (layer_id.find(node_proto.input(i)) == layer_id.end()) + { + Mat blob = getBlob(node_proto, i); + if (i == 1) // indices, from int32/int64 to float32 + blob.convertTo(blob, CV_32F); + + LayerParams constParams; + constParams.name = node_proto.input(i); + constParams.type = "Const"; + constParams.blobs.push_back(blob); + + opencv_onnx::NodeProto proto; + proto.add_output(constParams.name); + addLayer(constParams, proto); + } + } + } + + addLayer(layerParams, node_proto); +} + +void ONNXImporter::parseTile(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + // for Tile>1, only the case of 'repeats' being constant is supported. + // 'repeats' is treated as a parameter instead of an input to determine shape in pre-run. + + CV_Assert(node_proto.input_size() == 2 || node_proto.input_size() == 3); // tile-1: 3 inputs, tile>1: 2 inputs + bool is_opset_1 = node_proto.input_size() == 3; + + std::vector const_input_idx; + for (size_t i = 0; i < node_proto.input_size(); ++i) + if (layer_id.find(node_proto.input(i)) == layer_id.end()) + const_input_idx.push_back(i); + + bool all_const = false; + if (const_input_idx.size() == node_proto.input_size()) // all inputs are constant + { + all_const = true; + } + else if ((const_input_idx.size() == 1 && const_input_idx[0] == 1) || // tile>1 + (const_input_idx.size() == 2 && const_input_idx[0] == 1 && const_input_idx[1] == 2)) // tile-1 + { + all_const = false; + } + else + { + if (!is_opset_1) + CV_Error(Error::StsNotImplemented, "ONNX/Tile: repeats being non-constant is not supported."); + else + CV_Error(Error::StsNotImplemented, "ONNX/Tile: tiles or axis being non-constant are not supported."); + } + + int input0_dims = 1; + if (all_const) + input0_dims = getBlob(node_proto, 0).dims; + else + input0_dims = outShapes[node_proto.input(0)].size(); + + // repeats, treated as paramenter + std::vector repeats_vec(input0_dims, 1); + Mat input1_blob = getBlob(node_proto, 1); + if (is_opset_1) + { + // input1 in tile-1: tiles, 1d tensor of shape [1] + CV_CheckEQ(input1_blob.total(), 1ull, "ONNX/Tile: tiles must be a 0D tensor or 1D tensor of shape [1]."); + int tiles = input1_blob.at(0); + // input2 in tile-1: axis, 1d tensor of shape [1] + Mat input2_blob = getBlob(node_proto, 2); + CV_CheckEQ(input2_blob.total(), 1ull, "ONNX/Tile: axis must be a 0D tensor or 1D tensor of shape [1]."); + int axis = input2_blob.at(0); + repeats_vec[axis] = tiles; + } + else + { + // input1 in tile>1: repeats + CV_CheckEQ(input1_blob.dims, 2, "ONNX/Tile: repeats must be a 1D tensor."); // 1D tensor is represented as a 2D Mat + for (int i = 0; i < input1_blob.total(); i++) + repeats_vec[i] = input1_blob.at(i); + } + layerParams.set("repeats", DictValue::arrayInt(repeats_vec.data(), repeats_vec.size())); + + if (all_const) + { + std::vector inputs, output; + Mat input0_blob = getBlob(node_proto, 0); + inputs.push_back(input0_blob); + runLayer(layerParams, inputs, output); + CV_Assert(output.size() == 1); + addConstant(node_proto.output(0), output[0]); + return; + } + else + { + addLayer(layerParams, node_proto); + } +} + void ONNXImporter::parseSimpleLayers(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { + bool is_all_input_const = true; + for (int i = 0; i < node_proto.input_size(); i++) + { + if (layer_id.find(node_proto.input(i)) != layer_id.end()) + { + is_all_input_const = false; + break; + } + } + if (is_all_input_const && node_proto.output_size() == 1) + { + std::vector input, output; + for (int i = 0; i < node_proto.input_size(); i++) + input.push_back(getBlob(node_proto, i)); + runLayer(layerParams, input, output); + addConstant(node_proto.output(0), output[0]); + return; + } + for (int j = 0; j < node_proto.input_size(); j++) { if (layer_id.find(node_proto.input(j)) == layer_id.end()) layerParams.blobs.push_back(getBlob(node_proto, j)); @@ -3128,18 +3337,69 @@ void ONNXImporter::parseCustomLayer(LayerParams& layerParams, const opencv_onnx: void ONNXImporter::parseQuantDequant(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - CV_Assert(node_proto.input_size() == 3); + CV_Assert(node_proto.input_size() == 2 || node_proto.input_size() == 3); layerParams.type = (node_proto.op_type() == "QuantizeLinear") ? "Quantize" : "Dequantize"; + int axis = layerParams.get("axis", 1); + // For QuantizeLinear and DequantizeLinear, the scale and zeropoint can be a Scalar (per-tensor quantized) + // or 1-D tensor (per-channel quantized). + bool is1D = false; - if (node_proto.op_type() == "DequantizeLinear") + Mat scaleMat = getBlob(node_proto, 1); + if(scaleMat.total() > 1) is1D = true; + + Mat zpMat; + if (node_proto.input_size() == 3) { - Mat scale = getBlob(node_proto, 1); - Mat zeropoint = getBlob(node_proto, 2); - - layerParams.set("scales", DictValue::arrayReal(scale.ptr(), 1)); - layerParams.set("zeropoints", DictValue::arrayInt(zeropoint.ptr(), 1)); + zpMat = getBlob(node_proto, 2); + CV_Assert(zpMat.total() == scaleMat.total()); // zero point should has the same shape as scale. } - addLayer(layerParams, node_proto); + + if (is1D) + { + const int num = scaleMat.total(); + + std::vector zeropoints(num, 0); + std::vector scales(num, 0); + + for (int i = 0; i < num; i++) + { + scales[i] = scaleMat.at(i); + if (!zpMat.empty()) + zeropoints[i] = zpMat.depth() == CV_32S ? + zpMat.at(i) : (int)zpMat.at(i); + } + + layerParams.set("is1D", true); + layerParams.set("axis", axis); + layerParams.set("scales", DictValue::arrayReal(scales.data(), scales.size())); + layerParams.set("zeropoints", DictValue::arrayInt(zeropoints.data(), zeropoints.size())); + } + else + { + int zeropoint = zpMat.empty() ? 0 : zpMat.depth() == CV_32S ? + getScalarFromMat(zpMat) : (int)getScalarFromMat(zpMat); + float scale = getScalarFromMat(scaleMat); + + layerParams.set("is1D", false); + layerParams.set("scales", scale); + layerParams.set("zeropoints", zeropoint); + } + + if (layerParams.type == "Quantize") + layerParams.set("depth", CV_8S); + else // Dequantize + layerParams.set("depth", CV_32F); + + if (constBlobs.find(node_proto.input(0)) != constBlobs.end()) // Variable input. + { + std::vector inputs, outputs; + inputs.push_back(getBlob(node_proto, 0)); + + runLayer(layerParams, inputs, outputs); + addConstant(node_proto.output(0), outputs[0]); + } + else + addLayer(layerParams, node_proto); } void ONNXImporter::parseQConv(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_) @@ -3148,8 +3408,8 @@ void ONNXImporter::parseQConv(LayerParams& layerParams, const opencv_onnx::NodeP int ninputs = node_proto.input_size(); CV_Assert(ninputs == 8 || ninputs == 9); - Mat inp_sc = getBlob(node_proto, 1); - Mat inp_zp = getBlob(node_proto, 2); + float inp_sc = getScalarFromMat(getBlob(node_proto, 1)); + int inp_zp = (int)getScalarFromMat(getBlob(node_proto, 2)); if (layerParams.has("pad")) { @@ -3179,7 +3439,7 @@ void ONNXImporter::parseQConv(LayerParams& layerParams, const opencv_onnx::NodeP padLp.type = "PaddingInt8"; padLp.set("paddings", DictValue::arrayInt(&paddings[0], paddings.size())); padLp.set("depth", CV_8S); - padLp.set("value", inp_zp.at(0)); + padLp.set("value", inp_zp); opencv_onnx::NodeProto proto; proto.add_input(node_proto.input(0)); @@ -3194,10 +3454,12 @@ void ONNXImporter::parseQConv(LayerParams& layerParams, const opencv_onnx::NodeP int outCn = weights.size[0]; Mat w_scale = getBlob(node_proto, 4); CV_Assert(w_scale.total() == 1 || w_scale.total() == outCn); - bool per_channel = w_scale.total() == outCn ? true : false; + bool per_channel = w_scale.total() == outCn; Mat wt_sc = (w_scale.total() == outCn) ? w_scale : Mat(1, outCn, CV_32F, Scalar(w_scale.at(0))); - Mat out_sc = getBlob(node_proto, 6); + float out_sc = getScalarFromMat(getBlob(node_proto, 6)); + int8_t out_zp = getScalarFromMat(getBlob(node_proto, 7)); + Mat bias = (ninputs == 9) ? getBlob(node_proto, 8) : Mat::zeros(1, outCn, CV_32S); Mat weights_2d = weights.reshape(1, outCn); @@ -3205,14 +3467,16 @@ void ONNXImporter::parseQConv(LayerParams& layerParams, const opencv_onnx::NodeP Mat outputMultiplier(1, outCn, CV_32F); for (int i = 0; i < outCn; i++) { - biasFused.at(i) = bias.at(i) - inp_zp.at(0)*(cv::sum(weights_2d.row(i))[0]); - outputMultiplier.at(i) = (inp_sc.at(0) * wt_sc.at(i)) / out_sc.at(0); + biasFused.at(i) = bias.at(i) - inp_zp*(cv::sum(weights_2d.row(i))[0]); + outputMultiplier.at(i) = (inp_sc * wt_sc.at(i)) / out_sc; } layerParams.type = "ConvolutionInt8"; layerParams.set("num_output", outCn); - layerParams.set("input_zeropoint", inp_zp.at(0)); - layerParams.set("input_scale",inp_sc.at(0)); + layerParams.set("input_zeropoint", inp_zp); + layerParams.set("input_scale",inp_sc); + layerParams.set("zeropoints", out_zp); + layerParams.set("scales", out_sc); layerParams.set("per_channel", per_channel); layerParams.blobs.push_back(weights); layerParams.blobs.push_back(biasFused); @@ -3230,8 +3494,8 @@ void ONNXImporter::parseQMatMul(LayerParams& layerParams, const opencv_onnx::Nod int firstInpDims = outShapes[node_proto.input(0)].size(); - Mat inp_sc = getBlob(node_proto, 1); - Mat inp_zp = getBlob(node_proto, 2); + float inp_sc = getScalarFromMat(getBlob(node_proto, 1)); + int8_t inp_zp = getScalarFromMat(getBlob(node_proto, 2)); Mat weights = getBlob(node_proto, 3).t(); int outCn = weights.size[0]; @@ -3241,21 +3505,25 @@ void ONNXImporter::parseQMatMul(LayerParams& layerParams, const opencv_onnx::Nod CV_Assert(w_scale.total() == 1 || w_scale.total() == outCn); bool per_channel = w_scale.total() == outCn ? true : false; Mat wt_sc = (w_scale.total() == outCn) ? w_scale : Mat(1, outCn, CV_32F, Scalar(w_scale.at(0))); - Mat out_sc = getBlob(node_proto, 6); + + float out_sc = getScalarFromMat(getBlob(node_proto, 6)); + int8_t out_zp = getScalarFromMat(getBlob(node_proto, 7)); Mat bias(1, outCn, CV_32S); Mat outputMultiplier(1, outCn, CV_32F); for (int i = 0; i < outCn; i++) { - bias.at(i) = -inp_zp.at(0)*(cv::sum(weights.row(i))[0]); - outputMultiplier.at(i) = (inp_sc.at(0) * wt_sc.at(i)) / out_sc.at(0); + bias.at(i) = -inp_zp*(cv::sum(weights.row(i))[0]); + outputMultiplier.at(i) = (inp_sc * wt_sc.at(i)) / out_sc; } layerParams.type = "InnerProductInt8"; layerParams.set("num_output", outCn); layerParams.set("axis", firstInpDims - secondInpDims + 1); - layerParams.set("input_scale", inp_sc.at(0)); - layerParams.set("input_zeropoint", inp_zp.at(0)); + layerParams.set("input_scale", inp_sc); + layerParams.set("input_zeropoint", inp_zp); + layerParams.set("zeropoints", out_zp); + layerParams.set("scales", out_sc); layerParams.set("per_channel", per_channel); layerParams.blobs.push_back(weights); @@ -3264,10 +3532,86 @@ void ONNXImporter::parseQMatMul(LayerParams& layerParams, const opencv_onnx::Nod addLayer(layerParams, node_proto); } +// A * B + C = Y, we require that the dimension of A is [m, k], and the dimension of B is [n, k]. +// And the dim of output Y is [m, n] +void ONNXImporter::parseQGemm(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) +{ + int ninputs = node_proto.input_size(); + CV_Assert(ninputs == 8 || ninputs == 9); + + layerParams.type = "InnerProductInt8"; + + if (constBlobs.find(node_proto.input(3)) == constBlobs.end()) + CV_Error(Error::StsNotImplemented, "Variable weights is not supported"); + + Mat weights = getBlob(node_proto, 3); + + if (!layerParams.get("transB", 0)) + { + transpose(weights, weights); + } + + CV_Assert(layerParams.get("alpha", 1) == 1.0f); + CV_Assert(layerParams.get("transA", 0) == 0); + + int firstInpDims = outShapes[node_proto.input(0)].size(); + + float inp_sc = getScalarFromMat(getBlob(node_proto, 1)); + int8_t inp_zp = getScalarFromMat(getBlob(node_proto, 2)); + + int outCn = weights.size[0]; + int secondInpDims = weights.dims; + + Mat w_scale = getBlob(node_proto, 4); + CV_Assert(w_scale.total() == 1 || w_scale.total() == outCn); + bool per_channel = w_scale.total() == outCn; + Mat wt_sc = (w_scale.total() == outCn) ? w_scale : Mat(1, outCn, CV_32F, Scalar(w_scale.at(0))); + + Mat w_zp = getBlob(node_proto, 5); + int8_t* ptrZp = w_zp.ptr(0); + + for (int i = 0; i < w_zp.total(); i++) + { + if (ptrZp[i] != (int8_t)0) + CV_Error(Error::StsUnsupportedFormat, "The zero-point non-zero case of W is not supported!"); + } + + float out_sc = getScalarFromMat(getBlob(node_proto, 7)); + int8_t out_zp = ninputs == 9 ? getScalarFromMat(getBlob(node_proto, 8)) : 0; + + Mat bias; + if (constBlobs.find(node_proto.input(6)) != constBlobs.end()) + bias = getBlob(node_proto, 6); + else + bias = Mat::zeros(1, outCn, CV_32S); + + Mat biasFused(1, outCn, CV_32S); + Mat outputMultiplier(1, outCn, CV_32F); + for (int i = 0; i < outCn; i++) + { + biasFused.at(i) = bias.at(i) - inp_zp*(cv::sum(weights.row(i))[0]); + outputMultiplier.at(i) = (inp_sc * wt_sc.at(i)) / out_sc; + } + + layerParams.type = "InnerProductInt8"; + layerParams.set("num_output", outCn); + layerParams.set("axis", firstInpDims - secondInpDims + 1); + layerParams.set("input_scale", inp_sc); + layerParams.set("input_zeropoint", inp_zp); + layerParams.set("scales", out_sc); + layerParams.set("zeropoints", out_zp); + layerParams.set("per_channel", per_channel); + + layerParams.blobs.push_back(weights); + layerParams.blobs.push_back(biasFused); + layerParams.blobs.push_back(outputMultiplier); + addLayer(layerParams, node_proto); +} + void ONNXImporter::parseQEltwise(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto_) { opencv_onnx::NodeProto node_proto = node_proto_; - CV_Assert(node_proto.input_size() == 8); + CV_Assert(node_proto.input_size() == 7 || node_proto.input_size() == 8); std::string op = (node_proto.op_type() == "QLinearAdd") ? "sum" : "prod"; int constId = -1; for (int i = 0; i < 4; i += 3) @@ -3276,11 +3620,11 @@ void ONNXImporter::parseQEltwise(LayerParams& layerParams, const opencv_onnx::No constId = i; } - Mat inp_0_sc = getBlob(node_proto, 1); - Mat inp_0_zp = getBlob(node_proto, 2); + float inp_0_sc = getScalarFromMat(getBlob(node_proto, 1)); + int8_t inp_0_zp = getScalarFromMat(getBlob(node_proto, 2)); - Mat inp_1_sc = getBlob(node_proto, 4); - Mat inp_1_zp = getBlob(node_proto, 5); + float inp_1_sc = getScalarFromMat(getBlob(node_proto, 4)); + int8_t inp_1_zp = getScalarFromMat(getBlob(node_proto, 5)); // Set 2nd input as the const input if (constId == 0) @@ -3289,11 +3633,14 @@ void ONNXImporter::parseQEltwise(LayerParams& layerParams, const opencv_onnx::No cv::swap(inp_0_zp, inp_1_zp); } - float out_sc = getBlob(node_proto, 6).at(0); - int8_t out_zp = getBlob(node_proto, 7).at(0); + float out_sc = getScalarFromMat(getBlob(node_proto, 6)); - std::vector inp_scales = {inp_0_sc.at(0), inp_1_sc.at(0)}; - std::vector inp_zps = {inp_0_zp.at(0), inp_1_zp.at(0)}; + int8_t out_zp = 0; + if (node_proto.input_size() == 8) + out_zp = getScalarFromMat(getBlob(node_proto, 7)); + + std::vector inp_scales = {inp_0_sc, inp_1_sc}; + std::vector inp_zps = {inp_0_zp, inp_1_zp}; std::vector coeffs; float offset; @@ -3344,12 +3691,12 @@ void ONNXImporter::parseQEltwise(LayerParams& layerParams, const opencv_onnx::No constParams.name = layerParams.name + "/const"; constParams.type = "ConstInt8"; constParams.set("depth", CV_8S); - constParams.set("scales", DictValue::arrayReal(inp_1_sc.ptr(), 1)); - constParams.set("zeropoints", DictValue::arrayInt(inp_1_zp.ptr(), 1)); + constParams.set("scales", inp_1_sc); + constParams.set("zeropoints", inp_1_zp); constParams.blobs.push_back(blob); int id = dstNet.addLayer(constParams.name, constParams.type, CV_8S, constParams); - layer_id.insert(std::make_pair(constParams.name, LayerInfo(id, 0))); + layer_id.insert(std::make_pair(constParams.name, LayerInfo(id, 0, CV_8S))); outShapes[constParams.name] = shape(blob); node_proto.set_input(constId, constParams.name); @@ -3395,18 +3742,21 @@ void ONNXImporter::parseQEltwise(LayerParams& layerParams, const opencv_onnx::No layerParams.set("input_scales", DictValue::arrayReal(inp_scales.data(), inp_scales.size())); layerParams.set("input_zeropoints", DictValue::arrayInt(inp_zps.data(), inp_zps.size())); + layerParams.set("scales", out_sc); + layerParams.set("zeropoints", out_zp); + addLayer(layerParams, node_proto); } void ONNXImporter::parseQLeakyRelu(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - CV_Assert(node_proto.input_size() == 5); + CV_Assert(node_proto.input_size() == 4 || node_proto.input_size() == 5); float slope = layerParams.get("alpha"); - float inp_sc = getBlob(node_proto, 1).at(0); - int8_t inp_zp = getBlob(node_proto, 2).at(0); - float out_sc = getBlob(node_proto, 3).at(0); - int8_t out_zp = getBlob(node_proto, 4).at(0); + float inp_sc = getScalarFromMat(getBlob(node_proto, 1)); + int8_t inp_zp = getScalarFromMat(getBlob(node_proto, 2)); + float out_sc = getScalarFromMat(getBlob(node_proto, 3)); + int8_t out_zp = node_proto.input_size() == 4 ? 0 : getScalarFromMat(getBlob(node_proto, 4)); Mat lookUpTable(1, 256, CV_8S); int8_t* table = lookUpTable.ptr(); @@ -3421,6 +3771,8 @@ void ONNXImporter::parseQLeakyRelu(LayerParams& layerParams, const opencv_onnx:: layerParams.type = "ReLUInt8"; layerParams.set("input_scale", inp_sc); layerParams.set("input_zeropoint", inp_zp); + layerParams.set("scales", out_sc); + layerParams.set("zeropoints", out_zp); layerParams.set("slope", slope); layerParams.blobs.push_back(lookUpTable); addLayer(layerParams, node_proto); @@ -3428,12 +3780,12 @@ void ONNXImporter::parseQLeakyRelu(LayerParams& layerParams, const opencv_onnx:: void ONNXImporter::parseQSigmoid(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - CV_Assert(node_proto.input_size() == 5); + CV_Assert(node_proto.input_size() == 4 || node_proto.input_size() == 5); - float inp_sc = getBlob(node_proto, 1).at(0); - int8_t inp_zp = getBlob(node_proto, 2).at(0); - float out_sc = getBlob(node_proto, 3).at(0); - int8_t out_zp = getBlob(node_proto, 4).at(0); + float inp_sc = getScalarFromMat(getBlob(node_proto, 1)); + int8_t inp_zp = getScalarFromMat(getBlob(node_proto, 2)); + float out_sc = getScalarFromMat(getBlob(node_proto, 3)); + int8_t out_zp = node_proto.input_size() == 4 ? 0 : getScalarFromMat(getBlob(node_proto, 4)); Mat lookUpTable(1, 256, CV_8S); int8_t* table = lookUpTable.ptr(); @@ -3448,22 +3800,29 @@ void ONNXImporter::parseQSigmoid(LayerParams& layerParams, const opencv_onnx::No layerParams.type = "SigmoidInt8"; layerParams.set("input_scale", inp_sc); layerParams.set("input_zeropoint", inp_zp); + layerParams.set("scales", out_sc); + layerParams.set("zeropoints", out_zp); layerParams.blobs.push_back(lookUpTable); addLayer(layerParams, node_proto); } void ONNXImporter::parseQAvgPool(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto) { - CV_Assert(node_proto.input_size() == 5); - float inp_sc = getBlob(node_proto, 1).at(0); - int8_t inp_zp = getBlob(node_proto, 2).at(0); - float out_sc = getBlob(node_proto, 3).at(0); + CV_Assert(node_proto.input_size() == 4 || node_proto.input_size() == 5); + + float inp_sc = getScalarFromMat(getBlob(node_proto, 1)); + int8_t inp_zp = getScalarFromMat(getBlob(node_proto, 2)); + float out_sc = getScalarFromMat(getBlob(node_proto, 3)); + int8_t out_zp = node_proto.input_size() == 4 ? 0 : getScalarFromMat(getBlob(node_proto, 4)); layerParams.type = "PoolingInt8"; layerParams.set("pool", "ave"); layerParams.set("global_pooling", node_proto.op_type() == "QLinearGlobalAveragePool"); layerParams.set("multiplier", inp_sc/out_sc); + layerParams.set("input_scale", inp_sc); layerParams.set("input_zeropoint", inp_zp); + layerParams.set("scales", out_sc); + layerParams.set("zeropoints", out_zp); addLayer(layerParams, node_proto); } @@ -3473,13 +3832,13 @@ void ONNXImporter::parseQConcat(LayerParams& layerParams, const opencv_onnx::Nod layerParams.type = "ConcatInt8"; int num_inputs = node_proto.input_size(); - float out_scale = getBlob(node_proto, 0).at(0); - int out_zp = getBlob(node_proto, 1).at(0); + float out_scale = getScalarFromMat(getBlob(node_proto, 0)); + int8_t out_zp = getScalarFromMat(getBlob(node_proto, 1)); for (int i = 2; i < num_inputs; i += 3) { - float inp_scale = getBlob(node_proto, i + 1).at(0); - int inp_zp = getBlob(node_proto, i + 2).at(0); + float inp_scale = getScalarFromMat(getBlob(node_proto, i + 1)); + int8_t inp_zp = getScalarFromMat(getBlob(node_proto, i + 2)); if (inp_scale != out_scale || inp_zp != out_zp) { @@ -3567,6 +3926,8 @@ void ONNXImporter::parseQConcat(LayerParams& layerParams, const opencv_onnx::Nod } } } + layerParams.set("scales", out_scale); + layerParams.set("zeropoints", out_zp); addLayer(layerParams, node_proto); } @@ -3582,8 +3943,8 @@ void ONNXImporter::buildDispatchMap_ONNX_AI(int opset_version) dispatch["MaxPool"] = &ONNXImporter::parseMaxPool; dispatch["AveragePool"] = &ONNXImporter::parseAveragePool; dispatch["GlobalAveragePool"] = dispatch["GlobalMaxPool"] = &ONNXImporter::parseGlobalPool; - dispatch["ReduceMax"] = dispatch["ReduceMin"] = dispatch["ReduceMean"] = dispatch["ReduceSum"] = dispatch["ReduceMax"] = - dispatch["ReduceMin"] = dispatch["ReduceSumSquare"] = dispatch["ReduceProd"] = dispatch["ReduceL1"] = + dispatch["ReduceMax"] = dispatch["ReduceMin"] = dispatch["ReduceMean"] = dispatch["ReduceSum"] = + dispatch["ReduceSumSquare"] = dispatch["ReduceProd"] = dispatch["ReduceL1"] = dispatch["ReduceL2"] = dispatch["ReduceLogSum"] = dispatch["ReduceLogSumExp"] = &ONNXImporter::parseReduce; dispatch["Slice"] = &ONNXImporter::parseSlice; dispatch["Split"] = &ONNXImporter::parseSplit; @@ -3624,10 +3985,15 @@ void ONNXImporter::buildDispatchMap_ONNX_AI(int opset_version) dispatch["DetectionOutput"] = &ONNXImporter::parseDetectionOutput; dispatch["CumSum"] = &ONNXImporter::parseCumSum; dispatch["SpaceToDepth"] = dispatch["DepthToSpace"] = &ONNXImporter::parseDepthToSpace; + dispatch["ScatterElements"] = dispatch["Scatter"] = dispatch["ScatterND"] = &ONNXImporter::parseScatter; + dispatch["Tile"] = &ONNXImporter::parseTile; dispatch["Equal"] = dispatch["Greater"] = dispatch["Less"] = dispatch["Pow"] = dispatch["Add"] = - dispatch["Sub"] = dispatch["Mul"] = dispatch["Div"] = &ONNXImporter::parseElementWise; + dispatch["Sub"] = dispatch["Mul"] = dispatch["Div"] = dispatch["GreaterOrEqual"] = + dispatch["LessOrEqual"] = &ONNXImporter::parseElementWise; + dispatch["Sum"] = dispatch["Min"] = dispatch["Max"] = &ONNXImporter::parseElementWise; + dispatch["Range"] = &ONNXImporter::parseRange; std::vector simpleLayers{"Acos", "Acosh", "Asin", "Asinh", "Atan", "Atanh", "Ceil", "Celu", "Cos", "Cosh", "Dropout", "Erf", "Exp", "Floor", "HardSigmoid", "HardSwish", @@ -3658,6 +4024,7 @@ void ONNXImporter::buildDispatchMap_COM_MICROSOFT(int opset_version) dispatch["QLinearLeakyRelu"] = &ONNXImporter::parseQLeakyRelu; dispatch["QLinearSigmoid"] = &ONNXImporter::parseQSigmoid; dispatch["QLinearConcat"] = &ONNXImporter::parseQConcat; + dispatch["QGemm"] = &ONNXImporter::parseQGemm; domain_dispatch_map["com.microsoft"] = dispatch; } diff --git a/modules/dnn/src/op_cann.cpp b/modules/dnn/src/op_cann.cpp new file mode 100644 index 0000000000..6d8a57446b --- /dev/null +++ b/modules/dnn/src/op_cann.cpp @@ -0,0 +1,329 @@ +// 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 "op_cann.hpp" + +#include +#include +#include // memcpy + +#include +#include + +namespace cv { namespace dnn { + +#ifdef HAVE_CANN + +std::shared_ptr AclEnvGuard::global_acl_env_ = nullptr; +std::mutex AclEnvGuard::global_acl_env_mutex_; + +AclEnvGuard::AclEnvGuard() +{ + CV_LOG_INFO(NULL, "Start to initialize CANN"); + ACL_CHECK_RET(aclInit(NULL)); + CV_LOG_INFO(NULL, "[Success] initialized CANN"); +} + +AclEnvGuard::~AclEnvGuard() +{ + CV_LOG_INFO(NULL, "Start to finalize CANN"); + ACL_CHECK_RET(aclFinalize()); + CV_LOG_INFO(NULL, "[Success] finalized CANN"); +} + +std::shared_ptr AclEnvGuard::GetAclEnv() +{ + std::shared_ptr acl_env; + + std::lock_guard lock(global_acl_env_mutex_); + acl_env = global_acl_env_; + if (acl_env != nullptr) + { + CV_LOG_INFO(NULL, "CANN has been initialized. Skipping..."); + } + else + { + acl_env = std::make_shared(); + global_acl_env_ = acl_env; + } + return acl_env; +} + +CannConstOp::CannConstOp(const uint8_t* data, const int dtype, const std::vector& shape, const std::string& name) +{ + std::vector shape_{shape.begin(), shape.end()}; + + auto ge_shape = ge::Shape(shape_); + auto ge_dtype = ge::DT_FLOAT; + switch (dtype) + { + case CV_32F: break; + case CV_32S: ge_dtype = ge::DT_INT32; break; + default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); + } + auto size_of_type = sizeof(float); + switch (dtype) + { + case CV_32F: break; + case CV_32S: size_of_type = sizeof(int); break; + default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); + } + desc_ = std::make_shared(ge_shape, ge::FORMAT_NCHW, ge_dtype); + auto ge_tensor = std::make_shared(); + ge_tensor->SetTensorDesc(*desc_); + ge_tensor->SetData(data, ge_shape.GetShapeSize() * size_of_type); + op_ = std::make_shared(name); + op_->set_attr_value(*ge_tensor); +} + +CannBackendNode::CannBackendNode(const std::shared_ptr& op) + : BackendNode(DNN_BACKEND_CANN), op_(op) { } + +std::shared_ptr CannBackendNode::getOp() { return op_; } + +CannBackendWrapper::CannBackendWrapper(const Mat& m) + : BackendWrapper(DNN_BACKEND_CANN, DNN_TARGET_NPU), host((Mat*)&m) +{ + auto mat_shape = shape(*host); + std::vector shape_{mat_shape.begin(), mat_shape.end()}; + + auto ge_shape = ge::Shape(shape_); + desc_ = std::make_shared(ge_shape, ge::FORMAT_NCHW, ge::DT_FLOAT); +} + +void CannBackendWrapper::copyToHost() +{ + CV_LOG_DEBUG(NULL, "Not implemented"); +} + +void CannBackendWrapper::setHostDirty() +{ + CV_LOG_DEBUG(NULL, "Not implemented"); +} + +CannNet::~CannNet() +{ + CV_LOG_INFO(NULL, "In ~CannNet, inputs = " << inputs << ", outputs = " << outputs); + if (!model_desc) + { + CV_LOG_INFO(NULL, "[Failed] Tried to deconstruct CannNet but model is not loaded"); + return; + } + // free datasets: inputs, outputs + if (inputs) + { + CV_LOG_INFO(NULL, "In ~CannNet: destroy inputs"); + destroyDataset(&inputs); + } + if (outputs) + { + CV_LOG_INFO(NULL, "In ~CannNet: destroy outputs"); + destroyDataset(&outputs); + } + // unload model + ACL_CHECK_RET(aclmdlUnload(model_id)); + // destroy model_desc + ACL_CHECK_RET(aclmdlDestroyDesc(model_desc)); + model_desc = nullptr; + CV_LOG_INFO(NULL, "[Success] Unloaded model (id=" << model_id << ")"); + + // destroy context + if (context != nullptr) + { + ACL_CHECK_RET(aclrtDestroyContext(context)); + context = nullptr; + } + // reset device + if (context == nullptr) + { + ACL_CHECK_RET(aclrtResetDevice(device_id)); + } +} + +bool CannNet::empty() const +{ + return (model_desc == nullptr); +} + +void CannNet::loadModelBuffer(std::shared_ptr modelBuffer) +{ + model.clear(); + model.resize(modelBuffer->length); + std::memcpy(reinterpret_cast(model.data()), + reinterpret_cast(modelBuffer->data.get()), + modelBuffer->length); + loadToDevice(); +} + +void CannNet::bindInputWrappers(const std::vector>& inputWrappers) +{ + CV_Assert(inputWrappers.size() == getInputNum()); + for (size_t i = 0; i < inputWrappers.size(); ++i) + { + auto wrapper = inputWrappers[i].dynamicCast(); + + // verify size + aclmdlIODims model_dims; + ACL_CHECK_RET(aclmdlGetInputDims(model_desc, i, &model_dims)); + CV_CheckEQ((int)model_dims.dimCount, wrapper->host->dims, "Dimension of input does not match with model's requirement"); + for (size_t j = 0; j < model_dims.dimCount; ++j) + CV_CheckEQ((int)model_dims.dims[j], wrapper->host->size[j], "Size of input does not match with model's requirement"); + + input_wrappers.push_back(wrapper); + } +} + +void CannNet::bindOutputWrappers(const std::vector>& outputWrappers) +{ + CV_Assert(outputWrappers.size() == getOutputNum()); + for (int i = 0; i < outputWrappers.size(); ++i) + { + auto wrapper = outputWrappers[i].dynamicCast(); + + // verify size + aclmdlIODims model_dims; + ACL_CHECK_RET(aclmdlGetOutputDims(model_desc, i, &model_dims)); + CV_CheckEQ((int)model_dims.dimCount, wrapper->host->dims, "Dimension of input does not match with model's requirement"); + for (size_t j = 0; j < model_dims.dimCount; ++j) + CV_CheckEQ((int)model_dims.dims[j], wrapper->host->size[j], "Size of input does not match with model's requirement"); + + output_wrappers.push_back(wrapper); + } +} + +void CannNet::forward() +{ + // send inputs from host to device + CV_LOG_DEBUG(NULL, "DNN/CANN: start sending inputs to device"); + for (size_t i = 0; i < input_wrappers.size(); ++i) + { + const void* p_host = (const void*)input_wrappers[i]->host->data; + + auto db = aclmdlGetDatasetBuffer(inputs, i); + auto p_device = aclGetDataBufferAddr(db); + auto db_size = aclGetDataBufferSizeV2(db); + + ACL_CHECK_RET(aclrtMemcpy(p_device, db_size, p_host, db_size, ACL_MEMCPY_HOST_TO_DEVICE)); + } + CV_LOG_DEBUG(NULL, "DNN/CANN: finished sending inputs to device"); + + // forward + CV_LOG_DEBUG(NULL, "DNN/CANN: start network forward"); + ACL_CHECK_RET(aclrtSetCurrentContext(context)); + ACL_CHECK_RET(aclmdlExecute(model_id, inputs, outputs)); + CV_LOG_DEBUG(NULL, "DNN/CANN: finished network forward"); + + // fetch ouputs from device to host + CV_LOG_DEBUG(NULL, "DNN/CANN: start fetching outputs to host"); + for (size_t i = 0; i < output_wrappers.size(); ++i) + { + void* p_host = (void*)output_wrappers[i]->host->data; + + auto db = aclmdlGetDatasetBuffer(outputs, i); + auto p_device = aclGetDataBufferAddr(db); + auto db_size = aclGetDataBufferSizeV2(db); + + ACL_CHECK_RET(aclrtMemcpy(p_host, db_size, p_device, db_size, ACL_MEMCPY_DEVICE_TO_HOST)); + } + CV_LOG_DEBUG(NULL, "DNN/CANN: finish fetching outputs to host"); +} + +size_t CannNet::getInputNum() const +{ + return aclmdlGetNumInputs(model_desc); +} + +size_t CannNet::getOutputNum() const +{ + return aclmdlGetNumOutputs(model_desc); +} + +void CannNet::init() +{ + ACL_CHECK_RET(aclrtSetDevice(device_id)); + ACL_CHECK_RET(aclrtCreateContext(&context, device_id)); +} + +void CannNet::loadToDevice() +{ + if (model_desc != nullptr) + { + CV_LOG_INFO(NULL, "Model has been loaded to device. Skipping ..."); + return; + } + + CV_LOG_INFO(NULL, "Load model to NPU memory"); + ACL_CHECK_RET(aclmdlLoadFromMem(reinterpret_cast(model.data()), model.size(), &model_id)); + + CV_LOG_INFO(NULL, "Create model description"); + model_desc = aclmdlCreateDesc(); + ACL_CHECK_RET(aclmdlGetDesc(model_desc, model_id)); + + createInputDataset(); + createOutputDataset(); +} + +void CannNet::createInputDataset() +{ + inputs = aclmdlCreateDataset(); + size_t n_inputs = aclmdlGetNumInputs(model_desc); + size_t length; + for (size_t i = 0; i < n_inputs; i++) + { + length = aclmdlGetInputSizeByIndex(model_desc, i); + CV_LOG_INFO(NULL, "length = " << length); + void* p_device = nullptr; + ACL_CHECK_RET(aclrtMalloc(&p_device, length, ACL_MEM_MALLOC_NORMAL_ONLY)); + auto p_data_buffer = aclCreateDataBuffer(p_device, length); + ACL_CHECK_RET(aclmdlAddDatasetBuffer(inputs, p_data_buffer)); + } +} + +void CannNet::createOutputDataset() +{ + outputs = aclmdlCreateDataset(); + size_t n_outputs = aclmdlGetNumOutputs(model_desc); + size_t length; + for (size_t i = 0; i < n_outputs; i++) + { + length = aclmdlGetOutputSizeByIndex(model_desc, i); + void* p_device = nullptr; + ACL_CHECK_RET(aclrtMalloc(&p_device, length, ACL_MEM_MALLOC_NORMAL_ONLY)); + auto p_data_buffer = aclCreateDataBuffer(p_device, length); + ACL_CHECK_RET(aclmdlAddDatasetBuffer(outputs, p_data_buffer)); + } +} + +void CannNet::destroyDataset(aclmdlDataset** dataset) +{ + if (!dataset) + { + CV_LOG_INFO(NULL, "CANN dataset is not initialized"); + return; + } + auto buffer_count = aclmdlGetDatasetNumBuffers(*dataset); + CV_LOG_INFO(NULL, "buffer_count = " << buffer_count); + for (auto i = 0; i < buffer_count; i++) + { + auto data_buffer = aclmdlGetDatasetBuffer(*dataset, i); + auto p_device = aclGetDataBufferAddr(data_buffer); + if (p_device) + { + ACL_CHECK_RET(aclrtFree(p_device)); // 107000? + } + else + { + CV_LOG_INFO(NULL, "Data buffer (i=" << i << ") from ACL dataset is invalid"); + } + ACL_CHECK_RET(aclDestroyDataBuffer(data_buffer)); + } + ACL_CHECK_RET(aclmdlDestroyDataset(*dataset)); + *dataset = nullptr; + CV_LOG_INFO(NULL, "[Success] Destroyed dataset"); +} + +#endif // HAVE_CANN + +}} // namespace cv::dnn diff --git a/modules/dnn/src/op_cann.hpp b/modules/dnn/src/op_cann.hpp new file mode 100644 index 0000000000..2237dd4855 --- /dev/null +++ b/modules/dnn/src/op_cann.hpp @@ -0,0 +1,164 @@ +// 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_DNN_OP_CANN_HPP +#define OPENCV_DNN_OP_CANN_HPP + +#ifdef HAVE_CANN +#include "acl/acl.h" // acl* functions +#include "graph/graph.h" // ge::Graph; ge::Operator from operator.h +#include "graph/ge_error_codes.h" // GRAPH_SUCCESS, ... + +#include "op_proto/built-in/inc/all_ops.h" // ge::Conv2D, ... +#include "graph/tensor.h" // ge::Shape, ge::Tensor, ge::TensorDesc +#include "graph/types.h" // DT_FLOAT, ... ; FORMAT_NCHW, ... + +#include "ge/ge_api_types.h" // ge::ir_option::SOC_VERSION +#include "ge/ge_ir_build.h" // build graph + +// for fork() +#include +#include +#include +#include +#include + +#endif // HAVE_CANN + +#include + +#ifdef HAVE_CANN +#define ACL_CHECK_RET(f) \ +{ \ + if (f != ACL_SUCCESS) \ + { \ + CV_LOG_ERROR(NULL, "CANN check failed, ret = " << f); \ + CV_Error(Error::StsError, "CANN check failed"); \ + } \ +} +#define ACL_CHECK_GRAPH_RET(f) \ +{ \ + if (f != ge::GRAPH_SUCCESS) \ + { \ + CV_LOG_ERROR(NULL, "CANN graph check failed, ret = " << f); \ + CV_Error(Error::StsError, "CANN graph check failed"); \ + } \ +} + +#endif + +namespace cv { namespace dnn { + +#ifdef HAVE_CANN + +CV__DNN_INLINE_NS_BEGIN + +void switchToCannBackend(Net& net); + +CV__DNN_INLINE_NS_END + + class CannNet; + + class AclEnvGuard { + public: + explicit AclEnvGuard(); + ~AclEnvGuard(); + static std::shared_ptr GetAclEnv(); + + private: + static std::shared_ptr global_acl_env_; + static std::mutex global_acl_env_mutex_; + }; + + class CannConstOp + { + public: + CannConstOp(const uint8_t* data, const int dtype, const std::vector& shape, const std::string& name); + std::shared_ptr getOp() { return op_; } + std::shared_ptr getTensorDesc() { return desc_; } + private: + std::shared_ptr op_; + std::shared_ptr desc_; + }; + + class CannBackendNode : public BackendNode + { + public: + CannBackendNode(const std::shared_ptr& op); + std::shared_ptr getOp(); + std::shared_ptr net; + private: + std::shared_ptr op_; + }; + + class CannBackendWrapper : public BackendWrapper + { + public: + CannBackendWrapper(const Mat& m); + ~CannBackendWrapper() { } + + std::shared_ptr getTensorDesc() { return desc_; } + + virtual void copyToHost() CV_OVERRIDE; + + virtual void setHostDirty() CV_OVERRIDE; + + Mat* host; + std::shared_ptr desc_; + }; + + class CannNet + { + public: + explicit CannNet(int deviceId = 0) + : device_id(deviceId) + { + init(); + acl_env = AclEnvGuard::GetAclEnv(); + } + ~CannNet(); // release private members + + bool empty() const; + + void loadModelBuffer(std::shared_ptr modelBuffer); + + void bindInputWrappers(const std::vector>& inputWrappers); + void bindOutputWrappers(const std::vector>& outputWrappers); + + void forward(); + + size_t getInputNum() const; + size_t getOutputNum() const; + + private: + void init(); + + void loadToDevice(); // call aclInit before this API is called + void createInputDataset(); + void createOutputDataset(); + + int getOutputIndexByName(const std::string& name); + + void destroyDataset(aclmdlDataset** dataset); + + std::shared_ptr acl_env; + + std::vector> input_wrappers; + std::vector> output_wrappers; + + uint32_t model_id{0}; + aclmdlDesc* model_desc{nullptr}; + std::vector model; + aclmdlDataset* inputs{nullptr}; + aclmdlDataset* outputs{nullptr}; + + int device_id{0}; + aclrtContext context{nullptr}; + }; + +#endif // HAVE_CANN + +}} // namespace cv::dnn + +#endif // OPENCV_DNN_OP_CANN_HPP diff --git a/modules/dnn/src/op_cuda.cpp b/modules/dnn/src/op_cuda.cpp index a1b588ecfb..46e68f7689 100644 --- a/modules/dnn/src/op_cuda.cpp +++ b/modules/dnn/src/op_cuda.cpp @@ -86,8 +86,11 @@ void Net::Impl::initCUDABackend(const std::vector& blobsToKeep_) auto node = layerInstance->initCUDA(&context, ld.inputBlobsWrappers, ld.outputBlobsWrappers); ld.backendNodes[DNN_BACKEND_CUDA] = node; - auto cudaNode = node.dynamicCast(); - cudaInfo->workspace.require(cudaNode->get_workspace_memory_in_bytes()); + if(!node.empty()) + { + auto cudaNode = node.dynamicCast(); + cudaInfo->workspace.require(cudaNode->get_workspace_memory_in_bytes()); + } } if (blobsToKeep_.size() > 1) diff --git a/modules/dnn/src/op_inf_engine.cpp b/modules/dnn/src/op_inf_engine.cpp index 8a27dc2221..f9e3993d20 100644 --- a/modules/dnn/src/op_inf_engine.cpp +++ b/modules/dnn/src/op_inf_engine.cpp @@ -11,7 +11,11 @@ #ifdef HAVE_INF_ENGINE #include -#endif // HAVE_INF_ENGINE +#elif defined(ENABLE_PLUGINS) +// using plugin API +#include "backend.hpp" +#include "factory.hpp" +#endif #include #include @@ -35,6 +39,86 @@ cv::String setInferenceEngineBackendType(const cv::String& newBackendType) CV__DNN_INLINE_NS_END +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) +namespace InferenceEngine { + +CNNNetwork::CNNNetwork() {} + +CNNNetwork::CNNNetwork(std::shared_ptr model) : model(model) {} + +std::shared_ptr CNNNetwork::getFunction() const { + return model; +} + +void CNNNetwork::serialize(const std::string& xmlPath, const std::string& binPath) { + ov::pass::Serialize(xmlPath, binPath).run_on_model(model); +} + +void CNNNetwork::reshape(const std::map >& shapes) { + std::map partialShapes; + for (const auto& it : shapes) { + ov::PartialShape shape; + shape.insert(shape.begin(), it.second.begin(), it.second.end()); + partialShapes.insert({it.first, shape}); + } + model->reshape(partialShapes); +} + +std::vector Core::GetAvailableDevices() { + return get_available_devices(); +} + +void Core::UnregisterPlugin(const std::string& id) { + unload_plugin(id); +} + +CNNNetwork Core::ReadNetwork(const std::string& xmlPath, const std::string& binPath) { + return read_model(xmlPath, binPath); +} + +ExecutableNetwork Core::LoadNetwork(CNNNetwork net, const std::string& device, + const std::map& config) { + ov::AnyMap props; + for (const auto& it : config) { + props.insert(it); + } + return compile_model(net.getFunction(), device, props); +} + +ExecutableNetwork::ExecutableNetwork() {} + +ExecutableNetwork::ExecutableNetwork(const ov::CompiledModel& copy) : CompiledModel(copy) {} + +ov::InferRequest ExecutableNetwork::CreateInferRequest() { return create_infer_request(); } + +} // namespace InferenceEngine + +Mat infEngineBlobToMat(const ov::Tensor& blob) +{ + std::vector dims = blob.get_shape(); + std::vector size(dims.begin(), dims.end()); + auto precision = blob.get_element_type(); + + int type = -1; + switch (precision) + { + case ov::element::f32: type = CV_32F; break; + case ov::element::u8: type = CV_8U; break; + default: + CV_Error(Error::StsNotImplemented, "Unsupported blob precision"); + } + return Mat(size, type, blob.data()); +} + +void infEngineBlobsToMats(const ov::TensorVector& blobs, + std::vector& mats) +{ + mats.resize(blobs.size()); + for (int i = 0; i < blobs.size(); ++i) + mats[i] = infEngineBlobToMat(blobs[i]); +} + +#else Mat infEngineBlobToMat(const InferenceEngine::Blob::Ptr& blob) { @@ -61,7 +145,7 @@ void infEngineBlobsToMats(const std::vector& blobs, for (int i = 0; i < blobs.size(); ++i) mats[i] = infEngineBlobToMat(blobs[i]); } - +#endif // OpenVINO >= 2022.1 static bool init_IE_plugins() { @@ -126,7 +210,11 @@ static bool detectArmPlugin_() { if (i->find("CPU") != std::string::npos) { +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + const std::string name = ie.get_property(*i, ov::device::full_name); +#else const std::string name = ie.GetMetric(*i, METRIC_KEY(FULL_DEVICE_NAME)).as(); +#endif CV_LOG_INFO(NULL, "CPU plugin: " << name); return name.find("arm_compute::NEON") != std::string::npos; } @@ -146,7 +234,11 @@ static bool detectMyriadX_(const std::string& device) { if (i->find(device) != std::string::npos) { +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) + const std::string name = ie.get_property(*i, ov::device::full_name); +#else const std::string name = ie.GetMetric(*i, METRIC_KEY(FULL_DEVICE_NAME)).as(); +#endif CV_LOG_INFO(NULL, "Myriad device: " << name); return name.find("MyriadX") != std::string::npos || name.find("Myriad X") != std::string::npos || name.find("HDDL") != std::string::npos; } @@ -155,7 +247,6 @@ static bool detectMyriadX_(const std::string& device) } #endif // !defined(OPENCV_DNN_IE_VPU_TYPE_DEFAULT) - #endif // HAVE_INF_ENGINE @@ -281,24 +372,100 @@ bool checkTarget(Target target) #else // HAVE_INF_ENGINE + +namespace openvino { + +bool checkTarget(Target target) +{ +#if defined(ENABLE_PLUGINS) + try + { + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + return networkBackend.checkTarget(target); + } + catch (const std::exception& e) + { + CV_LOG_INFO(NULL, "DNN/OpenVINO: checkTarget failed: " << e.what()) + } +#endif + return false; +} + +} // namespace openvino + + cv::String getInferenceEngineBackendType() { +#if defined(ENABLE_PLUGINS) + try + { + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + CV_UNUSED(networkBackend); + return CV_DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + } + catch (const std::exception& e) + { + CV_LOG_INFO(NULL, "DNN/OpenVINO: plugin is not available: " << e.what()) + } +#endif CV_Error(Error::StsNotImplemented, "This OpenCV build doesn't include InferenceEngine support"); } cv::String setInferenceEngineBackendType(const cv::String& newBackendType) { +#if defined(ENABLE_PLUGINS) + try + { + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + CV_UNUSED(networkBackend); + CV_Assert(newBackendType == CV_DNN_BACKEND_INFERENCE_ENGINE_NGRAPH); + } + catch (const std::exception& e) + { + CV_LOG_INFO(NULL, "DNN/OpenVINO: plugin is not available: " << e.what()) + } +#endif CV_UNUSED(newBackendType); CV_Error(Error::StsNotImplemented, "This OpenCV build doesn't include InferenceEngine support"); } cv::String getInferenceEngineVPUType() { +#if defined(ENABLE_PLUGINS) + try + { + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + if (networkBackend.checkTarget(DNN_TARGET_MYRIAD)) + return CV_DNN_INFERENCE_ENGINE_VPU_TYPE_MYRIAD_X; // 2021.4 supports NCS2 only + CV_Error(Error::StsError, "DNN/OpenVINO: DNN_TARGET_MYRIAD is not available"); + } + catch (const std::exception& e) + { + CV_LOG_INFO(NULL, "DNN/OpenVINO: plugin is not available: " << e.what()) + } +#endif CV_Error(Error::StsNotImplemented, "This OpenCV build doesn't include InferenceEngine support"); } cv::String getInferenceEngineCPUType() { +#if defined(ENABLE_PLUGINS) + try + { + auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); + CV_UNUSED(networkBackend); +#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM64) + return CV_DNN_INFERENCE_ENGINE_CPU_TYPE_ARM_COMPUTE; +#else + return CV_DNN_INFERENCE_ENGINE_CPU_TYPE_X86; +#endif + } + catch (const std::exception& e) + { + CV_LOG_INFO(NULL, "DNN/OpenVINO: plugin is not available: " << e.what()) + } +#endif CV_Error(Error::StsNotImplemented, "This OpenCV build doesn't include InferenceEngine support"); } + #endif // HAVE_INF_ENGINE diff --git a/modules/dnn/src/op_inf_engine.hpp b/modules/dnn/src/op_inf_engine.hpp index 856441e71d..45913d3b31 100644 --- a/modules/dnn/src/op_inf_engine.hpp +++ b/modules/dnn/src/op_inf_engine.hpp @@ -19,11 +19,6 @@ #ifdef HAVE_INF_ENGINE -#define INF_ENGINE_RELEASE_2018R5 2018050000 -#define INF_ENGINE_RELEASE_2019R1 2019010000 -#define INF_ENGINE_RELEASE_2019R2 2019020000 -#define INF_ENGINE_RELEASE_2019R3 2019030000 -#define INF_ENGINE_RELEASE_2020_1 2020010000 #define INF_ENGINE_RELEASE_2020_2 2020020000 #define INF_ENGINE_RELEASE_2020_3 2020030000 #define INF_ENGINE_RELEASE_2020_4 2020040000 @@ -31,6 +26,7 @@ #define INF_ENGINE_RELEASE_2021_2 2021020000 #define INF_ENGINE_RELEASE_2021_3 2021030000 #define INF_ENGINE_RELEASE_2021_4 2021040000 +#define INF_ENGINE_RELEASE_2022_1 2022010000 #ifndef INF_ENGINE_RELEASE #warning("IE version have not been provided via command-line. Using 2021.4 by default") @@ -48,7 +44,13 @@ #pragma GCC diagnostic ignored "-Wsuggest-override" #endif +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) +#include +#include +#include +#else #include +#endif #if defined(__GNUC__) && __GNUC__ >= 5 //#pragma GCC diagnostic pop @@ -60,25 +62,35 @@ namespace cv { namespace dnn { -#ifdef HAVE_INF_ENGINE - -Backend& getInferenceEngineBackendTypeParam(); - -Mat infEngineBlobToMat(const InferenceEngine::Blob::Ptr& blob); - -void infEngineBlobsToMats(const std::vector& blobs, - std::vector& mats); - - - CV__DNN_INLINE_NS_BEGIN - namespace openvino { // TODO: use std::string as parameter bool checkTarget(Target target); } // namespace openvino +CV__DNN_INLINE_NS_END + +#ifdef HAVE_INF_ENGINE + +Backend& getInferenceEngineBackendTypeParam(); + +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) +Mat infEngineBlobToMat(const ov::Tensor& blob); + +void infEngineBlobsToMats(const ov::TensorVector& blobs, + std::vector& mats); +#else +Mat infEngineBlobToMat(const InferenceEngine::Blob::Ptr& blob); + +void infEngineBlobsToMats(const std::vector& blobs, + std::vector& mats); +#endif // OpenVINO >= 2022.1 + + +CV__DNN_INLINE_NS_BEGIN + +void switchToOpenVINOBackend(Net& net); bool isMyriadX(); @@ -86,6 +98,52 @@ bool isArmComputePlugin(); CV__DNN_INLINE_NS_END +// A series of wrappers for classes from OpenVINO API 2.0. +// Need just for less conditional compilation inserts. +#if INF_ENGINE_VER_MAJOR_GE(INF_ENGINE_RELEASE_2022_1) +namespace InferenceEngine { + +class CNNNetwork { +public: + CNNNetwork(); + + CNNNetwork(std::shared_ptr model); + + std::shared_ptr getFunction() const; + + void serialize(const std::string& xmlPath, const std::string& binPath); + + void reshape(const std::map >& shapes); + +private: + std::shared_ptr model = nullptr; +}; + +typedef ov::InferRequest InferRequest; + +class ExecutableNetwork : public ov::CompiledModel { +public: + ExecutableNetwork(); + + ExecutableNetwork(const ov::CompiledModel& copy); + + ov::InferRequest CreateInferRequest(); +}; + +class Core : public ov::Core { +public: + std::vector GetAvailableDevices(); + + void UnregisterPlugin(const std::string& id); + + CNNNetwork ReadNetwork(const std::string& xmlPath, const std::string& binPath); + + ExecutableNetwork LoadNetwork(CNNNetwork net, const std::string& device, + const std::map& config); +}; + +} +#endif // OpenVINO >= 2022.1 InferenceEngine::Core& getCore(const std::string& id); diff --git a/modules/dnn/src/plugin_api.hpp b/modules/dnn/src/plugin_api.hpp new file mode 100644 index 0000000000..83f4189df2 --- /dev/null +++ b/modules/dnn/src/plugin_api.hpp @@ -0,0 +1,72 @@ +// 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 DNN_PLUGIN_API_HPP +#define DNN_PLUGIN_API_HPP + +#include +#include + +#include "backend.hpp" + +#if !defined(BUILD_PLUGIN) + +/// 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 API_VERSION 0 // preview + +/// increased for incompatible changes, e.g. remove function argument +/// Caller ABI == Plugin ABI -> plugin is compatible +/// Caller ABI > Plugin ABI -> plugin is not compatible, caller should use shim code to use old ABI plugins (caller may know how lower ABI works, so it is possible) +/// Caller ABI < Plugin ABI -> plugin can't be used (plugin should provide interface with lower ABI to handle that) +#define ABI_VERSION 0 // preview + +#else // !defined(BUILD_PLUGIN) + +#if !defined(ABI_VERSION) || !defined(API_VERSION) +#error "Plugin must define ABI_VERSION and API_VERSION before including plugin_api.hpp" +#endif + +#endif // !defined(BUILD_PLUGIN) + +typedef cv::dnn_backend::NetworkBackend* CvPluginDNNNetworkBackend; + +struct OpenCV_DNN_Plugin_API_v0_0_api_entries +{ + /** @brief Get backend API instance + + @param[out] handle pointer on inference backend API handle + + @note API-CALL 1, API-Version == 0 + */ + CvResult (CV_API_CALL *getInstance)(CV_OUT CvPluginDNNNetworkBackend* handle) CV_NOEXCEPT; +}; // OpenCV_DNN_Plugin_API_v0_0_api_entries + +typedef struct OpenCV_DNN_Plugin_API_v0 +{ + OpenCV_API_Header api_header; + struct OpenCV_DNN_Plugin_API_v0_0_api_entries v0; +} OpenCV_DNN_Plugin_API_v0; + +#if ABI_VERSION == 0 && API_VERSION == 0 +typedef OpenCV_DNN_Plugin_API_v0 OpenCV_DNN_Plugin_API; +#else +#error "Not supported configuration: check ABI_VERSION/API_VERSION" +#endif + +#ifdef BUILD_PLUGIN +extern "C" { + +CV_PLUGIN_EXPORTS +const OpenCV_DNN_Plugin_API* CV_API_CALL opencv_dnn_plugin_init_v0 + (int requested_abi_version, int requested_api_version, void* reserved /*NULL*/) CV_NOEXCEPT; + +} // extern "C" +#else // BUILD_PLUGIN +typedef const OpenCV_DNN_Plugin_API* (CV_API_CALL *FN_opencv_dnn_plugin_init_t) + (int requested_abi_version, int requested_api_version, void* reserved /*NULL*/); +#endif // BUILD_PLUGIN + +#endif // DNN_PLUGIN_API_HPP diff --git a/modules/dnn/src/plugin_wrapper.impl.hpp b/modules/dnn/src/plugin_wrapper.impl.hpp new file mode 100644 index 0000000000..94b2e4d219 --- /dev/null +++ b/modules/dnn/src/plugin_wrapper.impl.hpp @@ -0,0 +1,319 @@ +// 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. + +// +// Not a standalone header, part of backend.cpp +// + +//================================================================================================== +// Dynamic backend implementation + +#include "opencv2/core/utils/plugin_loader.private.hpp" + +namespace cv { namespace impl { + +using namespace cv::dnn_backend; + +#if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) + +using namespace cv::plugin::impl; // plugin_loader.hpp + +class PluginDNNBackend CV_FINAL: public std::enable_shared_from_this +{ +protected: + void initPluginAPI() + { + const char* init_name = "opencv_dnn_plugin_init_v0"; + FN_opencv_dnn_plugin_init_t fn_init = reinterpret_cast(lib_->getSymbol(init_name)); + if (fn_init) + { + CV_LOG_DEBUG(NULL, "Found entry: '" << init_name << "'"); + for (int supported_api_version = API_VERSION; supported_api_version >= 0; supported_api_version--) + { + plugin_api_ = fn_init(ABI_VERSION, supported_api_version, NULL); + if (plugin_api_) + break; + } + if (!plugin_api_) + { + CV_LOG_INFO(NULL, "DNN: plugin is incompatible (can't be initialized): " << lib_->getName()); + return; + } + // NB: force strict minor version check (ABI is not preserved for now) + if (!checkCompatibility(plugin_api_->api_header, ABI_VERSION, API_VERSION, true)) + { + plugin_api_ = NULL; + return; + } + CV_LOG_INFO(NULL, "DNN: plugin is ready to use '" << plugin_api_->api_header.api_description << "'"); + } + else + { + CV_LOG_INFO(NULL, "DNN: plugin is incompatible, missing init function: '" << init_name << "', file: " << lib_->getName()); + } + } + + + bool checkCompatibility(const OpenCV_API_Header& api_header, unsigned int abi_version, unsigned int api_version, bool checkMinorOpenCVVersion) + { + if (api_header.opencv_version_major != CV_VERSION_MAJOR) + { + CV_LOG_ERROR(NULL, "DNN: wrong OpenCV major version used by plugin '" << api_header.api_description << "': " << + cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor)) + return false; + } + if (!checkMinorOpenCVVersion) + { + // no checks for OpenCV minor version + } + else if (api_header.opencv_version_minor != CV_VERSION_MINOR) + { + CV_LOG_ERROR(NULL, "DNN: wrong OpenCV minor version used by plugin '" << api_header.api_description << "': " << + cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor)) + return false; + } + CV_LOG_DEBUG(NULL, "DNN: initialized '" << api_header.api_description << "': built with " + << cv::format("OpenCV %d.%d (ABI/API = %d/%d)", + api_header.opencv_version_major, api_header.opencv_version_minor, + api_header.min_api_version, api_header.api_version) + << ", current OpenCV version is '" CV_VERSION "' (ABI/API = " << abi_version << "/" << api_version << ")" + ); + if (api_header.min_api_version != abi_version) // future: range can be here + { + // actually this should never happen due to checks in plugin's init() function + CV_LOG_ERROR(NULL, "DNN: plugin is not supported due to incompatible ABI = " << api_header.min_api_version); + return false; + } + if (api_header.api_version != api_version) + { + CV_LOG_INFO(NULL, "DNN: NOTE: plugin is supported, but there is API version mismath: " + << cv::format("plugin API level (%d) != OpenCV API level (%d)", api_header.api_version, api_version)); + if (api_header.api_version < api_version) + { + CV_LOG_INFO(NULL, "DNN: NOTE: some functionality may be unavailable due to lack of support by plugin implementation"); + } + } + return true; + } + +public: + std::shared_ptr lib_; + const OpenCV_DNN_Plugin_API* plugin_api_; + + PluginDNNBackend(const std::shared_ptr& lib) + : lib_(lib) + , plugin_api_(NULL) + { + initPluginAPI(); + } + + std::shared_ptr createNetworkBackend() const + { + CV_Assert(plugin_api_); + + CvPluginDNNNetworkBackend instancePtr = NULL; + + if (plugin_api_->v0.getInstance) + { + if (CV_ERROR_OK == plugin_api_->v0.getInstance(&instancePtr)) + { + CV_Assert(instancePtr); + // TODO C++20 "aliasing constructor" + return std::shared_ptr(instancePtr, [](cv::dnn_backend::NetworkBackend*){}); // empty deleter + } + } + return std::shared_ptr(); + } + +}; // class PluginDNNBackend + + +class PluginDNNBackendFactory CV_FINAL: public IDNNBackendFactory +{ +public: + std::string baseName_; + std::shared_ptr backend; + bool initialized; +public: + PluginDNNBackendFactory(const std::string& baseName) + : baseName_(baseName) + , initialized(false) + { + // nothing, plugins are loaded on demand + } + + std::shared_ptr createNetworkBackend() const CV_OVERRIDE + { + if (!initialized) + { + const_cast(this)->initBackend(); + } + if (backend) + return backend->createNetworkBackend(); + return std::shared_ptr(); + } + +protected: + void initBackend() + { + AutoLock lock(getInitializationMutex()); + try + { + if (!initialized) + loadPlugin(); + } + catch (...) + { + CV_LOG_INFO(NULL, "DNN: exception during plugin loading: " << baseName_ << ". SKIP"); + } + initialized = true; + } + void loadPlugin(); +}; + +static +std::vector getPluginCandidates(const std::string& baseName) +{ + using namespace cv::utils; + using namespace cv::utils::fs; + const std::string baseName_l = toLowerCase(baseName); + const std::string baseName_u = toUpperCase(baseName); + const FileSystemPath_t baseName_l_fs = toFileSystemPath(baseName_l); + std::vector paths; + // TODO OPENCV_PLUGIN_PATH + const std::vector paths_ = getConfigurationParameterPaths("OPENCV_DNN_PLUGIN_PATH", std::vector()); + if (paths_.size() != 0) + { + for (size_t i = 0; i < paths_.size(); i++) + { + paths.push_back(toFileSystemPath(paths_[i])); + } + } + else + { + FileSystemPath_t binaryLocation; + if (getBinLocation(binaryLocation)) + { + binaryLocation = getParent(binaryLocation); +#ifndef CV_DNN_PLUGIN_SUBDIRECTORY + paths.push_back(binaryLocation); +#else + paths.push_back(binaryLocation + toFileSystemPath("/") + toFileSystemPath(CV_DNN_PLUGIN_SUBDIRECTORY_STR)); +#endif + } + } + const std::string default_expr = libraryPrefix() + "opencv_dnn_" + baseName_l + "*" + librarySuffix(); + const std::string plugin_expr = getConfigurationParameterString((std::string("OPENCV_DNN_PLUGIN_") + baseName_u).c_str(), default_expr.c_str()); + std::vector results; +#ifdef _WIN32 + FileSystemPath_t moduleName = toFileSystemPath(libraryPrefix() + "opencv_dnn_" + baseName_l + librarySuffix()); + if (plugin_expr != default_expr) + { + moduleName = toFileSystemPath(plugin_expr); + results.push_back(moduleName); + } + for (const FileSystemPath_t& path : paths) + { + results.push_back(path + L"\\" + moduleName); + } + results.push_back(moduleName); +#else + CV_LOG_DEBUG(NULL, "DNN: " << baseName << " plugin's glob is '" << plugin_expr << "', " << paths.size() << " location(s)"); + for (const std::string& path : paths) + { + if (path.empty()) + continue; + std::vector candidates; + cv::glob(utils::fs::join(path, plugin_expr), candidates); + // Prefer candidates with higher versions + // TODO: implemented accurate versions-based comparator + std::sort(candidates.begin(), candidates.end(), std::greater()); + CV_LOG_DEBUG(NULL, " - " << path << ": " << candidates.size()); + copy(candidates.begin(), candidates.end(), back_inserter(results)); + } +#endif + CV_LOG_DEBUG(NULL, "Found " << results.size() << " plugin(s) for " << baseName); + return results; +} + +void PluginDNNBackendFactory::loadPlugin() +{ + for (const FileSystemPath_t& plugin : getPluginCandidates(baseName_)) + { + auto lib = std::make_shared(plugin); + if (!lib->isLoaded()) + { + continue; + } + try + { + auto pluginBackend = std::make_shared(lib); + if (!pluginBackend) + { + continue; + } + if (pluginBackend->plugin_api_ == NULL) + { + CV_LOG_ERROR(NULL, "DNN: no compatible plugin API for backend: " << baseName_ << " in " << toPrintablePath(plugin)); + continue; + } + // NB: we are going to use backend, so prevent automatic library unloading + lib->disableAutomaticLibraryUnloading(); + backend = pluginBackend; + return; + } + catch (...) + { + CV_LOG_WARNING(NULL, "DNN: exception during plugin initialization: " << toPrintablePath(plugin) << ". SKIP"); + } + } +} + +#endif // OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) + +} // namespace + + + +namespace dnn_backend { + + +std::shared_ptr createPluginDNNBackendFactory(const std::string& baseName) +{ +#if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) + const std::string baseName_u = toUpperCase(baseName); + AutoLock lock(getInitializationMutex()); + static std::map> g_plugins_cache; + auto it = g_plugins_cache.find(baseName_u); + if (it == g_plugins_cache.end()) + { + auto factory = std::make_shared(baseName); + g_plugins_cache.insert(std::pair>(baseName_u, factory)); + return factory; + } + return it->second; +#else + CV_UNUSED(baseName); + return std::shared_ptr(); +#endif +} + + +cv::dnn_backend::NetworkBackend& createPluginDNNNetworkBackend(const std::string& baseName) +{ + auto factory = dnn_backend::createPluginDNNBackendFactory(baseName); + if (!factory) + { + CV_Error(Error::StsNotImplemented, cv::format("Plugin factory is not available: '%s'", baseName.c_str())); + } + auto backend = factory->createNetworkBackend(); + if (!backend) + { + CV_Error(Error::StsNotImplemented, cv::format("Backend (plugin) is not available: '%s'", baseName.c_str())); + } + return *backend; +} + + +}} // namespace diff --git a/modules/dnn/src/precomp.hpp b/modules/dnn/src/precomp.hpp index abcd3745f9..0100eb2c7f 100644 --- a/modules/dnn/src/precomp.hpp +++ b/modules/dnn/src/precomp.hpp @@ -39,8 +39,14 @@ // //M*/ -#include +#if !defined(BUILD_PLUGIN) #include "cvconfig.h" +#else +#include +#undef __OPENCV_BUILD // allow public API only +#endif + +#include #ifndef CV_OCL4DNN #define CV_OCL4DNN 0 diff --git a/modules/dnn/src/registry.cpp b/modules/dnn/src/registry.cpp index 697fca6015..f5c9e584c6 100644 --- a/modules/dnn/src/registry.cpp +++ b/modules/dnn/src/registry.cpp @@ -11,9 +11,12 @@ #include "op_cuda.hpp" #include "op_webnn.hpp" #include "op_timvx.hpp" +#include "op_cann.hpp" #include "halide_scheduler.hpp" +#include "backend.hpp" +#include "factory.hpp" namespace cv { namespace dnn { @@ -43,43 +46,46 @@ private: #endif #endif // HAVE_HALIDE + bool haveBackendOpenVINO = false; #ifdef HAVE_INF_ENGINE - if (openvino::checkTarget(DNN_TARGET_CPU)) + haveBackendOpenVINO = true; +#elif defined(ENABLE_PLUGINS) + { + auto factory = dnn_backend::createPluginDNNBackendFactory("openvino"); + if (factory) + { + auto backend = factory->createNetworkBackend(); + if (backend) + haveBackendOpenVINO = true; + } + } +#endif + + if (haveBackendOpenVINO && openvino::checkTarget(DNN_TARGET_CPU)) { -#ifdef HAVE_DNN_NGRAPH backends.push_back(std::make_pair(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, DNN_TARGET_CPU)); -#endif } - if (openvino::checkTarget(DNN_TARGET_MYRIAD)) + if (haveBackendOpenVINO && openvino::checkTarget(DNN_TARGET_MYRIAD)) { -#ifdef HAVE_DNN_NGRAPH backends.push_back(std::make_pair(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, DNN_TARGET_MYRIAD)); -#endif } - if (openvino::checkTarget(DNN_TARGET_HDDL)) + if (haveBackendOpenVINO && openvino::checkTarget(DNN_TARGET_HDDL)) { -#ifdef HAVE_DNN_NGRAPH backends.push_back(std::make_pair(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, DNN_TARGET_HDDL)); -#endif } #ifdef HAVE_OPENCL if (cv::ocl::useOpenCL() && ocl::Device::getDefault().isIntel()) { - if (openvino::checkTarget(DNN_TARGET_OPENCL)) + if (haveBackendOpenVINO && openvino::checkTarget(DNN_TARGET_OPENCL)) { -#ifdef HAVE_DNN_NGRAPH backends.push_back(std::make_pair(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, DNN_TARGET_OPENCL)); -#endif } - if (openvino::checkTarget(DNN_TARGET_OPENCL_FP16)) + if (haveBackendOpenVINO && openvino::checkTarget(DNN_TARGET_OPENCL_FP16)) { -#ifdef HAVE_DNN_NGRAPH backends.push_back(std::make_pair(DNN_BACKEND_INFERENCE_ENGINE_NGRAPH, DNN_TARGET_OPENCL_FP16)); -#endif } } -#endif -#endif // HAVE_INF_ENGINE +#endif // HAVE_OPENCL #ifdef HAVE_WEBNN if (haveWebnn()) @@ -117,6 +123,10 @@ private: backends.push_back(std::make_pair(DNN_BACKEND_TIMVX, DNN_TARGET_NPU)); } #endif + +#ifdef HAVE_CANN + backends.push_back(std::make_pair(DNN_BACKEND_CANN, DNN_TARGET_NPU)); +#endif } BackendsList backends; @@ -132,10 +142,9 @@ std::vector getAvailableTargets(Backend be) { if (be == DNN_BACKEND_DEFAULT) be = (Backend)getParam_DNN_BACKEND_DEFAULT(); -#ifdef HAVE_INF_ENGINE + if (be == DNN_BACKEND_INFERENCE_ENGINE) be = DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; -#endif std::vector result; const BackendRegistry::BackendsList all_backends = getAvailableBackends(); diff --git a/modules/dnn/src/tengine4dnn/include/tengine_graph_convolution.hpp b/modules/dnn/src/tengine4dnn/include/tengine_graph_convolution.hpp index c6b0495ab5..8ec99c9685 100644 --- a/modules/dnn/src/tengine4dnn/include/tengine_graph_convolution.hpp +++ b/modules/dnn/src/tengine4dnn/include/tengine_graph_convolution.hpp @@ -34,11 +34,15 @@ namespace cv { namespace dnn { +// pad_h0: pad_top +// pad_h1: pad_bottom +// pad_w0: pad_left +// pad_w1: pad_right teng_graph_t tengine_init(const char* name , float* input_, int inch, int group, int in_h, int in_w, float *output_, int out_b, int outch, int out_h, int out_w, float *kernel_,int kernel_s , int kernel_h, int kernel_w, - float *teg_bias, int stride_h,int stride_w, - int pad_h, int pad_w, int dilation_h, int dilation_w, + float *teg_bias, int stride_h, int stride_w, + int pad_h0, int pad_h1, int pad_w0, int pad_w1, int dilation_h, int dilation_w, size_t wstep, const std::string padMode , teng_graph_t& graph, int nstripes) ; bool tengine_forward(teng_graph_t& graph) ; diff --git a/modules/dnn/src/tengine4dnn/src/tengine_graph_convolution.cpp b/modules/dnn/src/tengine4dnn/src/tengine_graph_convolution.cpp index daadc32ad2..d35937006c 100644 --- a/modules/dnn/src/tengine4dnn/src/tengine_graph_convolution.cpp +++ b/modules/dnn/src/tengine4dnn/src/tengine_graph_convolution.cpp @@ -56,7 +56,7 @@ static int create_input_node(teng_graph_t graph, const char* node_name, int inch } static int create_conv_node(teng_graph_t graph, const char* node_name, const char* input_name, int in_h, int in_w, int out_h, int out_w, - int kernel_h, int kernel_w, int stride_h, int stride_w, int pad_h, int pad_w, int inch, int outch, int group, + int kernel_h, int kernel_w, int stride_h, int stride_w, int pad_h0, int pad_h1, int pad_w0, int pad_w1, int inch, int outch, int group, int dilation_h, int dilation_w, int activation, std::string padMode) { node_t conv_node = teng_create_graph_node(graph, node_name, "Convolution"); @@ -107,15 +107,12 @@ static int create_conv_node(teng_graph_t graph, const char* node_name, const cha teng_release_graph_node(b_node); teng_release_graph_tensor(b_tensor); - int pad_h1 = pad_h; - int pad_w1 = pad_w; - if (!padMode.empty()) { if (padMode == "SAME") { - int out_h_temp = (in_h-kernel_h + 2*pad_h)/stride_h + 1; - int out_w_temp = (in_w-kernel_w + 2*pad_w)/stride_w + 1; + int out_h_temp = (in_h-kernel_h + 2*pad_h0)/stride_h + 1; + int out_w_temp = (in_w-kernel_w + 2*pad_w0)/stride_w + 1; if (out_h_temp < out_h) pad_h1 += 1; @@ -129,8 +126,8 @@ static int create_conv_node(teng_graph_t graph, const char* node_name, const cha teng_set_node_attr_int(conv_node, "kernel_w", &kernel_w); teng_set_node_attr_int(conv_node, "stride_h", &stride_h); teng_set_node_attr_int(conv_node, "stride_w", &stride_w); - teng_set_node_attr_int(conv_node, "pad_h0", &pad_h); - teng_set_node_attr_int(conv_node, "pad_w0", &pad_w); + teng_set_node_attr_int(conv_node, "pad_h0", &pad_h0); + teng_set_node_attr_int(conv_node, "pad_w0", &pad_w0); teng_set_node_attr_int(conv_node, "pad_h1", &pad_h1); teng_set_node_attr_int(conv_node, "pad_w1", &pad_w1); teng_set_node_attr_int(conv_node, "output_channel", &outch); @@ -149,7 +146,7 @@ static teng_graph_t create_conv_graph(const char* layer_name, float* input_data, float* output_data, int outch, int out_h, int out_w, int kernel_h, int kernel_w, int stride_h,int stride_w, - int pad_h, int pad_w, int dilation_h, int dilation_w, int activation, + int pad_h0, int pad_h1, int pad_w0, int pad_w1, int dilation_h, int dilation_w, int activation, float* teg_weight, float* teg_bias, std::string padMode, int nstripes) { node_t conv_node = NULL; @@ -188,7 +185,7 @@ static teng_graph_t create_conv_graph(const char* layer_name, float* input_data, } if (ok && create_conv_node(graph, conv_name, input_name, in_h, in_w, out_h, out_w, kernel_h, kernel_w, - stride_h, stride_w, pad_h, pad_w, inch, outch, group, dilation_h, dilation_w, activation, padMode) < 0) + stride_h, stride_w, pad_h0, pad_h1, pad_w0, pad_w1, inch, outch, group, dilation_h, dilation_w, activation, padMode) < 0) { CV_LOG_WARNING(NULL,"Tengine: create conv node failed." ); ok = false; @@ -289,8 +286,8 @@ static bool tengine_init_flag = false; teng_graph_t tengine_init(const char* layer_name, float* input_, int inch, int group, int in_h, int in_w, float *output_, int out_b, int outch, int out_h, int out_w, float *kernel_, int kernel_s ,int kernel_h, int kernel_w, - float *teg_bias, int stride_h,int stride_w, - int pad_h, int pad_w, int dilation_h, int dilation_w, + float *teg_bias, int stride_h, int stride_w, + int pad_h0, int pad_h1, int pad_w0, int pad_w1, int dilation_h, int dilation_w, size_t wstep, const std::string padMode, teng_graph_t &graph, int nstripes) { std::vector teg_weight_vec; @@ -299,9 +296,9 @@ teng_graph_t tengine_init(const char* layer_name, float* input_, int inch, int g // Do not using the activation fuse mode, just convolution only. int activation = -1; - if (!(kernel_s == 2 && kernel_h == kernel_w && pad_h == pad_w + if (!(kernel_s == 2 && kernel_h == kernel_w && dilation_h == dilation_w && stride_h == stride_w - && out_b == 1 && pad_h < 10)) // just for Conv2D + && out_b == 1 && pad_h0 < 10 && pad_h1 < 10 && pad_w0 < 10 && pad_w1 < 10)) // just for Conv2D { // printf("return : just for Conv2D\n"); return NULL; @@ -314,7 +311,7 @@ teng_graph_t tengine_init(const char* layer_name, float* input_, int inch, int g kernel_w, kernel_h, stride_w, stride_h, dilation_w, dilation_h, - pad_w, pad_h); + pad_h0, pad_h1, pad_w0, pad_w1); */ // weight if (kernel_inwh != wstep) @@ -342,7 +339,7 @@ teng_graph_t tengine_init(const char* layer_name, float* input_, int inch, int g graph = create_conv_graph(layer_name, input_, inch, group, in_h, in_w, output_, outch, out_h, out_w, kernel_h, kernel_w, stride_h,stride_w, - pad_h, pad_w, dilation_h, dilation_w, activation, + pad_h0, pad_h1, pad_w0, pad_w1, dilation_h, dilation_w, activation, teg_weight, teg_bias, padMode, nstripes); if(NULL == graph ) { diff --git a/modules/dnn/src/tensorflow/tf_graph_simplifier.cpp b/modules/dnn/src/tensorflow/tf_graph_simplifier.cpp index 72f546ead9..25ef850417 100644 --- a/modules/dnn/src/tensorflow/tf_graph_simplifier.cpp +++ b/modules/dnn/src/tensorflow/tf_graph_simplifier.cpp @@ -829,12 +829,19 @@ void RemoveIdentityOps(tensorflow::GraphDef& net) IdentityOpsMap::iterator it = identity_ops.find(input_op_name); if (it != identity_ops.end()) { + std::set loopCheckSet; // In case of Identity after Identity while (true) { IdentityOpsMap::iterator nextIt = identity_ops.find(it->second); if (nextIt != identity_ops.end()) + { + // Loop check + if (loopCheckSet.find(it->second) != loopCheckSet.end()) + CV_Error(Error::StsError, "Found a loop in your input Tensorflow model, which is illegal!"); + loopCheckSet.insert(it->second); it = nextIt; + } else break; } diff --git a/modules/dnn/src/tensorflow/tf_importer.cpp b/modules/dnn/src/tensorflow/tf_importer.cpp index 80a8b6dfc5..44e70bac41 100644 --- a/modules/dnn/src/tensorflow/tf_importer.cpp +++ b/modules/dnn/src/tensorflow/tf_importer.cpp @@ -1097,6 +1097,9 @@ void TFImporter::parseReshape(tensorflow::GraphDef& net, const tensorflow::NodeD std::swap(*newShape.ptr(0, 1), *newShape.ptr(0, 2)); hasSwap = true; } + + bool changedType{false}; + if (inpLayout == DATA_LAYOUT_NHWC) { if (newShapeSize >= 2 || newShape.at(1) == 1) @@ -1110,23 +1113,28 @@ void TFImporter::parseReshape(tensorflow::GraphDef& net, const tensorflow::NodeD else { inpLayout = DATA_LAYOUT_NHWC; + changedType = newShapeSize == 4 && !hasSwap; } } } layerParams.set("dim", DictValue::arrayInt(newShape.ptr(), newShapeSize)); - int id = dstNet.addLayer(name, "Reshape", layerParams); - layer_id[name] = id; + std::string setName = changedType ? name + "/realReshape" : name; + + int id = dstNet.addLayer(setName, "Reshape", layerParams); + layer_id[setName] = id; // one input only connect(layer_id, dstNet, inpId, id, 0); - inpId = Pin(name); + inpId = Pin(setName); if ((inpLayout == DATA_LAYOUT_NHWC || inpLayout == DATA_LAYOUT_UNKNOWN || inpLayout == DATA_LAYOUT_PLANAR) && newShapeSize == 4 && !hasSwap) { int order[] = {0, 3, 1, 2}; // Transform back to OpenCV's NCHW. - addPermuteLayer(order, name + "/nchw", inpId); + + setName = changedType ? name : name + "/nchw"; + addPermuteLayer(order, setName, inpId); inpLayout = DATA_LAYOUT_NCHW; } @@ -1698,6 +1706,19 @@ void TFImporter::parseStridedSlice(tensorflow::GraphDef& net, const tensorflow:: layerParams.set("begin", DictValue::arrayInt((int*)begins.data, begins.total())); layerParams.set("end", DictValue::arrayInt((int*)ends.data, ends.total())); + Pin inp = parsePin(layer.input(0)); + if (value_id.find(inp.name) != value_id.end()) + { + // The input is constant. + LayerParams lp; + lp.name = inp.name; + lp.type = "Const"; + lp.blobs.push_back(getTensorContent(getConstBlob(layer, value_id, 0))); + + int constInpId = dstNet.addLayer(lp.name, lp.type, lp); + layer_id[lp.name] = constInpId; + } + int id = dstNet.addLayer(name, "Slice", layerParams); layer_id[name] = id; diff --git a/modules/dnn/src/vkcom/shader/conv48_nobias.comp b/modules/dnn/src/vkcom/shader/conv48_nobias.comp new file mode 100644 index 0000000000..cb26fc716f --- /dev/null +++ b/modules/dnn/src/vkcom/shader/conv48_nobias.comp @@ -0,0 +1,134 @@ +#version 450 + +layout (constant_id = 0) const int LOCAL_SZ_X = 0; +layout (constant_id = 1) const int LOCAL_SZ_Y = 0; +layout (constant_id = 2) const int LOCAL_SZ_Z = 0; +layout (constant_id = 3) const int IN_H = 0; +layout (constant_id = 4) const int IN_W = 0; +layout (constant_id = 5) const int OUT_W = 0; +layout (constant_id = 6) const int STRIDE_H = 0; +layout (constant_id = 7) const int STRIDE_W = 0; +layout (constant_id = 8) const int PAD_H = 0; +layout (constant_id = 9) const int PAD_W = 0; +layout (constant_id = 10) const int FILTER_H = 0; +layout (constant_id = 11) const int FILTER_W = 0; +layout (constant_id = 12) const int CHANNELS = 0; +layout (constant_id = 13) const int BATCH = 0; +layout (constant_id = 14) const int M = 0; +layout (constant_id = 15) const int K = 0; +layout (constant_id = 16) const int N = 0; +layout (constant_id = 17) const int TAIL_M = 0; +layout (constant_id = 18) const int DILATION_H = 0; +layout (constant_id = 19) const int DILATION_W = 0; + +#if defined(ACTIVATION_RELU) +#define ACTIVATION_FUNCTION(x) clamp(x, vec4(0.0), vec4(999999999.0)) +#elif defined(ACTIVATION_RELU1) +#define ACTIVATION_FUNCTION(x) clamp(x, vec4(-1.0), vec4(1.0)) +#elif defined(ACTIVATION_RELU6) +#define ACTIVATION_FUNCTION(x) clamp(x, vec4(0.0), vec4(6.0)) +#else +#define ACTIVATION_FUNCTION(x) (x) +#endif + +layout(binding = 0) readonly buffer Input0{ + float data[]; +} src0; +layout(binding = 1) readonly buffer Input1 { + vec4 data[]; +} bias; +layout(binding = 2) readonly buffer Input3{ + vec4 data[]; +} src1; +layout(binding = 3) writeonly buffer Output{ + vec4 data[]; +} out0; + +layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in; + +#define VEC_SIZE 4 +#define BLOCK_H 4 +#define BLOCK_W 8 +#define FILTER_AREA (FILTER_H * FILTER_W) +#define LOAD_A(elm_idx, a_component) \ + src0_x = org_x + ((i * VEC_SIZE + elm_idx) % FILTER_W) * DILATION_W; \ + src0_y = org_y + (((i * VEC_SIZE + elm_idx) % FILTER_AREA) / FILTER_W) * DILATION_H; \ + src0_z = (i * VEC_SIZE + elm_idx) / FILTER_AREA; \ + if(src0_y >= 0 && src0_y < IN_H && src0_x >= 0 && src0_x < IN_W) \ + { \ + a_component = src0.data[input_batch_offset + src0_z * (IN_H * IN_W) + src0_y * IN_W + src0_x]; \ + } + +#define A_MULTIPLY_BTILE(a, sliver_num, comp) \ + dst_x = (out_y + sliver_num) % OUT_W; \ + dst_y = (out_y + sliver_num) / OUT_W; \ + org_y = dst_y * STRIDE_H - PAD_H; \ + org_x = dst_x * STRIDE_W - PAD_W; \ + LOAD_A(0, a.x); \ + LOAD_A(1, a.y); \ + LOAD_A(2, a.z); \ + LOAD_A(3, a.w); \ + dot0.comp += dot(brow0, a); \ + dot1.comp += dot(brow1, a); \ + dot2.comp += dot(brow2, a); \ + dot3.comp += dot(brow3, a); \ + dot4.comp += dot(brow4, a); \ + dot5.comp += dot(brow5, a); \ + dot6.comp += dot(brow6, a); \ + dot7.comp += dot(brow7, a); + +void main() +{ + int gx = int(gl_GlobalInvocationID.x); + int gy = int(gl_GlobalInvocationID.y); + int gz = int(gl_GlobalInvocationID.z); + int out_x = BLOCK_W * gx; + int out_y = BLOCK_H * gy; + int input_batch_offset = gz * IN_H * IN_W * CHANNELS; + int output_batch_offset = gz * M * N / VEC_SIZE; + if (out_x < N && gy < M / BLOCK_H) + { + int width0 = K / VEC_SIZE; + int width1 = N / VEC_SIZE; + int src1_read0_offset = out_x * width0; + vec4 dot0 = vec4(0.f); + vec4 dot1 = vec4(0.f); + vec4 dot2 = vec4(0.f); + vec4 dot3 = vec4(0.f); + vec4 dot4 = vec4(0.f); + vec4 dot5 = vec4(0.f); + vec4 dot6 = vec4(0.f); + vec4 dot7 = vec4(0.f); + int i = 0; + do + { + int dst_x, dst_y, org_x, org_y, src0_x, src0_y, src0_z; + vec4 a0 = vec4(0.f), a1 = vec4(0.f), a2 = vec4(0.f), a3 = vec4(0.f); + vec4 brow0 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + vec4 brow1 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + vec4 brow2 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + vec4 brow3 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + vec4 brow4 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + vec4 brow5 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + vec4 brow6 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + vec4 brow7 = src1.data[src1_read0_offset]; src1_read0_offset += width0; + src1_read0_offset += 1 - BLOCK_W * width0; + + A_MULTIPLY_BTILE(a0, 0, x); + A_MULTIPLY_BTILE(a1, 1, y); + A_MULTIPLY_BTILE(a2, 2, z); + A_MULTIPLY_BTILE(a3, 3, w); + i++; + } + while( i < width0 ); + + out0.data[output_batch_offset + (out_x + 0) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot0); + out0.data[output_batch_offset + (out_x + 1) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot1); + out0.data[output_batch_offset + (out_x + 2) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot2); + out0.data[output_batch_offset + (out_x + 3) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot3); + out0.data[output_batch_offset + (out_x + 4) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot4); + out0.data[output_batch_offset + (out_x + 5) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot5); + out0.data[output_batch_offset + (out_x + 6) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot6); + out0.data[output_batch_offset + (out_x + 7) * M / VEC_SIZE + gy] = ACTIVATION_FUNCTION(dot7); + } +} diff --git a/modules/dnn/src/vkcom/shader/conv48_nobias_spv.cpp b/modules/dnn/src/vkcom/shader/conv48_nobias_spv.cpp new file mode 100644 index 0000000000..2f35417e02 --- /dev/null +++ b/modules/dnn/src/vkcom/shader/conv48_nobias_spv.cpp @@ -0,0 +1,913 @@ +// 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) 2018, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include "../../precomp.hpp" + +namespace cv { namespace dnn { namespace vkcom { + +extern const unsigned int conv48_nobias_spv[7182] = { + 0x07230203,0x00010000,0x0008000a,0x00000523,0x00000000,0x00020011,0x00000001,0x0006000b, + 0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001, + 0x0006000f,0x00000005,0x00000004,0x6e69616d,0x00000000,0x0000000c,0x00060010,0x00000004, + 0x00000011,0x00000001,0x00000001,0x00000001,0x00030003,0x00000002,0x000001c2,0x00040005, + 0x00000004,0x6e69616d,0x00000000,0x00030005,0x00000008,0x00007867,0x00080005,0x0000000c, + 0x475f6c67,0x61626f6c,0x766e496c,0x7461636f,0x496e6f69,0x00000044,0x00030005,0x00000012, + 0x00007967,0x00030005,0x00000017,0x00007a67,0x00040005,0x0000001c,0x5f74756f,0x00000078, + 0x00040005,0x00000020,0x5f74756f,0x00000079,0x00070005,0x00000024,0x75706e69,0x61625f74, + 0x5f686374,0x7366666f,0x00007465,0x00040005,0x00000026,0x485f4e49,0x00000000,0x00040005, + 0x00000028,0x575f4e49,0x00000000,0x00050005,0x0000002a,0x4e414843,0x534c454e,0x00000000, + 0x00070005,0x0000002c,0x7074756f,0x625f7475,0x68637461,0x66666f5f,0x00746573,0x00030005, + 0x0000002e,0x0000004d,0x00030005,0x00000030,0x0000004e,0x00040005,0x0000003e,0x74646977, + 0x00003068,0x00030005,0x0000003f,0x0000004b,0x00040005,0x00000041,0x74646977,0x00003168, + 0x00070005,0x00000043,0x31637273,0x6165725f,0x6f5f3064,0x65736666,0x00000074,0x00040005, + 0x0000004a,0x30746f64,0x00000000,0x00040005,0x0000004d,0x31746f64,0x00000000,0x00040005, + 0x0000004e,0x32746f64,0x00000000,0x00040005,0x0000004f,0x33746f64,0x00000000,0x00040005, + 0x00000050,0x34746f64,0x00000000,0x00040005,0x00000051,0x35746f64,0x00000000,0x00040005, + 0x00000052,0x36746f64,0x00000000,0x00040005,0x00000053,0x37746f64,0x00000000,0x00030005, + 0x00000054,0x00000069,0x00030005,0x0000005a,0x00003061,0x00030005,0x0000005b,0x00003161, + 0x00030005,0x0000005c,0x00003261,0x00030005,0x0000005d,0x00003361,0x00040005,0x0000005e, + 0x776f7262,0x00000030,0x00040005,0x00000060,0x75706e49,0x00003374,0x00050006,0x00000060, + 0x00000000,0x61746164,0x00000000,0x00040005,0x00000062,0x31637273,0x00000000,0x00040005, + 0x0000006a,0x776f7262,0x00000031,0x00040005,0x00000071,0x776f7262,0x00000032,0x00040005, + 0x00000078,0x776f7262,0x00000033,0x00040005,0x0000007f,0x776f7262,0x00000034,0x00040005, + 0x00000086,0x776f7262,0x00000035,0x00040005,0x0000008d,0x776f7262,0x00000036,0x00040005, + 0x00000094,0x776f7262,0x00000037,0x00040005,0x000000a1,0x5f747364,0x00000078,0x00040005, + 0x000000a4,0x5f54554f,0x00000057,0x00040005,0x000000a6,0x5f747364,0x00000079,0x00040005, + 0x000000aa,0x5f67726f,0x00000079,0x00050005,0x000000ac,0x49525453,0x485f4544,0x00000000, + 0x00040005,0x000000ae,0x5f444150,0x00000048,0x00040005,0x000000b0,0x5f67726f,0x00000078, + 0x00050005,0x000000b2,0x49525453,0x575f4544,0x00000000,0x00040005,0x000000b4,0x5f444150, + 0x00000057,0x00040005,0x000000b6,0x30637273,0x0000785f,0x00050005,0x000000bb,0x544c4946, + 0x575f5245,0x00000000,0x00050005,0x000000bd,0x414c4944,0x4e4f4954,0x0000575f,0x00040005, + 0x000000c0,0x30637273,0x0000795f,0x00050005,0x000000c5,0x544c4946,0x485f5245,0x00000000, + 0x00050005,0x000000c9,0x414c4944,0x4e4f4954,0x0000485f,0x00040005,0x000000cc,0x30637273, + 0x00007a5f,0x00040005,0x000000e0,0x75706e49,0x00003074,0x00050006,0x000000e0,0x00000000, + 0x61746164,0x00000000,0x00040005,0x000000e2,0x30637273,0x00000000,0x00040005,0x000004c0, + 0x7074754f,0x00007475,0x00050006,0x000004c0,0x00000000,0x61746164,0x00000000,0x00040005, + 0x000004c2,0x3074756f,0x00000000,0x00050005,0x00000516,0x41434f4c,0x5a535f4c,0x0000585f, + 0x00050005,0x00000517,0x41434f4c,0x5a535f4c,0x0000595f,0x00050005,0x00000518,0x41434f4c, + 0x5a535f4c,0x00005a5f,0x00040005,0x00000519,0x43544142,0x00000048,0x00040005,0x0000051a, + 0x4c494154,0x00004d5f,0x00040005,0x0000051c,0x75706e49,0x00003174,0x00050006,0x0000051c, + 0x00000000,0x61746164,0x00000000,0x00040005,0x0000051e,0x73616962,0x00000000,0x00040047, + 0x0000000c,0x0000000b,0x0000001c,0x00040047,0x00000026,0x00000001,0x00000003,0x00040047, + 0x00000028,0x00000001,0x00000004,0x00040047,0x0000002a,0x00000001,0x0000000c,0x00040047, + 0x0000002e,0x00000001,0x0000000e,0x00040047,0x00000030,0x00000001,0x00000010,0x00040047, + 0x0000003f,0x00000001,0x0000000f,0x00040047,0x0000005f,0x00000006,0x00000010,0x00040048, + 0x00000060,0x00000000,0x00000018,0x00050048,0x00000060,0x00000000,0x00000023,0x00000000, + 0x00030047,0x00000060,0x00000003,0x00040047,0x00000062,0x00000022,0x00000000,0x00040047, + 0x00000062,0x00000021,0x00000002,0x00040047,0x000000a4,0x00000001,0x00000005,0x00040047, + 0x000000ac,0x00000001,0x00000006,0x00040047,0x000000ae,0x00000001,0x00000008,0x00040047, + 0x000000b2,0x00000001,0x00000007,0x00040047,0x000000b4,0x00000001,0x00000009,0x00040047, + 0x000000bb,0x00000001,0x0000000b,0x00040047,0x000000bd,0x00000001,0x00000013,0x00040047, + 0x000000c5,0x00000001,0x0000000a,0x00040047,0x000000c9,0x00000001,0x00000012,0x00040047, + 0x000000df,0x00000006,0x00000004,0x00040048,0x000000e0,0x00000000,0x00000018,0x00050048, + 0x000000e0,0x00000000,0x00000023,0x00000000,0x00030047,0x000000e0,0x00000003,0x00040047, + 0x000000e2,0x00000022,0x00000000,0x00040047,0x000000e2,0x00000021,0x00000000,0x00040047, + 0x000004bf,0x00000006,0x00000010,0x00040048,0x000004c0,0x00000000,0x00000019,0x00050048, + 0x000004c0,0x00000000,0x00000023,0x00000000,0x00030047,0x000004c0,0x00000003,0x00040047, + 0x000004c2,0x00000022,0x00000000,0x00040047,0x000004c2,0x00000021,0x00000003,0x00040047, + 0x00000516,0x00000001,0x00000000,0x00040047,0x00000517,0x00000001,0x00000001,0x00040047, + 0x00000518,0x00000001,0x00000002,0x00040047,0x00000519,0x00000001,0x0000000d,0x00040047, + 0x0000051a,0x00000001,0x00000011,0x00040047,0x0000051b,0x00000006,0x00000010,0x00040048, + 0x0000051c,0x00000000,0x00000018,0x00050048,0x0000051c,0x00000000,0x00000023,0x00000000, + 0x00030047,0x0000051c,0x00000003,0x00040047,0x0000051e,0x00000022,0x00000000,0x00040047, + 0x0000051e,0x00000021,0x00000001,0x00040047,0x0000051f,0x00000001,0x00000000,0x00040047, + 0x00000520,0x00000001,0x00000001,0x00040047,0x00000521,0x00000001,0x00000002,0x00040047, + 0x00000522,0x0000000b,0x00000019,0x00020013,0x00000002,0x00030021,0x00000003,0x00000002, + 0x00040015,0x00000006,0x00000020,0x00000001,0x00040020,0x00000007,0x00000007,0x00000006, + 0x00040015,0x00000009,0x00000020,0x00000000,0x00040017,0x0000000a,0x00000009,0x00000003, + 0x00040020,0x0000000b,0x00000001,0x0000000a,0x0004003b,0x0000000b,0x0000000c,0x00000001, + 0x0004002b,0x00000009,0x0000000d,0x00000000,0x00040020,0x0000000e,0x00000001,0x00000009, + 0x0004002b,0x00000009,0x00000013,0x00000001,0x0004002b,0x00000009,0x00000018,0x00000002, + 0x0004002b,0x00000006,0x0000001d,0x00000008,0x0004002b,0x00000006,0x00000021,0x00000004, + 0x00040032,0x00000006,0x00000026,0x00000000,0x00040032,0x00000006,0x00000028,0x00000000, + 0x00040032,0x00000006,0x0000002a,0x00000000,0x00040032,0x00000006,0x0000002e,0x00000000, + 0x00040032,0x00000006,0x00000030,0x00000000,0x00020014,0x00000033,0x00060034,0x00000006, + 0x00000039,0x00000087,0x0000002e,0x00000021,0x00040032,0x00000006,0x0000003f,0x00000000, + 0x00060034,0x00000006,0x00000040,0x00000087,0x0000003f,0x00000021,0x00060034,0x00000006, + 0x00000042,0x00000087,0x00000030,0x00000021,0x00030016,0x00000047,0x00000020,0x00040017, + 0x00000048,0x00000047,0x00000004,0x00040020,0x00000049,0x00000007,0x00000048,0x0004002b, + 0x00000047,0x0000004b,0x00000000,0x0007002c,0x00000048,0x0000004c,0x0000004b,0x0000004b, + 0x0000004b,0x0000004b,0x0004002b,0x00000006,0x00000055,0x00000000,0x0003001d,0x0000005f, + 0x00000048,0x0003001e,0x00000060,0x0000005f,0x00040020,0x00000061,0x00000002,0x00000060, + 0x0004003b,0x00000061,0x00000062,0x00000002,0x00040020,0x00000064,0x00000002,0x00000048, + 0x0004002b,0x00000006,0x0000009b,0x00000001,0x00040032,0x00000006,0x000000a4,0x00000000, + 0x00040032,0x00000006,0x000000ac,0x00000000,0x00040032,0x00000006,0x000000ae,0x00000000, + 0x00040032,0x00000006,0x000000b2,0x00000000,0x00040032,0x00000006,0x000000b4,0x00000000, + 0x00040032,0x00000006,0x000000bb,0x00000000,0x00040032,0x00000006,0x000000bd,0x00000000, + 0x00040032,0x00000006,0x000000c5,0x00000000,0x00060034,0x00000006,0x000000c6,0x00000084, + 0x000000c5,0x000000bb,0x00040032,0x00000006,0x000000c9,0x00000000,0x00060034,0x00000006, + 0x000000d0,0x00000084,0x000000c5,0x000000bb,0x0003001d,0x000000df,0x00000047,0x0003001e, + 0x000000e0,0x000000df,0x00040020,0x000000e1,0x00000002,0x000000e0,0x0004003b,0x000000e1, + 0x000000e2,0x00000002,0x00060034,0x00000006,0x000000e5,0x00000084,0x00000026,0x00000028, + 0x00040020,0x000000ed,0x00000002,0x00000047,0x00040020,0x000000f0,0x00000007,0x00000047, + 0x00060034,0x00000006,0x000000fd,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006, + 0x00000105,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000116,0x00000084, + 0x00000026,0x00000028,0x0004002b,0x00000006,0x00000124,0x00000002,0x00060034,0x00000006, + 0x0000012d,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000135,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000146,0x00000084,0x00000026,0x00000028, + 0x0004002b,0x00000006,0x00000154,0x00000003,0x00060034,0x00000006,0x0000015d,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000165,0x00000084,0x000000c5,0x000000bb, + 0x00060034,0x00000006,0x00000176,0x00000084,0x00000026,0x00000028,0x0004002b,0x00000009, + 0x00000180,0x00000003,0x00060034,0x00000006,0x000001d1,0x00000084,0x000000c5,0x000000bb, + 0x00060034,0x00000006,0x000001d9,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006, + 0x000001ea,0x00000084,0x00000026,0x00000028,0x00060034,0x00000006,0x00000200,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000208,0x00000084,0x000000c5,0x000000bb, + 0x00060034,0x00000006,0x00000219,0x00000084,0x00000026,0x00000028,0x00060034,0x00000006, + 0x0000022f,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000237,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000248,0x00000084,0x00000026,0x00000028, + 0x00060034,0x00000006,0x0000025e,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006, + 0x00000266,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000277,0x00000084, + 0x00000026,0x00000028,0x00060034,0x00000006,0x000002d1,0x00000084,0x000000c5,0x000000bb, + 0x00060034,0x00000006,0x000002d9,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006, + 0x000002ea,0x00000084,0x00000026,0x00000028,0x00060034,0x00000006,0x00000300,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000308,0x00000084,0x000000c5,0x000000bb, + 0x00060034,0x00000006,0x00000319,0x00000084,0x00000026,0x00000028,0x00060034,0x00000006, + 0x0000032f,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000337,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000348,0x00000084,0x00000026,0x00000028, + 0x00060034,0x00000006,0x0000035e,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006, + 0x00000366,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000377,0x00000084, + 0x00000026,0x00000028,0x00060034,0x00000006,0x000003d1,0x00000084,0x000000c5,0x000000bb, + 0x00060034,0x00000006,0x000003d9,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006, + 0x000003ea,0x00000084,0x00000026,0x00000028,0x00060034,0x00000006,0x00000400,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000408,0x00000084,0x000000c5,0x000000bb, + 0x00060034,0x00000006,0x00000419,0x00000084,0x00000026,0x00000028,0x00060034,0x00000006, + 0x0000042f,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000437,0x00000084, + 0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000448,0x00000084,0x00000026,0x00000028, + 0x00060034,0x00000006,0x0000045e,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006, + 0x00000466,0x00000084,0x000000c5,0x000000bb,0x00060034,0x00000006,0x00000477,0x00000084, + 0x00000026,0x00000028,0x0003001d,0x000004bf,0x00000048,0x0003001e,0x000004c0,0x000004bf, + 0x00040020,0x000004c1,0x00000002,0x000004c0,0x0004003b,0x000004c1,0x000004c2,0x00000002, + 0x0004002b,0x00000006,0x000004f7,0x00000005,0x0004002b,0x00000006,0x00000502,0x00000006, + 0x0004002b,0x00000006,0x0000050d,0x00000007,0x00040032,0x00000006,0x00000516,0x00000000, + 0x00040032,0x00000006,0x00000517,0x00000000,0x00040032,0x00000006,0x00000518,0x00000000, + 0x00040032,0x00000006,0x00000519,0x00000000,0x00040032,0x00000006,0x0000051a,0x00000000, + 0x0003001d,0x0000051b,0x00000048,0x0003001e,0x0000051c,0x0000051b,0x00040020,0x0000051d, + 0x00000002,0x0000051c,0x0004003b,0x0000051d,0x0000051e,0x00000002,0x00040032,0x00000009, + 0x0000051f,0x00000001,0x00040032,0x00000009,0x00000520,0x00000001,0x00040032,0x00000009, + 0x00000521,0x00000001,0x00060033,0x0000000a,0x00000522,0x0000051f,0x00000520,0x00000521, + 0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,0x00000005,0x0004003b, + 0x00000007,0x00000008,0x00000007,0x0004003b,0x00000007,0x00000012,0x00000007,0x0004003b, + 0x00000007,0x00000017,0x00000007,0x0004003b,0x00000007,0x0000001c,0x00000007,0x0004003b, + 0x00000007,0x00000020,0x00000007,0x0004003b,0x00000007,0x00000024,0x00000007,0x0004003b, + 0x00000007,0x0000002c,0x00000007,0x0004003b,0x00000007,0x0000003e,0x00000007,0x0004003b, + 0x00000007,0x00000041,0x00000007,0x0004003b,0x00000007,0x00000043,0x00000007,0x0004003b, + 0x00000049,0x0000004a,0x00000007,0x0004003b,0x00000049,0x0000004d,0x00000007,0x0004003b, + 0x00000049,0x0000004e,0x00000007,0x0004003b,0x00000049,0x0000004f,0x00000007,0x0004003b, + 0x00000049,0x00000050,0x00000007,0x0004003b,0x00000049,0x00000051,0x00000007,0x0004003b, + 0x00000049,0x00000052,0x00000007,0x0004003b,0x00000049,0x00000053,0x00000007,0x0004003b, + 0x00000007,0x00000054,0x00000007,0x0004003b,0x00000049,0x0000005a,0x00000007,0x0004003b, + 0x00000049,0x0000005b,0x00000007,0x0004003b,0x00000049,0x0000005c,0x00000007,0x0004003b, + 0x00000049,0x0000005d,0x00000007,0x0004003b,0x00000049,0x0000005e,0x00000007,0x0004003b, + 0x00000049,0x0000006a,0x00000007,0x0004003b,0x00000049,0x00000071,0x00000007,0x0004003b, + 0x00000049,0x00000078,0x00000007,0x0004003b,0x00000049,0x0000007f,0x00000007,0x0004003b, + 0x00000049,0x00000086,0x00000007,0x0004003b,0x00000049,0x0000008d,0x00000007,0x0004003b, + 0x00000049,0x00000094,0x00000007,0x0004003b,0x00000007,0x000000a1,0x00000007,0x0004003b, + 0x00000007,0x000000a6,0x00000007,0x0004003b,0x00000007,0x000000aa,0x00000007,0x0004003b, + 0x00000007,0x000000b0,0x00000007,0x0004003b,0x00000007,0x000000b6,0x00000007,0x0004003b, + 0x00000007,0x000000c0,0x00000007,0x0004003b,0x00000007,0x000000cc,0x00000007,0x00050041, + 0x0000000e,0x0000000f,0x0000000c,0x0000000d,0x0004003d,0x00000009,0x00000010,0x0000000f, + 0x0004007c,0x00000006,0x00000011,0x00000010,0x0003003e,0x00000008,0x00000011,0x00050041, + 0x0000000e,0x00000014,0x0000000c,0x00000013,0x0004003d,0x00000009,0x00000015,0x00000014, + 0x0004007c,0x00000006,0x00000016,0x00000015,0x0003003e,0x00000012,0x00000016,0x00050041, + 0x0000000e,0x00000019,0x0000000c,0x00000018,0x0004003d,0x00000009,0x0000001a,0x00000019, + 0x0004007c,0x00000006,0x0000001b,0x0000001a,0x0003003e,0x00000017,0x0000001b,0x0004003d, + 0x00000006,0x0000001e,0x00000008,0x00050084,0x00000006,0x0000001f,0x0000001d,0x0000001e, + 0x0003003e,0x0000001c,0x0000001f,0x0004003d,0x00000006,0x00000022,0x00000012,0x00050084, + 0x00000006,0x00000023,0x00000021,0x00000022,0x0003003e,0x00000020,0x00000023,0x0004003d, + 0x00000006,0x00000025,0x00000017,0x00050084,0x00000006,0x00000027,0x00000025,0x00000026, + 0x00050084,0x00000006,0x00000029,0x00000027,0x00000028,0x00050084,0x00000006,0x0000002b, + 0x00000029,0x0000002a,0x0003003e,0x00000024,0x0000002b,0x0004003d,0x00000006,0x0000002d, + 0x00000017,0x00050084,0x00000006,0x0000002f,0x0000002d,0x0000002e,0x00050084,0x00000006, + 0x00000031,0x0000002f,0x00000030,0x00050087,0x00000006,0x00000032,0x00000031,0x00000021, + 0x0003003e,0x0000002c,0x00000032,0x0004003d,0x00000006,0x00000034,0x0000001c,0x000500b1, + 0x00000033,0x00000035,0x00000034,0x00000030,0x000300f7,0x00000037,0x00000000,0x000400fa, + 0x00000035,0x00000036,0x00000037,0x000200f8,0x00000036,0x0004003d,0x00000006,0x00000038, + 0x00000012,0x000500b1,0x00000033,0x0000003a,0x00000038,0x00000039,0x000200f9,0x00000037, + 0x000200f8,0x00000037,0x000700f5,0x00000033,0x0000003b,0x00000035,0x00000005,0x0000003a, + 0x00000036,0x000300f7,0x0000003d,0x00000000,0x000400fa,0x0000003b,0x0000003c,0x0000003d, + 0x000200f8,0x0000003c,0x0003003e,0x0000003e,0x00000040,0x0003003e,0x00000041,0x00000042, + 0x0004003d,0x00000006,0x00000044,0x0000001c,0x0004003d,0x00000006,0x00000045,0x0000003e, + 0x00050084,0x00000006,0x00000046,0x00000044,0x00000045,0x0003003e,0x00000043,0x00000046, + 0x0003003e,0x0000004a,0x0000004c,0x0003003e,0x0000004d,0x0000004c,0x0003003e,0x0000004e, + 0x0000004c,0x0003003e,0x0000004f,0x0000004c,0x0003003e,0x00000050,0x0000004c,0x0003003e, + 0x00000051,0x0000004c,0x0003003e,0x00000052,0x0000004c,0x0003003e,0x00000053,0x0000004c, + 0x0003003e,0x00000054,0x00000055,0x000200f9,0x00000056,0x000200f8,0x00000056,0x000400f6, + 0x00000058,0x00000059,0x00000000,0x000200f9,0x00000057,0x000200f8,0x00000057,0x0003003e, + 0x0000005a,0x0000004c,0x0003003e,0x0000005b,0x0000004c,0x0003003e,0x0000005c,0x0000004c, + 0x0003003e,0x0000005d,0x0000004c,0x0004003d,0x00000006,0x00000063,0x00000043,0x00060041, + 0x00000064,0x00000065,0x00000062,0x00000055,0x00000063,0x0004003d,0x00000048,0x00000066, + 0x00000065,0x0003003e,0x0000005e,0x00000066,0x0004003d,0x00000006,0x00000067,0x0000003e, + 0x0004003d,0x00000006,0x00000068,0x00000043,0x00050080,0x00000006,0x00000069,0x00000068, + 0x00000067,0x0003003e,0x00000043,0x00000069,0x0004003d,0x00000006,0x0000006b,0x00000043, + 0x00060041,0x00000064,0x0000006c,0x00000062,0x00000055,0x0000006b,0x0004003d,0x00000048, + 0x0000006d,0x0000006c,0x0003003e,0x0000006a,0x0000006d,0x0004003d,0x00000006,0x0000006e, + 0x0000003e,0x0004003d,0x00000006,0x0000006f,0x00000043,0x00050080,0x00000006,0x00000070, + 0x0000006f,0x0000006e,0x0003003e,0x00000043,0x00000070,0x0004003d,0x00000006,0x00000072, + 0x00000043,0x00060041,0x00000064,0x00000073,0x00000062,0x00000055,0x00000072,0x0004003d, + 0x00000048,0x00000074,0x00000073,0x0003003e,0x00000071,0x00000074,0x0004003d,0x00000006, + 0x00000075,0x0000003e,0x0004003d,0x00000006,0x00000076,0x00000043,0x00050080,0x00000006, + 0x00000077,0x00000076,0x00000075,0x0003003e,0x00000043,0x00000077,0x0004003d,0x00000006, + 0x00000079,0x00000043,0x00060041,0x00000064,0x0000007a,0x00000062,0x00000055,0x00000079, + 0x0004003d,0x00000048,0x0000007b,0x0000007a,0x0003003e,0x00000078,0x0000007b,0x0004003d, + 0x00000006,0x0000007c,0x0000003e,0x0004003d,0x00000006,0x0000007d,0x00000043,0x00050080, + 0x00000006,0x0000007e,0x0000007d,0x0000007c,0x0003003e,0x00000043,0x0000007e,0x0004003d, + 0x00000006,0x00000080,0x00000043,0x00060041,0x00000064,0x00000081,0x00000062,0x00000055, + 0x00000080,0x0004003d,0x00000048,0x00000082,0x00000081,0x0003003e,0x0000007f,0x00000082, + 0x0004003d,0x00000006,0x00000083,0x0000003e,0x0004003d,0x00000006,0x00000084,0x00000043, + 0x00050080,0x00000006,0x00000085,0x00000084,0x00000083,0x0003003e,0x00000043,0x00000085, + 0x0004003d,0x00000006,0x00000087,0x00000043,0x00060041,0x00000064,0x00000088,0x00000062, + 0x00000055,0x00000087,0x0004003d,0x00000048,0x00000089,0x00000088,0x0003003e,0x00000086, + 0x00000089,0x0004003d,0x00000006,0x0000008a,0x0000003e,0x0004003d,0x00000006,0x0000008b, + 0x00000043,0x00050080,0x00000006,0x0000008c,0x0000008b,0x0000008a,0x0003003e,0x00000043, + 0x0000008c,0x0004003d,0x00000006,0x0000008e,0x00000043,0x00060041,0x00000064,0x0000008f, + 0x00000062,0x00000055,0x0000008e,0x0004003d,0x00000048,0x00000090,0x0000008f,0x0003003e, + 0x0000008d,0x00000090,0x0004003d,0x00000006,0x00000091,0x0000003e,0x0004003d,0x00000006, + 0x00000092,0x00000043,0x00050080,0x00000006,0x00000093,0x00000092,0x00000091,0x0003003e, + 0x00000043,0x00000093,0x0004003d,0x00000006,0x00000095,0x00000043,0x00060041,0x00000064, + 0x00000096,0x00000062,0x00000055,0x00000095,0x0004003d,0x00000048,0x00000097,0x00000096, + 0x0003003e,0x00000094,0x00000097,0x0004003d,0x00000006,0x00000098,0x0000003e,0x0004003d, + 0x00000006,0x00000099,0x00000043,0x00050080,0x00000006,0x0000009a,0x00000099,0x00000098, + 0x0003003e,0x00000043,0x0000009a,0x0004003d,0x00000006,0x0000009c,0x0000003e,0x00050084, + 0x00000006,0x0000009d,0x0000001d,0x0000009c,0x00050082,0x00000006,0x0000009e,0x0000009b, + 0x0000009d,0x0004003d,0x00000006,0x0000009f,0x00000043,0x00050080,0x00000006,0x000000a0, + 0x0000009f,0x0000009e,0x0003003e,0x00000043,0x000000a0,0x0004003d,0x00000006,0x000000a2, + 0x00000020,0x00050080,0x00000006,0x000000a3,0x000000a2,0x00000055,0x0005008b,0x00000006, + 0x000000a5,0x000000a3,0x000000a4,0x0003003e,0x000000a1,0x000000a5,0x0004003d,0x00000006, + 0x000000a7,0x00000020,0x00050080,0x00000006,0x000000a8,0x000000a7,0x00000055,0x00050087, + 0x00000006,0x000000a9,0x000000a8,0x000000a4,0x0003003e,0x000000a6,0x000000a9,0x0004003d, + 0x00000006,0x000000ab,0x000000a6,0x00050084,0x00000006,0x000000ad,0x000000ab,0x000000ac, + 0x00050082,0x00000006,0x000000af,0x000000ad,0x000000ae,0x0003003e,0x000000aa,0x000000af, + 0x0004003d,0x00000006,0x000000b1,0x000000a1,0x00050084,0x00000006,0x000000b3,0x000000b1, + 0x000000b2,0x00050082,0x00000006,0x000000b5,0x000000b3,0x000000b4,0x0003003e,0x000000b0, + 0x000000b5,0x0004003d,0x00000006,0x000000b7,0x000000b0,0x0004003d,0x00000006,0x000000b8, + 0x00000054,0x00050084,0x00000006,0x000000b9,0x000000b8,0x00000021,0x00050080,0x00000006, + 0x000000ba,0x000000b9,0x00000055,0x0005008b,0x00000006,0x000000bc,0x000000ba,0x000000bb, + 0x00050084,0x00000006,0x000000be,0x000000bc,0x000000bd,0x00050080,0x00000006,0x000000bf, + 0x000000b7,0x000000be,0x0003003e,0x000000b6,0x000000bf,0x0004003d,0x00000006,0x000000c1, + 0x000000aa,0x0004003d,0x00000006,0x000000c2,0x00000054,0x00050084,0x00000006,0x000000c3, + 0x000000c2,0x00000021,0x00050080,0x00000006,0x000000c4,0x000000c3,0x00000055,0x0005008b, + 0x00000006,0x000000c7,0x000000c4,0x000000c6,0x00050087,0x00000006,0x000000c8,0x000000c7, + 0x000000bb,0x00050084,0x00000006,0x000000ca,0x000000c8,0x000000c9,0x00050080,0x00000006, + 0x000000cb,0x000000c1,0x000000ca,0x0003003e,0x000000c0,0x000000cb,0x0004003d,0x00000006, + 0x000000cd,0x00000054,0x00050084,0x00000006,0x000000ce,0x000000cd,0x00000021,0x00050080, + 0x00000006,0x000000cf,0x000000ce,0x00000055,0x00050087,0x00000006,0x000000d1,0x000000cf, + 0x000000d0,0x0003003e,0x000000cc,0x000000d1,0x0004003d,0x00000006,0x000000d2,0x000000c0, + 0x000500af,0x00000033,0x000000d3,0x000000d2,0x00000055,0x0004003d,0x00000006,0x000000d4, + 0x000000c0,0x000500b1,0x00000033,0x000000d5,0x000000d4,0x00000026,0x000500a7,0x00000033, + 0x000000d6,0x000000d3,0x000000d5,0x0004003d,0x00000006,0x000000d7,0x000000b6,0x000500af, + 0x00000033,0x000000d8,0x000000d7,0x00000055,0x000500a7,0x00000033,0x000000d9,0x000000d6, + 0x000000d8,0x0004003d,0x00000006,0x000000da,0x000000b6,0x000500b1,0x00000033,0x000000db, + 0x000000da,0x00000028,0x000500a7,0x00000033,0x000000dc,0x000000d9,0x000000db,0x000300f7, + 0x000000de,0x00000000,0x000400fa,0x000000dc,0x000000dd,0x000000de,0x000200f8,0x000000dd, + 0x0004003d,0x00000006,0x000000e3,0x00000024,0x0004003d,0x00000006,0x000000e4,0x000000cc, + 0x00050084,0x00000006,0x000000e6,0x000000e4,0x000000e5,0x00050080,0x00000006,0x000000e7, + 0x000000e3,0x000000e6,0x0004003d,0x00000006,0x000000e8,0x000000c0,0x00050084,0x00000006, + 0x000000e9,0x000000e8,0x00000028,0x00050080,0x00000006,0x000000ea,0x000000e7,0x000000e9, + 0x0004003d,0x00000006,0x000000eb,0x000000b6,0x00050080,0x00000006,0x000000ec,0x000000ea, + 0x000000eb,0x00060041,0x000000ed,0x000000ee,0x000000e2,0x00000055,0x000000ec,0x0004003d, + 0x00000047,0x000000ef,0x000000ee,0x00050041,0x000000f0,0x000000f1,0x0000005a,0x0000000d, + 0x0003003e,0x000000f1,0x000000ef,0x000200f9,0x000000de,0x000200f8,0x000000de,0x0004003d, + 0x00000006,0x000000f2,0x000000b0,0x0004003d,0x00000006,0x000000f3,0x00000054,0x00050084, + 0x00000006,0x000000f4,0x000000f3,0x00000021,0x00050080,0x00000006,0x000000f5,0x000000f4, + 0x0000009b,0x0005008b,0x00000006,0x000000f6,0x000000f5,0x000000bb,0x00050084,0x00000006, + 0x000000f7,0x000000f6,0x000000bd,0x00050080,0x00000006,0x000000f8,0x000000f2,0x000000f7, + 0x0003003e,0x000000b6,0x000000f8,0x0004003d,0x00000006,0x000000f9,0x000000aa,0x0004003d, + 0x00000006,0x000000fa,0x00000054,0x00050084,0x00000006,0x000000fb,0x000000fa,0x00000021, + 0x00050080,0x00000006,0x000000fc,0x000000fb,0x0000009b,0x0005008b,0x00000006,0x000000fe, + 0x000000fc,0x000000fd,0x00050087,0x00000006,0x000000ff,0x000000fe,0x000000bb,0x00050084, + 0x00000006,0x00000100,0x000000ff,0x000000c9,0x00050080,0x00000006,0x00000101,0x000000f9, + 0x00000100,0x0003003e,0x000000c0,0x00000101,0x0004003d,0x00000006,0x00000102,0x00000054, + 0x00050084,0x00000006,0x00000103,0x00000102,0x00000021,0x00050080,0x00000006,0x00000104, + 0x00000103,0x0000009b,0x00050087,0x00000006,0x00000106,0x00000104,0x00000105,0x0003003e, + 0x000000cc,0x00000106,0x0004003d,0x00000006,0x00000107,0x000000c0,0x000500af,0x00000033, + 0x00000108,0x00000107,0x00000055,0x0004003d,0x00000006,0x00000109,0x000000c0,0x000500b1, + 0x00000033,0x0000010a,0x00000109,0x00000026,0x000500a7,0x00000033,0x0000010b,0x00000108, + 0x0000010a,0x0004003d,0x00000006,0x0000010c,0x000000b6,0x000500af,0x00000033,0x0000010d, + 0x0000010c,0x00000055,0x000500a7,0x00000033,0x0000010e,0x0000010b,0x0000010d,0x0004003d, + 0x00000006,0x0000010f,0x000000b6,0x000500b1,0x00000033,0x00000110,0x0000010f,0x00000028, + 0x000500a7,0x00000033,0x00000111,0x0000010e,0x00000110,0x000300f7,0x00000113,0x00000000, + 0x000400fa,0x00000111,0x00000112,0x00000113,0x000200f8,0x00000112,0x0004003d,0x00000006, + 0x00000114,0x00000024,0x0004003d,0x00000006,0x00000115,0x000000cc,0x00050084,0x00000006, + 0x00000117,0x00000115,0x00000116,0x00050080,0x00000006,0x00000118,0x00000114,0x00000117, + 0x0004003d,0x00000006,0x00000119,0x000000c0,0x00050084,0x00000006,0x0000011a,0x00000119, + 0x00000028,0x00050080,0x00000006,0x0000011b,0x00000118,0x0000011a,0x0004003d,0x00000006, + 0x0000011c,0x000000b6,0x00050080,0x00000006,0x0000011d,0x0000011b,0x0000011c,0x00060041, + 0x000000ed,0x0000011e,0x000000e2,0x00000055,0x0000011d,0x0004003d,0x00000047,0x0000011f, + 0x0000011e,0x00050041,0x000000f0,0x00000120,0x0000005a,0x00000013,0x0003003e,0x00000120, + 0x0000011f,0x000200f9,0x00000113,0x000200f8,0x00000113,0x0004003d,0x00000006,0x00000121, + 0x000000b0,0x0004003d,0x00000006,0x00000122,0x00000054,0x00050084,0x00000006,0x00000123, + 0x00000122,0x00000021,0x00050080,0x00000006,0x00000125,0x00000123,0x00000124,0x0005008b, + 0x00000006,0x00000126,0x00000125,0x000000bb,0x00050084,0x00000006,0x00000127,0x00000126, + 0x000000bd,0x00050080,0x00000006,0x00000128,0x00000121,0x00000127,0x0003003e,0x000000b6, + 0x00000128,0x0004003d,0x00000006,0x00000129,0x000000aa,0x0004003d,0x00000006,0x0000012a, + 0x00000054,0x00050084,0x00000006,0x0000012b,0x0000012a,0x00000021,0x00050080,0x00000006, + 0x0000012c,0x0000012b,0x00000124,0x0005008b,0x00000006,0x0000012e,0x0000012c,0x0000012d, + 0x00050087,0x00000006,0x0000012f,0x0000012e,0x000000bb,0x00050084,0x00000006,0x00000130, + 0x0000012f,0x000000c9,0x00050080,0x00000006,0x00000131,0x00000129,0x00000130,0x0003003e, + 0x000000c0,0x00000131,0x0004003d,0x00000006,0x00000132,0x00000054,0x00050084,0x00000006, + 0x00000133,0x00000132,0x00000021,0x00050080,0x00000006,0x00000134,0x00000133,0x00000124, + 0x00050087,0x00000006,0x00000136,0x00000134,0x00000135,0x0003003e,0x000000cc,0x00000136, + 0x0004003d,0x00000006,0x00000137,0x000000c0,0x000500af,0x00000033,0x00000138,0x00000137, + 0x00000055,0x0004003d,0x00000006,0x00000139,0x000000c0,0x000500b1,0x00000033,0x0000013a, + 0x00000139,0x00000026,0x000500a7,0x00000033,0x0000013b,0x00000138,0x0000013a,0x0004003d, + 0x00000006,0x0000013c,0x000000b6,0x000500af,0x00000033,0x0000013d,0x0000013c,0x00000055, + 0x000500a7,0x00000033,0x0000013e,0x0000013b,0x0000013d,0x0004003d,0x00000006,0x0000013f, + 0x000000b6,0x000500b1,0x00000033,0x00000140,0x0000013f,0x00000028,0x000500a7,0x00000033, + 0x00000141,0x0000013e,0x00000140,0x000300f7,0x00000143,0x00000000,0x000400fa,0x00000141, + 0x00000142,0x00000143,0x000200f8,0x00000142,0x0004003d,0x00000006,0x00000144,0x00000024, + 0x0004003d,0x00000006,0x00000145,0x000000cc,0x00050084,0x00000006,0x00000147,0x00000145, + 0x00000146,0x00050080,0x00000006,0x00000148,0x00000144,0x00000147,0x0004003d,0x00000006, + 0x00000149,0x000000c0,0x00050084,0x00000006,0x0000014a,0x00000149,0x00000028,0x00050080, + 0x00000006,0x0000014b,0x00000148,0x0000014a,0x0004003d,0x00000006,0x0000014c,0x000000b6, + 0x00050080,0x00000006,0x0000014d,0x0000014b,0x0000014c,0x00060041,0x000000ed,0x0000014e, + 0x000000e2,0x00000055,0x0000014d,0x0004003d,0x00000047,0x0000014f,0x0000014e,0x00050041, + 0x000000f0,0x00000150,0x0000005a,0x00000018,0x0003003e,0x00000150,0x0000014f,0x000200f9, + 0x00000143,0x000200f8,0x00000143,0x0004003d,0x00000006,0x00000151,0x000000b0,0x0004003d, + 0x00000006,0x00000152,0x00000054,0x00050084,0x00000006,0x00000153,0x00000152,0x00000021, + 0x00050080,0x00000006,0x00000155,0x00000153,0x00000154,0x0005008b,0x00000006,0x00000156, + 0x00000155,0x000000bb,0x00050084,0x00000006,0x00000157,0x00000156,0x000000bd,0x00050080, + 0x00000006,0x00000158,0x00000151,0x00000157,0x0003003e,0x000000b6,0x00000158,0x0004003d, + 0x00000006,0x00000159,0x000000aa,0x0004003d,0x00000006,0x0000015a,0x00000054,0x00050084, + 0x00000006,0x0000015b,0x0000015a,0x00000021,0x00050080,0x00000006,0x0000015c,0x0000015b, + 0x00000154,0x0005008b,0x00000006,0x0000015e,0x0000015c,0x0000015d,0x00050087,0x00000006, + 0x0000015f,0x0000015e,0x000000bb,0x00050084,0x00000006,0x00000160,0x0000015f,0x000000c9, + 0x00050080,0x00000006,0x00000161,0x00000159,0x00000160,0x0003003e,0x000000c0,0x00000161, + 0x0004003d,0x00000006,0x00000162,0x00000054,0x00050084,0x00000006,0x00000163,0x00000162, + 0x00000021,0x00050080,0x00000006,0x00000164,0x00000163,0x00000154,0x00050087,0x00000006, + 0x00000166,0x00000164,0x00000165,0x0003003e,0x000000cc,0x00000166,0x0004003d,0x00000006, + 0x00000167,0x000000c0,0x000500af,0x00000033,0x00000168,0x00000167,0x00000055,0x0004003d, + 0x00000006,0x00000169,0x000000c0,0x000500b1,0x00000033,0x0000016a,0x00000169,0x00000026, + 0x000500a7,0x00000033,0x0000016b,0x00000168,0x0000016a,0x0004003d,0x00000006,0x0000016c, + 0x000000b6,0x000500af,0x00000033,0x0000016d,0x0000016c,0x00000055,0x000500a7,0x00000033, + 0x0000016e,0x0000016b,0x0000016d,0x0004003d,0x00000006,0x0000016f,0x000000b6,0x000500b1, + 0x00000033,0x00000170,0x0000016f,0x00000028,0x000500a7,0x00000033,0x00000171,0x0000016e, + 0x00000170,0x000300f7,0x00000173,0x00000000,0x000400fa,0x00000171,0x00000172,0x00000173, + 0x000200f8,0x00000172,0x0004003d,0x00000006,0x00000174,0x00000024,0x0004003d,0x00000006, + 0x00000175,0x000000cc,0x00050084,0x00000006,0x00000177,0x00000175,0x00000176,0x00050080, + 0x00000006,0x00000178,0x00000174,0x00000177,0x0004003d,0x00000006,0x00000179,0x000000c0, + 0x00050084,0x00000006,0x0000017a,0x00000179,0x00000028,0x00050080,0x00000006,0x0000017b, + 0x00000178,0x0000017a,0x0004003d,0x00000006,0x0000017c,0x000000b6,0x00050080,0x00000006, + 0x0000017d,0x0000017b,0x0000017c,0x00060041,0x000000ed,0x0000017e,0x000000e2,0x00000055, + 0x0000017d,0x0004003d,0x00000047,0x0000017f,0x0000017e,0x00050041,0x000000f0,0x00000181, + 0x0000005a,0x00000180,0x0003003e,0x00000181,0x0000017f,0x000200f9,0x00000173,0x000200f8, + 0x00000173,0x0004003d,0x00000048,0x00000182,0x0000005e,0x0004003d,0x00000048,0x00000183, + 0x0000005a,0x00050094,0x00000047,0x00000184,0x00000182,0x00000183,0x00050041,0x000000f0, + 0x00000185,0x0000004a,0x0000000d,0x0004003d,0x00000047,0x00000186,0x00000185,0x00050081, + 0x00000047,0x00000187,0x00000186,0x00000184,0x00050041,0x000000f0,0x00000188,0x0000004a, + 0x0000000d,0x0003003e,0x00000188,0x00000187,0x0004003d,0x00000048,0x00000189,0x0000006a, + 0x0004003d,0x00000048,0x0000018a,0x0000005a,0x00050094,0x00000047,0x0000018b,0x00000189, + 0x0000018a,0x00050041,0x000000f0,0x0000018c,0x0000004d,0x0000000d,0x0004003d,0x00000047, + 0x0000018d,0x0000018c,0x00050081,0x00000047,0x0000018e,0x0000018d,0x0000018b,0x00050041, + 0x000000f0,0x0000018f,0x0000004d,0x0000000d,0x0003003e,0x0000018f,0x0000018e,0x0004003d, + 0x00000048,0x00000190,0x00000071,0x0004003d,0x00000048,0x00000191,0x0000005a,0x00050094, + 0x00000047,0x00000192,0x00000190,0x00000191,0x00050041,0x000000f0,0x00000193,0x0000004e, + 0x0000000d,0x0004003d,0x00000047,0x00000194,0x00000193,0x00050081,0x00000047,0x00000195, + 0x00000194,0x00000192,0x00050041,0x000000f0,0x00000196,0x0000004e,0x0000000d,0x0003003e, + 0x00000196,0x00000195,0x0004003d,0x00000048,0x00000197,0x00000078,0x0004003d,0x00000048, + 0x00000198,0x0000005a,0x00050094,0x00000047,0x00000199,0x00000197,0x00000198,0x00050041, + 0x000000f0,0x0000019a,0x0000004f,0x0000000d,0x0004003d,0x00000047,0x0000019b,0x0000019a, + 0x00050081,0x00000047,0x0000019c,0x0000019b,0x00000199,0x00050041,0x000000f0,0x0000019d, + 0x0000004f,0x0000000d,0x0003003e,0x0000019d,0x0000019c,0x0004003d,0x00000048,0x0000019e, + 0x0000007f,0x0004003d,0x00000048,0x0000019f,0x0000005a,0x00050094,0x00000047,0x000001a0, + 0x0000019e,0x0000019f,0x00050041,0x000000f0,0x000001a1,0x00000050,0x0000000d,0x0004003d, + 0x00000047,0x000001a2,0x000001a1,0x00050081,0x00000047,0x000001a3,0x000001a2,0x000001a0, + 0x00050041,0x000000f0,0x000001a4,0x00000050,0x0000000d,0x0003003e,0x000001a4,0x000001a3, + 0x0004003d,0x00000048,0x000001a5,0x00000086,0x0004003d,0x00000048,0x000001a6,0x0000005a, + 0x00050094,0x00000047,0x000001a7,0x000001a5,0x000001a6,0x00050041,0x000000f0,0x000001a8, + 0x00000051,0x0000000d,0x0004003d,0x00000047,0x000001a9,0x000001a8,0x00050081,0x00000047, + 0x000001aa,0x000001a9,0x000001a7,0x00050041,0x000000f0,0x000001ab,0x00000051,0x0000000d, + 0x0003003e,0x000001ab,0x000001aa,0x0004003d,0x00000048,0x000001ac,0x0000008d,0x0004003d, + 0x00000048,0x000001ad,0x0000005a,0x00050094,0x00000047,0x000001ae,0x000001ac,0x000001ad, + 0x00050041,0x000000f0,0x000001af,0x00000052,0x0000000d,0x0004003d,0x00000047,0x000001b0, + 0x000001af,0x00050081,0x00000047,0x000001b1,0x000001b0,0x000001ae,0x00050041,0x000000f0, + 0x000001b2,0x00000052,0x0000000d,0x0003003e,0x000001b2,0x000001b1,0x0004003d,0x00000048, + 0x000001b3,0x00000094,0x0004003d,0x00000048,0x000001b4,0x0000005a,0x00050094,0x00000047, + 0x000001b5,0x000001b3,0x000001b4,0x00050041,0x000000f0,0x000001b6,0x00000053,0x0000000d, + 0x0004003d,0x00000047,0x000001b7,0x000001b6,0x00050081,0x00000047,0x000001b8,0x000001b7, + 0x000001b5,0x00050041,0x000000f0,0x000001b9,0x00000053,0x0000000d,0x0003003e,0x000001b9, + 0x000001b8,0x0004003d,0x00000006,0x000001ba,0x00000020,0x00050080,0x00000006,0x000001bb, + 0x000001ba,0x0000009b,0x0005008b,0x00000006,0x000001bc,0x000001bb,0x000000a4,0x0003003e, + 0x000000a1,0x000001bc,0x0004003d,0x00000006,0x000001bd,0x00000020,0x00050080,0x00000006, + 0x000001be,0x000001bd,0x0000009b,0x00050087,0x00000006,0x000001bf,0x000001be,0x000000a4, + 0x0003003e,0x000000a6,0x000001bf,0x0004003d,0x00000006,0x000001c0,0x000000a6,0x00050084, + 0x00000006,0x000001c1,0x000001c0,0x000000ac,0x00050082,0x00000006,0x000001c2,0x000001c1, + 0x000000ae,0x0003003e,0x000000aa,0x000001c2,0x0004003d,0x00000006,0x000001c3,0x000000a1, + 0x00050084,0x00000006,0x000001c4,0x000001c3,0x000000b2,0x00050082,0x00000006,0x000001c5, + 0x000001c4,0x000000b4,0x0003003e,0x000000b0,0x000001c5,0x0004003d,0x00000006,0x000001c6, + 0x000000b0,0x0004003d,0x00000006,0x000001c7,0x00000054,0x00050084,0x00000006,0x000001c8, + 0x000001c7,0x00000021,0x00050080,0x00000006,0x000001c9,0x000001c8,0x00000055,0x0005008b, + 0x00000006,0x000001ca,0x000001c9,0x000000bb,0x00050084,0x00000006,0x000001cb,0x000001ca, + 0x000000bd,0x00050080,0x00000006,0x000001cc,0x000001c6,0x000001cb,0x0003003e,0x000000b6, + 0x000001cc,0x0004003d,0x00000006,0x000001cd,0x000000aa,0x0004003d,0x00000006,0x000001ce, + 0x00000054,0x00050084,0x00000006,0x000001cf,0x000001ce,0x00000021,0x00050080,0x00000006, + 0x000001d0,0x000001cf,0x00000055,0x0005008b,0x00000006,0x000001d2,0x000001d0,0x000001d1, + 0x00050087,0x00000006,0x000001d3,0x000001d2,0x000000bb,0x00050084,0x00000006,0x000001d4, + 0x000001d3,0x000000c9,0x00050080,0x00000006,0x000001d5,0x000001cd,0x000001d4,0x0003003e, + 0x000000c0,0x000001d5,0x0004003d,0x00000006,0x000001d6,0x00000054,0x00050084,0x00000006, + 0x000001d7,0x000001d6,0x00000021,0x00050080,0x00000006,0x000001d8,0x000001d7,0x00000055, + 0x00050087,0x00000006,0x000001da,0x000001d8,0x000001d9,0x0003003e,0x000000cc,0x000001da, + 0x0004003d,0x00000006,0x000001db,0x000000c0,0x000500af,0x00000033,0x000001dc,0x000001db, + 0x00000055,0x0004003d,0x00000006,0x000001dd,0x000000c0,0x000500b1,0x00000033,0x000001de, + 0x000001dd,0x00000026,0x000500a7,0x00000033,0x000001df,0x000001dc,0x000001de,0x0004003d, + 0x00000006,0x000001e0,0x000000b6,0x000500af,0x00000033,0x000001e1,0x000001e0,0x00000055, + 0x000500a7,0x00000033,0x000001e2,0x000001df,0x000001e1,0x0004003d,0x00000006,0x000001e3, + 0x000000b6,0x000500b1,0x00000033,0x000001e4,0x000001e3,0x00000028,0x000500a7,0x00000033, + 0x000001e5,0x000001e2,0x000001e4,0x000300f7,0x000001e7,0x00000000,0x000400fa,0x000001e5, + 0x000001e6,0x000001e7,0x000200f8,0x000001e6,0x0004003d,0x00000006,0x000001e8,0x00000024, + 0x0004003d,0x00000006,0x000001e9,0x000000cc,0x00050084,0x00000006,0x000001eb,0x000001e9, + 0x000001ea,0x00050080,0x00000006,0x000001ec,0x000001e8,0x000001eb,0x0004003d,0x00000006, + 0x000001ed,0x000000c0,0x00050084,0x00000006,0x000001ee,0x000001ed,0x00000028,0x00050080, + 0x00000006,0x000001ef,0x000001ec,0x000001ee,0x0004003d,0x00000006,0x000001f0,0x000000b6, + 0x00050080,0x00000006,0x000001f1,0x000001ef,0x000001f0,0x00060041,0x000000ed,0x000001f2, + 0x000000e2,0x00000055,0x000001f1,0x0004003d,0x00000047,0x000001f3,0x000001f2,0x00050041, + 0x000000f0,0x000001f4,0x0000005b,0x0000000d,0x0003003e,0x000001f4,0x000001f3,0x000200f9, + 0x000001e7,0x000200f8,0x000001e7,0x0004003d,0x00000006,0x000001f5,0x000000b0,0x0004003d, + 0x00000006,0x000001f6,0x00000054,0x00050084,0x00000006,0x000001f7,0x000001f6,0x00000021, + 0x00050080,0x00000006,0x000001f8,0x000001f7,0x0000009b,0x0005008b,0x00000006,0x000001f9, + 0x000001f8,0x000000bb,0x00050084,0x00000006,0x000001fa,0x000001f9,0x000000bd,0x00050080, + 0x00000006,0x000001fb,0x000001f5,0x000001fa,0x0003003e,0x000000b6,0x000001fb,0x0004003d, + 0x00000006,0x000001fc,0x000000aa,0x0004003d,0x00000006,0x000001fd,0x00000054,0x00050084, + 0x00000006,0x000001fe,0x000001fd,0x00000021,0x00050080,0x00000006,0x000001ff,0x000001fe, + 0x0000009b,0x0005008b,0x00000006,0x00000201,0x000001ff,0x00000200,0x00050087,0x00000006, + 0x00000202,0x00000201,0x000000bb,0x00050084,0x00000006,0x00000203,0x00000202,0x000000c9, + 0x00050080,0x00000006,0x00000204,0x000001fc,0x00000203,0x0003003e,0x000000c0,0x00000204, + 0x0004003d,0x00000006,0x00000205,0x00000054,0x00050084,0x00000006,0x00000206,0x00000205, + 0x00000021,0x00050080,0x00000006,0x00000207,0x00000206,0x0000009b,0x00050087,0x00000006, + 0x00000209,0x00000207,0x00000208,0x0003003e,0x000000cc,0x00000209,0x0004003d,0x00000006, + 0x0000020a,0x000000c0,0x000500af,0x00000033,0x0000020b,0x0000020a,0x00000055,0x0004003d, + 0x00000006,0x0000020c,0x000000c0,0x000500b1,0x00000033,0x0000020d,0x0000020c,0x00000026, + 0x000500a7,0x00000033,0x0000020e,0x0000020b,0x0000020d,0x0004003d,0x00000006,0x0000020f, + 0x000000b6,0x000500af,0x00000033,0x00000210,0x0000020f,0x00000055,0x000500a7,0x00000033, + 0x00000211,0x0000020e,0x00000210,0x0004003d,0x00000006,0x00000212,0x000000b6,0x000500b1, + 0x00000033,0x00000213,0x00000212,0x00000028,0x000500a7,0x00000033,0x00000214,0x00000211, + 0x00000213,0x000300f7,0x00000216,0x00000000,0x000400fa,0x00000214,0x00000215,0x00000216, + 0x000200f8,0x00000215,0x0004003d,0x00000006,0x00000217,0x00000024,0x0004003d,0x00000006, + 0x00000218,0x000000cc,0x00050084,0x00000006,0x0000021a,0x00000218,0x00000219,0x00050080, + 0x00000006,0x0000021b,0x00000217,0x0000021a,0x0004003d,0x00000006,0x0000021c,0x000000c0, + 0x00050084,0x00000006,0x0000021d,0x0000021c,0x00000028,0x00050080,0x00000006,0x0000021e, + 0x0000021b,0x0000021d,0x0004003d,0x00000006,0x0000021f,0x000000b6,0x00050080,0x00000006, + 0x00000220,0x0000021e,0x0000021f,0x00060041,0x000000ed,0x00000221,0x000000e2,0x00000055, + 0x00000220,0x0004003d,0x00000047,0x00000222,0x00000221,0x00050041,0x000000f0,0x00000223, + 0x0000005b,0x00000013,0x0003003e,0x00000223,0x00000222,0x000200f9,0x00000216,0x000200f8, + 0x00000216,0x0004003d,0x00000006,0x00000224,0x000000b0,0x0004003d,0x00000006,0x00000225, + 0x00000054,0x00050084,0x00000006,0x00000226,0x00000225,0x00000021,0x00050080,0x00000006, + 0x00000227,0x00000226,0x00000124,0x0005008b,0x00000006,0x00000228,0x00000227,0x000000bb, + 0x00050084,0x00000006,0x00000229,0x00000228,0x000000bd,0x00050080,0x00000006,0x0000022a, + 0x00000224,0x00000229,0x0003003e,0x000000b6,0x0000022a,0x0004003d,0x00000006,0x0000022b, + 0x000000aa,0x0004003d,0x00000006,0x0000022c,0x00000054,0x00050084,0x00000006,0x0000022d, + 0x0000022c,0x00000021,0x00050080,0x00000006,0x0000022e,0x0000022d,0x00000124,0x0005008b, + 0x00000006,0x00000230,0x0000022e,0x0000022f,0x00050087,0x00000006,0x00000231,0x00000230, + 0x000000bb,0x00050084,0x00000006,0x00000232,0x00000231,0x000000c9,0x00050080,0x00000006, + 0x00000233,0x0000022b,0x00000232,0x0003003e,0x000000c0,0x00000233,0x0004003d,0x00000006, + 0x00000234,0x00000054,0x00050084,0x00000006,0x00000235,0x00000234,0x00000021,0x00050080, + 0x00000006,0x00000236,0x00000235,0x00000124,0x00050087,0x00000006,0x00000238,0x00000236, + 0x00000237,0x0003003e,0x000000cc,0x00000238,0x0004003d,0x00000006,0x00000239,0x000000c0, + 0x000500af,0x00000033,0x0000023a,0x00000239,0x00000055,0x0004003d,0x00000006,0x0000023b, + 0x000000c0,0x000500b1,0x00000033,0x0000023c,0x0000023b,0x00000026,0x000500a7,0x00000033, + 0x0000023d,0x0000023a,0x0000023c,0x0004003d,0x00000006,0x0000023e,0x000000b6,0x000500af, + 0x00000033,0x0000023f,0x0000023e,0x00000055,0x000500a7,0x00000033,0x00000240,0x0000023d, + 0x0000023f,0x0004003d,0x00000006,0x00000241,0x000000b6,0x000500b1,0x00000033,0x00000242, + 0x00000241,0x00000028,0x000500a7,0x00000033,0x00000243,0x00000240,0x00000242,0x000300f7, + 0x00000245,0x00000000,0x000400fa,0x00000243,0x00000244,0x00000245,0x000200f8,0x00000244, + 0x0004003d,0x00000006,0x00000246,0x00000024,0x0004003d,0x00000006,0x00000247,0x000000cc, + 0x00050084,0x00000006,0x00000249,0x00000247,0x00000248,0x00050080,0x00000006,0x0000024a, + 0x00000246,0x00000249,0x0004003d,0x00000006,0x0000024b,0x000000c0,0x00050084,0x00000006, + 0x0000024c,0x0000024b,0x00000028,0x00050080,0x00000006,0x0000024d,0x0000024a,0x0000024c, + 0x0004003d,0x00000006,0x0000024e,0x000000b6,0x00050080,0x00000006,0x0000024f,0x0000024d, + 0x0000024e,0x00060041,0x000000ed,0x00000250,0x000000e2,0x00000055,0x0000024f,0x0004003d, + 0x00000047,0x00000251,0x00000250,0x00050041,0x000000f0,0x00000252,0x0000005b,0x00000018, + 0x0003003e,0x00000252,0x00000251,0x000200f9,0x00000245,0x000200f8,0x00000245,0x0004003d, + 0x00000006,0x00000253,0x000000b0,0x0004003d,0x00000006,0x00000254,0x00000054,0x00050084, + 0x00000006,0x00000255,0x00000254,0x00000021,0x00050080,0x00000006,0x00000256,0x00000255, + 0x00000154,0x0005008b,0x00000006,0x00000257,0x00000256,0x000000bb,0x00050084,0x00000006, + 0x00000258,0x00000257,0x000000bd,0x00050080,0x00000006,0x00000259,0x00000253,0x00000258, + 0x0003003e,0x000000b6,0x00000259,0x0004003d,0x00000006,0x0000025a,0x000000aa,0x0004003d, + 0x00000006,0x0000025b,0x00000054,0x00050084,0x00000006,0x0000025c,0x0000025b,0x00000021, + 0x00050080,0x00000006,0x0000025d,0x0000025c,0x00000154,0x0005008b,0x00000006,0x0000025f, + 0x0000025d,0x0000025e,0x00050087,0x00000006,0x00000260,0x0000025f,0x000000bb,0x00050084, + 0x00000006,0x00000261,0x00000260,0x000000c9,0x00050080,0x00000006,0x00000262,0x0000025a, + 0x00000261,0x0003003e,0x000000c0,0x00000262,0x0004003d,0x00000006,0x00000263,0x00000054, + 0x00050084,0x00000006,0x00000264,0x00000263,0x00000021,0x00050080,0x00000006,0x00000265, + 0x00000264,0x00000154,0x00050087,0x00000006,0x00000267,0x00000265,0x00000266,0x0003003e, + 0x000000cc,0x00000267,0x0004003d,0x00000006,0x00000268,0x000000c0,0x000500af,0x00000033, + 0x00000269,0x00000268,0x00000055,0x0004003d,0x00000006,0x0000026a,0x000000c0,0x000500b1, + 0x00000033,0x0000026b,0x0000026a,0x00000026,0x000500a7,0x00000033,0x0000026c,0x00000269, + 0x0000026b,0x0004003d,0x00000006,0x0000026d,0x000000b6,0x000500af,0x00000033,0x0000026e, + 0x0000026d,0x00000055,0x000500a7,0x00000033,0x0000026f,0x0000026c,0x0000026e,0x0004003d, + 0x00000006,0x00000270,0x000000b6,0x000500b1,0x00000033,0x00000271,0x00000270,0x00000028, + 0x000500a7,0x00000033,0x00000272,0x0000026f,0x00000271,0x000300f7,0x00000274,0x00000000, + 0x000400fa,0x00000272,0x00000273,0x00000274,0x000200f8,0x00000273,0x0004003d,0x00000006, + 0x00000275,0x00000024,0x0004003d,0x00000006,0x00000276,0x000000cc,0x00050084,0x00000006, + 0x00000278,0x00000276,0x00000277,0x00050080,0x00000006,0x00000279,0x00000275,0x00000278, + 0x0004003d,0x00000006,0x0000027a,0x000000c0,0x00050084,0x00000006,0x0000027b,0x0000027a, + 0x00000028,0x00050080,0x00000006,0x0000027c,0x00000279,0x0000027b,0x0004003d,0x00000006, + 0x0000027d,0x000000b6,0x00050080,0x00000006,0x0000027e,0x0000027c,0x0000027d,0x00060041, + 0x000000ed,0x0000027f,0x000000e2,0x00000055,0x0000027e,0x0004003d,0x00000047,0x00000280, + 0x0000027f,0x00050041,0x000000f0,0x00000281,0x0000005b,0x00000180,0x0003003e,0x00000281, + 0x00000280,0x000200f9,0x00000274,0x000200f8,0x00000274,0x0004003d,0x00000048,0x00000282, + 0x0000005e,0x0004003d,0x00000048,0x00000283,0x0000005b,0x00050094,0x00000047,0x00000284, + 0x00000282,0x00000283,0x00050041,0x000000f0,0x00000285,0x0000004a,0x00000013,0x0004003d, + 0x00000047,0x00000286,0x00000285,0x00050081,0x00000047,0x00000287,0x00000286,0x00000284, + 0x00050041,0x000000f0,0x00000288,0x0000004a,0x00000013,0x0003003e,0x00000288,0x00000287, + 0x0004003d,0x00000048,0x00000289,0x0000006a,0x0004003d,0x00000048,0x0000028a,0x0000005b, + 0x00050094,0x00000047,0x0000028b,0x00000289,0x0000028a,0x00050041,0x000000f0,0x0000028c, + 0x0000004d,0x00000013,0x0004003d,0x00000047,0x0000028d,0x0000028c,0x00050081,0x00000047, + 0x0000028e,0x0000028d,0x0000028b,0x00050041,0x000000f0,0x0000028f,0x0000004d,0x00000013, + 0x0003003e,0x0000028f,0x0000028e,0x0004003d,0x00000048,0x00000290,0x00000071,0x0004003d, + 0x00000048,0x00000291,0x0000005b,0x00050094,0x00000047,0x00000292,0x00000290,0x00000291, + 0x00050041,0x000000f0,0x00000293,0x0000004e,0x00000013,0x0004003d,0x00000047,0x00000294, + 0x00000293,0x00050081,0x00000047,0x00000295,0x00000294,0x00000292,0x00050041,0x000000f0, + 0x00000296,0x0000004e,0x00000013,0x0003003e,0x00000296,0x00000295,0x0004003d,0x00000048, + 0x00000297,0x00000078,0x0004003d,0x00000048,0x00000298,0x0000005b,0x00050094,0x00000047, + 0x00000299,0x00000297,0x00000298,0x00050041,0x000000f0,0x0000029a,0x0000004f,0x00000013, + 0x0004003d,0x00000047,0x0000029b,0x0000029a,0x00050081,0x00000047,0x0000029c,0x0000029b, + 0x00000299,0x00050041,0x000000f0,0x0000029d,0x0000004f,0x00000013,0x0003003e,0x0000029d, + 0x0000029c,0x0004003d,0x00000048,0x0000029e,0x0000007f,0x0004003d,0x00000048,0x0000029f, + 0x0000005b,0x00050094,0x00000047,0x000002a0,0x0000029e,0x0000029f,0x00050041,0x000000f0, + 0x000002a1,0x00000050,0x00000013,0x0004003d,0x00000047,0x000002a2,0x000002a1,0x00050081, + 0x00000047,0x000002a3,0x000002a2,0x000002a0,0x00050041,0x000000f0,0x000002a4,0x00000050, + 0x00000013,0x0003003e,0x000002a4,0x000002a3,0x0004003d,0x00000048,0x000002a5,0x00000086, + 0x0004003d,0x00000048,0x000002a6,0x0000005b,0x00050094,0x00000047,0x000002a7,0x000002a5, + 0x000002a6,0x00050041,0x000000f0,0x000002a8,0x00000051,0x00000013,0x0004003d,0x00000047, + 0x000002a9,0x000002a8,0x00050081,0x00000047,0x000002aa,0x000002a9,0x000002a7,0x00050041, + 0x000000f0,0x000002ab,0x00000051,0x00000013,0x0003003e,0x000002ab,0x000002aa,0x0004003d, + 0x00000048,0x000002ac,0x0000008d,0x0004003d,0x00000048,0x000002ad,0x0000005b,0x00050094, + 0x00000047,0x000002ae,0x000002ac,0x000002ad,0x00050041,0x000000f0,0x000002af,0x00000052, + 0x00000013,0x0004003d,0x00000047,0x000002b0,0x000002af,0x00050081,0x00000047,0x000002b1, + 0x000002b0,0x000002ae,0x00050041,0x000000f0,0x000002b2,0x00000052,0x00000013,0x0003003e, + 0x000002b2,0x000002b1,0x0004003d,0x00000048,0x000002b3,0x00000094,0x0004003d,0x00000048, + 0x000002b4,0x0000005b,0x00050094,0x00000047,0x000002b5,0x000002b3,0x000002b4,0x00050041, + 0x000000f0,0x000002b6,0x00000053,0x00000013,0x0004003d,0x00000047,0x000002b7,0x000002b6, + 0x00050081,0x00000047,0x000002b8,0x000002b7,0x000002b5,0x00050041,0x000000f0,0x000002b9, + 0x00000053,0x00000013,0x0003003e,0x000002b9,0x000002b8,0x0004003d,0x00000006,0x000002ba, + 0x00000020,0x00050080,0x00000006,0x000002bb,0x000002ba,0x00000124,0x0005008b,0x00000006, + 0x000002bc,0x000002bb,0x000000a4,0x0003003e,0x000000a1,0x000002bc,0x0004003d,0x00000006, + 0x000002bd,0x00000020,0x00050080,0x00000006,0x000002be,0x000002bd,0x00000124,0x00050087, + 0x00000006,0x000002bf,0x000002be,0x000000a4,0x0003003e,0x000000a6,0x000002bf,0x0004003d, + 0x00000006,0x000002c0,0x000000a6,0x00050084,0x00000006,0x000002c1,0x000002c0,0x000000ac, + 0x00050082,0x00000006,0x000002c2,0x000002c1,0x000000ae,0x0003003e,0x000000aa,0x000002c2, + 0x0004003d,0x00000006,0x000002c3,0x000000a1,0x00050084,0x00000006,0x000002c4,0x000002c3, + 0x000000b2,0x00050082,0x00000006,0x000002c5,0x000002c4,0x000000b4,0x0003003e,0x000000b0, + 0x000002c5,0x0004003d,0x00000006,0x000002c6,0x000000b0,0x0004003d,0x00000006,0x000002c7, + 0x00000054,0x00050084,0x00000006,0x000002c8,0x000002c7,0x00000021,0x00050080,0x00000006, + 0x000002c9,0x000002c8,0x00000055,0x0005008b,0x00000006,0x000002ca,0x000002c9,0x000000bb, + 0x00050084,0x00000006,0x000002cb,0x000002ca,0x000000bd,0x00050080,0x00000006,0x000002cc, + 0x000002c6,0x000002cb,0x0003003e,0x000000b6,0x000002cc,0x0004003d,0x00000006,0x000002cd, + 0x000000aa,0x0004003d,0x00000006,0x000002ce,0x00000054,0x00050084,0x00000006,0x000002cf, + 0x000002ce,0x00000021,0x00050080,0x00000006,0x000002d0,0x000002cf,0x00000055,0x0005008b, + 0x00000006,0x000002d2,0x000002d0,0x000002d1,0x00050087,0x00000006,0x000002d3,0x000002d2, + 0x000000bb,0x00050084,0x00000006,0x000002d4,0x000002d3,0x000000c9,0x00050080,0x00000006, + 0x000002d5,0x000002cd,0x000002d4,0x0003003e,0x000000c0,0x000002d5,0x0004003d,0x00000006, + 0x000002d6,0x00000054,0x00050084,0x00000006,0x000002d7,0x000002d6,0x00000021,0x00050080, + 0x00000006,0x000002d8,0x000002d7,0x00000055,0x00050087,0x00000006,0x000002da,0x000002d8, + 0x000002d9,0x0003003e,0x000000cc,0x000002da,0x0004003d,0x00000006,0x000002db,0x000000c0, + 0x000500af,0x00000033,0x000002dc,0x000002db,0x00000055,0x0004003d,0x00000006,0x000002dd, + 0x000000c0,0x000500b1,0x00000033,0x000002de,0x000002dd,0x00000026,0x000500a7,0x00000033, + 0x000002df,0x000002dc,0x000002de,0x0004003d,0x00000006,0x000002e0,0x000000b6,0x000500af, + 0x00000033,0x000002e1,0x000002e0,0x00000055,0x000500a7,0x00000033,0x000002e2,0x000002df, + 0x000002e1,0x0004003d,0x00000006,0x000002e3,0x000000b6,0x000500b1,0x00000033,0x000002e4, + 0x000002e3,0x00000028,0x000500a7,0x00000033,0x000002e5,0x000002e2,0x000002e4,0x000300f7, + 0x000002e7,0x00000000,0x000400fa,0x000002e5,0x000002e6,0x000002e7,0x000200f8,0x000002e6, + 0x0004003d,0x00000006,0x000002e8,0x00000024,0x0004003d,0x00000006,0x000002e9,0x000000cc, + 0x00050084,0x00000006,0x000002eb,0x000002e9,0x000002ea,0x00050080,0x00000006,0x000002ec, + 0x000002e8,0x000002eb,0x0004003d,0x00000006,0x000002ed,0x000000c0,0x00050084,0x00000006, + 0x000002ee,0x000002ed,0x00000028,0x00050080,0x00000006,0x000002ef,0x000002ec,0x000002ee, + 0x0004003d,0x00000006,0x000002f0,0x000000b6,0x00050080,0x00000006,0x000002f1,0x000002ef, + 0x000002f0,0x00060041,0x000000ed,0x000002f2,0x000000e2,0x00000055,0x000002f1,0x0004003d, + 0x00000047,0x000002f3,0x000002f2,0x00050041,0x000000f0,0x000002f4,0x0000005c,0x0000000d, + 0x0003003e,0x000002f4,0x000002f3,0x000200f9,0x000002e7,0x000200f8,0x000002e7,0x0004003d, + 0x00000006,0x000002f5,0x000000b0,0x0004003d,0x00000006,0x000002f6,0x00000054,0x00050084, + 0x00000006,0x000002f7,0x000002f6,0x00000021,0x00050080,0x00000006,0x000002f8,0x000002f7, + 0x0000009b,0x0005008b,0x00000006,0x000002f9,0x000002f8,0x000000bb,0x00050084,0x00000006, + 0x000002fa,0x000002f9,0x000000bd,0x00050080,0x00000006,0x000002fb,0x000002f5,0x000002fa, + 0x0003003e,0x000000b6,0x000002fb,0x0004003d,0x00000006,0x000002fc,0x000000aa,0x0004003d, + 0x00000006,0x000002fd,0x00000054,0x00050084,0x00000006,0x000002fe,0x000002fd,0x00000021, + 0x00050080,0x00000006,0x000002ff,0x000002fe,0x0000009b,0x0005008b,0x00000006,0x00000301, + 0x000002ff,0x00000300,0x00050087,0x00000006,0x00000302,0x00000301,0x000000bb,0x00050084, + 0x00000006,0x00000303,0x00000302,0x000000c9,0x00050080,0x00000006,0x00000304,0x000002fc, + 0x00000303,0x0003003e,0x000000c0,0x00000304,0x0004003d,0x00000006,0x00000305,0x00000054, + 0x00050084,0x00000006,0x00000306,0x00000305,0x00000021,0x00050080,0x00000006,0x00000307, + 0x00000306,0x0000009b,0x00050087,0x00000006,0x00000309,0x00000307,0x00000308,0x0003003e, + 0x000000cc,0x00000309,0x0004003d,0x00000006,0x0000030a,0x000000c0,0x000500af,0x00000033, + 0x0000030b,0x0000030a,0x00000055,0x0004003d,0x00000006,0x0000030c,0x000000c0,0x000500b1, + 0x00000033,0x0000030d,0x0000030c,0x00000026,0x000500a7,0x00000033,0x0000030e,0x0000030b, + 0x0000030d,0x0004003d,0x00000006,0x0000030f,0x000000b6,0x000500af,0x00000033,0x00000310, + 0x0000030f,0x00000055,0x000500a7,0x00000033,0x00000311,0x0000030e,0x00000310,0x0004003d, + 0x00000006,0x00000312,0x000000b6,0x000500b1,0x00000033,0x00000313,0x00000312,0x00000028, + 0x000500a7,0x00000033,0x00000314,0x00000311,0x00000313,0x000300f7,0x00000316,0x00000000, + 0x000400fa,0x00000314,0x00000315,0x00000316,0x000200f8,0x00000315,0x0004003d,0x00000006, + 0x00000317,0x00000024,0x0004003d,0x00000006,0x00000318,0x000000cc,0x00050084,0x00000006, + 0x0000031a,0x00000318,0x00000319,0x00050080,0x00000006,0x0000031b,0x00000317,0x0000031a, + 0x0004003d,0x00000006,0x0000031c,0x000000c0,0x00050084,0x00000006,0x0000031d,0x0000031c, + 0x00000028,0x00050080,0x00000006,0x0000031e,0x0000031b,0x0000031d,0x0004003d,0x00000006, + 0x0000031f,0x000000b6,0x00050080,0x00000006,0x00000320,0x0000031e,0x0000031f,0x00060041, + 0x000000ed,0x00000321,0x000000e2,0x00000055,0x00000320,0x0004003d,0x00000047,0x00000322, + 0x00000321,0x00050041,0x000000f0,0x00000323,0x0000005c,0x00000013,0x0003003e,0x00000323, + 0x00000322,0x000200f9,0x00000316,0x000200f8,0x00000316,0x0004003d,0x00000006,0x00000324, + 0x000000b0,0x0004003d,0x00000006,0x00000325,0x00000054,0x00050084,0x00000006,0x00000326, + 0x00000325,0x00000021,0x00050080,0x00000006,0x00000327,0x00000326,0x00000124,0x0005008b, + 0x00000006,0x00000328,0x00000327,0x000000bb,0x00050084,0x00000006,0x00000329,0x00000328, + 0x000000bd,0x00050080,0x00000006,0x0000032a,0x00000324,0x00000329,0x0003003e,0x000000b6, + 0x0000032a,0x0004003d,0x00000006,0x0000032b,0x000000aa,0x0004003d,0x00000006,0x0000032c, + 0x00000054,0x00050084,0x00000006,0x0000032d,0x0000032c,0x00000021,0x00050080,0x00000006, + 0x0000032e,0x0000032d,0x00000124,0x0005008b,0x00000006,0x00000330,0x0000032e,0x0000032f, + 0x00050087,0x00000006,0x00000331,0x00000330,0x000000bb,0x00050084,0x00000006,0x00000332, + 0x00000331,0x000000c9,0x00050080,0x00000006,0x00000333,0x0000032b,0x00000332,0x0003003e, + 0x000000c0,0x00000333,0x0004003d,0x00000006,0x00000334,0x00000054,0x00050084,0x00000006, + 0x00000335,0x00000334,0x00000021,0x00050080,0x00000006,0x00000336,0x00000335,0x00000124, + 0x00050087,0x00000006,0x00000338,0x00000336,0x00000337,0x0003003e,0x000000cc,0x00000338, + 0x0004003d,0x00000006,0x00000339,0x000000c0,0x000500af,0x00000033,0x0000033a,0x00000339, + 0x00000055,0x0004003d,0x00000006,0x0000033b,0x000000c0,0x000500b1,0x00000033,0x0000033c, + 0x0000033b,0x00000026,0x000500a7,0x00000033,0x0000033d,0x0000033a,0x0000033c,0x0004003d, + 0x00000006,0x0000033e,0x000000b6,0x000500af,0x00000033,0x0000033f,0x0000033e,0x00000055, + 0x000500a7,0x00000033,0x00000340,0x0000033d,0x0000033f,0x0004003d,0x00000006,0x00000341, + 0x000000b6,0x000500b1,0x00000033,0x00000342,0x00000341,0x00000028,0x000500a7,0x00000033, + 0x00000343,0x00000340,0x00000342,0x000300f7,0x00000345,0x00000000,0x000400fa,0x00000343, + 0x00000344,0x00000345,0x000200f8,0x00000344,0x0004003d,0x00000006,0x00000346,0x00000024, + 0x0004003d,0x00000006,0x00000347,0x000000cc,0x00050084,0x00000006,0x00000349,0x00000347, + 0x00000348,0x00050080,0x00000006,0x0000034a,0x00000346,0x00000349,0x0004003d,0x00000006, + 0x0000034b,0x000000c0,0x00050084,0x00000006,0x0000034c,0x0000034b,0x00000028,0x00050080, + 0x00000006,0x0000034d,0x0000034a,0x0000034c,0x0004003d,0x00000006,0x0000034e,0x000000b6, + 0x00050080,0x00000006,0x0000034f,0x0000034d,0x0000034e,0x00060041,0x000000ed,0x00000350, + 0x000000e2,0x00000055,0x0000034f,0x0004003d,0x00000047,0x00000351,0x00000350,0x00050041, + 0x000000f0,0x00000352,0x0000005c,0x00000018,0x0003003e,0x00000352,0x00000351,0x000200f9, + 0x00000345,0x000200f8,0x00000345,0x0004003d,0x00000006,0x00000353,0x000000b0,0x0004003d, + 0x00000006,0x00000354,0x00000054,0x00050084,0x00000006,0x00000355,0x00000354,0x00000021, + 0x00050080,0x00000006,0x00000356,0x00000355,0x00000154,0x0005008b,0x00000006,0x00000357, + 0x00000356,0x000000bb,0x00050084,0x00000006,0x00000358,0x00000357,0x000000bd,0x00050080, + 0x00000006,0x00000359,0x00000353,0x00000358,0x0003003e,0x000000b6,0x00000359,0x0004003d, + 0x00000006,0x0000035a,0x000000aa,0x0004003d,0x00000006,0x0000035b,0x00000054,0x00050084, + 0x00000006,0x0000035c,0x0000035b,0x00000021,0x00050080,0x00000006,0x0000035d,0x0000035c, + 0x00000154,0x0005008b,0x00000006,0x0000035f,0x0000035d,0x0000035e,0x00050087,0x00000006, + 0x00000360,0x0000035f,0x000000bb,0x00050084,0x00000006,0x00000361,0x00000360,0x000000c9, + 0x00050080,0x00000006,0x00000362,0x0000035a,0x00000361,0x0003003e,0x000000c0,0x00000362, + 0x0004003d,0x00000006,0x00000363,0x00000054,0x00050084,0x00000006,0x00000364,0x00000363, + 0x00000021,0x00050080,0x00000006,0x00000365,0x00000364,0x00000154,0x00050087,0x00000006, + 0x00000367,0x00000365,0x00000366,0x0003003e,0x000000cc,0x00000367,0x0004003d,0x00000006, + 0x00000368,0x000000c0,0x000500af,0x00000033,0x00000369,0x00000368,0x00000055,0x0004003d, + 0x00000006,0x0000036a,0x000000c0,0x000500b1,0x00000033,0x0000036b,0x0000036a,0x00000026, + 0x000500a7,0x00000033,0x0000036c,0x00000369,0x0000036b,0x0004003d,0x00000006,0x0000036d, + 0x000000b6,0x000500af,0x00000033,0x0000036e,0x0000036d,0x00000055,0x000500a7,0x00000033, + 0x0000036f,0x0000036c,0x0000036e,0x0004003d,0x00000006,0x00000370,0x000000b6,0x000500b1, + 0x00000033,0x00000371,0x00000370,0x00000028,0x000500a7,0x00000033,0x00000372,0x0000036f, + 0x00000371,0x000300f7,0x00000374,0x00000000,0x000400fa,0x00000372,0x00000373,0x00000374, + 0x000200f8,0x00000373,0x0004003d,0x00000006,0x00000375,0x00000024,0x0004003d,0x00000006, + 0x00000376,0x000000cc,0x00050084,0x00000006,0x00000378,0x00000376,0x00000377,0x00050080, + 0x00000006,0x00000379,0x00000375,0x00000378,0x0004003d,0x00000006,0x0000037a,0x000000c0, + 0x00050084,0x00000006,0x0000037b,0x0000037a,0x00000028,0x00050080,0x00000006,0x0000037c, + 0x00000379,0x0000037b,0x0004003d,0x00000006,0x0000037d,0x000000b6,0x00050080,0x00000006, + 0x0000037e,0x0000037c,0x0000037d,0x00060041,0x000000ed,0x0000037f,0x000000e2,0x00000055, + 0x0000037e,0x0004003d,0x00000047,0x00000380,0x0000037f,0x00050041,0x000000f0,0x00000381, + 0x0000005c,0x00000180,0x0003003e,0x00000381,0x00000380,0x000200f9,0x00000374,0x000200f8, + 0x00000374,0x0004003d,0x00000048,0x00000382,0x0000005e,0x0004003d,0x00000048,0x00000383, + 0x0000005c,0x00050094,0x00000047,0x00000384,0x00000382,0x00000383,0x00050041,0x000000f0, + 0x00000385,0x0000004a,0x00000018,0x0004003d,0x00000047,0x00000386,0x00000385,0x00050081, + 0x00000047,0x00000387,0x00000386,0x00000384,0x00050041,0x000000f0,0x00000388,0x0000004a, + 0x00000018,0x0003003e,0x00000388,0x00000387,0x0004003d,0x00000048,0x00000389,0x0000006a, + 0x0004003d,0x00000048,0x0000038a,0x0000005c,0x00050094,0x00000047,0x0000038b,0x00000389, + 0x0000038a,0x00050041,0x000000f0,0x0000038c,0x0000004d,0x00000018,0x0004003d,0x00000047, + 0x0000038d,0x0000038c,0x00050081,0x00000047,0x0000038e,0x0000038d,0x0000038b,0x00050041, + 0x000000f0,0x0000038f,0x0000004d,0x00000018,0x0003003e,0x0000038f,0x0000038e,0x0004003d, + 0x00000048,0x00000390,0x00000071,0x0004003d,0x00000048,0x00000391,0x0000005c,0x00050094, + 0x00000047,0x00000392,0x00000390,0x00000391,0x00050041,0x000000f0,0x00000393,0x0000004e, + 0x00000018,0x0004003d,0x00000047,0x00000394,0x00000393,0x00050081,0x00000047,0x00000395, + 0x00000394,0x00000392,0x00050041,0x000000f0,0x00000396,0x0000004e,0x00000018,0x0003003e, + 0x00000396,0x00000395,0x0004003d,0x00000048,0x00000397,0x00000078,0x0004003d,0x00000048, + 0x00000398,0x0000005c,0x00050094,0x00000047,0x00000399,0x00000397,0x00000398,0x00050041, + 0x000000f0,0x0000039a,0x0000004f,0x00000018,0x0004003d,0x00000047,0x0000039b,0x0000039a, + 0x00050081,0x00000047,0x0000039c,0x0000039b,0x00000399,0x00050041,0x000000f0,0x0000039d, + 0x0000004f,0x00000018,0x0003003e,0x0000039d,0x0000039c,0x0004003d,0x00000048,0x0000039e, + 0x0000007f,0x0004003d,0x00000048,0x0000039f,0x0000005c,0x00050094,0x00000047,0x000003a0, + 0x0000039e,0x0000039f,0x00050041,0x000000f0,0x000003a1,0x00000050,0x00000018,0x0004003d, + 0x00000047,0x000003a2,0x000003a1,0x00050081,0x00000047,0x000003a3,0x000003a2,0x000003a0, + 0x00050041,0x000000f0,0x000003a4,0x00000050,0x00000018,0x0003003e,0x000003a4,0x000003a3, + 0x0004003d,0x00000048,0x000003a5,0x00000086,0x0004003d,0x00000048,0x000003a6,0x0000005c, + 0x00050094,0x00000047,0x000003a7,0x000003a5,0x000003a6,0x00050041,0x000000f0,0x000003a8, + 0x00000051,0x00000018,0x0004003d,0x00000047,0x000003a9,0x000003a8,0x00050081,0x00000047, + 0x000003aa,0x000003a9,0x000003a7,0x00050041,0x000000f0,0x000003ab,0x00000051,0x00000018, + 0x0003003e,0x000003ab,0x000003aa,0x0004003d,0x00000048,0x000003ac,0x0000008d,0x0004003d, + 0x00000048,0x000003ad,0x0000005c,0x00050094,0x00000047,0x000003ae,0x000003ac,0x000003ad, + 0x00050041,0x000000f0,0x000003af,0x00000052,0x00000018,0x0004003d,0x00000047,0x000003b0, + 0x000003af,0x00050081,0x00000047,0x000003b1,0x000003b0,0x000003ae,0x00050041,0x000000f0, + 0x000003b2,0x00000052,0x00000018,0x0003003e,0x000003b2,0x000003b1,0x0004003d,0x00000048, + 0x000003b3,0x00000094,0x0004003d,0x00000048,0x000003b4,0x0000005c,0x00050094,0x00000047, + 0x000003b5,0x000003b3,0x000003b4,0x00050041,0x000000f0,0x000003b6,0x00000053,0x00000018, + 0x0004003d,0x00000047,0x000003b7,0x000003b6,0x00050081,0x00000047,0x000003b8,0x000003b7, + 0x000003b5,0x00050041,0x000000f0,0x000003b9,0x00000053,0x00000018,0x0003003e,0x000003b9, + 0x000003b8,0x0004003d,0x00000006,0x000003ba,0x00000020,0x00050080,0x00000006,0x000003bb, + 0x000003ba,0x00000154,0x0005008b,0x00000006,0x000003bc,0x000003bb,0x000000a4,0x0003003e, + 0x000000a1,0x000003bc,0x0004003d,0x00000006,0x000003bd,0x00000020,0x00050080,0x00000006, + 0x000003be,0x000003bd,0x00000154,0x00050087,0x00000006,0x000003bf,0x000003be,0x000000a4, + 0x0003003e,0x000000a6,0x000003bf,0x0004003d,0x00000006,0x000003c0,0x000000a6,0x00050084, + 0x00000006,0x000003c1,0x000003c0,0x000000ac,0x00050082,0x00000006,0x000003c2,0x000003c1, + 0x000000ae,0x0003003e,0x000000aa,0x000003c2,0x0004003d,0x00000006,0x000003c3,0x000000a1, + 0x00050084,0x00000006,0x000003c4,0x000003c3,0x000000b2,0x00050082,0x00000006,0x000003c5, + 0x000003c4,0x000000b4,0x0003003e,0x000000b0,0x000003c5,0x0004003d,0x00000006,0x000003c6, + 0x000000b0,0x0004003d,0x00000006,0x000003c7,0x00000054,0x00050084,0x00000006,0x000003c8, + 0x000003c7,0x00000021,0x00050080,0x00000006,0x000003c9,0x000003c8,0x00000055,0x0005008b, + 0x00000006,0x000003ca,0x000003c9,0x000000bb,0x00050084,0x00000006,0x000003cb,0x000003ca, + 0x000000bd,0x00050080,0x00000006,0x000003cc,0x000003c6,0x000003cb,0x0003003e,0x000000b6, + 0x000003cc,0x0004003d,0x00000006,0x000003cd,0x000000aa,0x0004003d,0x00000006,0x000003ce, + 0x00000054,0x00050084,0x00000006,0x000003cf,0x000003ce,0x00000021,0x00050080,0x00000006, + 0x000003d0,0x000003cf,0x00000055,0x0005008b,0x00000006,0x000003d2,0x000003d0,0x000003d1, + 0x00050087,0x00000006,0x000003d3,0x000003d2,0x000000bb,0x00050084,0x00000006,0x000003d4, + 0x000003d3,0x000000c9,0x00050080,0x00000006,0x000003d5,0x000003cd,0x000003d4,0x0003003e, + 0x000000c0,0x000003d5,0x0004003d,0x00000006,0x000003d6,0x00000054,0x00050084,0x00000006, + 0x000003d7,0x000003d6,0x00000021,0x00050080,0x00000006,0x000003d8,0x000003d7,0x00000055, + 0x00050087,0x00000006,0x000003da,0x000003d8,0x000003d9,0x0003003e,0x000000cc,0x000003da, + 0x0004003d,0x00000006,0x000003db,0x000000c0,0x000500af,0x00000033,0x000003dc,0x000003db, + 0x00000055,0x0004003d,0x00000006,0x000003dd,0x000000c0,0x000500b1,0x00000033,0x000003de, + 0x000003dd,0x00000026,0x000500a7,0x00000033,0x000003df,0x000003dc,0x000003de,0x0004003d, + 0x00000006,0x000003e0,0x000000b6,0x000500af,0x00000033,0x000003e1,0x000003e0,0x00000055, + 0x000500a7,0x00000033,0x000003e2,0x000003df,0x000003e1,0x0004003d,0x00000006,0x000003e3, + 0x000000b6,0x000500b1,0x00000033,0x000003e4,0x000003e3,0x00000028,0x000500a7,0x00000033, + 0x000003e5,0x000003e2,0x000003e4,0x000300f7,0x000003e7,0x00000000,0x000400fa,0x000003e5, + 0x000003e6,0x000003e7,0x000200f8,0x000003e6,0x0004003d,0x00000006,0x000003e8,0x00000024, + 0x0004003d,0x00000006,0x000003e9,0x000000cc,0x00050084,0x00000006,0x000003eb,0x000003e9, + 0x000003ea,0x00050080,0x00000006,0x000003ec,0x000003e8,0x000003eb,0x0004003d,0x00000006, + 0x000003ed,0x000000c0,0x00050084,0x00000006,0x000003ee,0x000003ed,0x00000028,0x00050080, + 0x00000006,0x000003ef,0x000003ec,0x000003ee,0x0004003d,0x00000006,0x000003f0,0x000000b6, + 0x00050080,0x00000006,0x000003f1,0x000003ef,0x000003f0,0x00060041,0x000000ed,0x000003f2, + 0x000000e2,0x00000055,0x000003f1,0x0004003d,0x00000047,0x000003f3,0x000003f2,0x00050041, + 0x000000f0,0x000003f4,0x0000005d,0x0000000d,0x0003003e,0x000003f4,0x000003f3,0x000200f9, + 0x000003e7,0x000200f8,0x000003e7,0x0004003d,0x00000006,0x000003f5,0x000000b0,0x0004003d, + 0x00000006,0x000003f6,0x00000054,0x00050084,0x00000006,0x000003f7,0x000003f6,0x00000021, + 0x00050080,0x00000006,0x000003f8,0x000003f7,0x0000009b,0x0005008b,0x00000006,0x000003f9, + 0x000003f8,0x000000bb,0x00050084,0x00000006,0x000003fa,0x000003f9,0x000000bd,0x00050080, + 0x00000006,0x000003fb,0x000003f5,0x000003fa,0x0003003e,0x000000b6,0x000003fb,0x0004003d, + 0x00000006,0x000003fc,0x000000aa,0x0004003d,0x00000006,0x000003fd,0x00000054,0x00050084, + 0x00000006,0x000003fe,0x000003fd,0x00000021,0x00050080,0x00000006,0x000003ff,0x000003fe, + 0x0000009b,0x0005008b,0x00000006,0x00000401,0x000003ff,0x00000400,0x00050087,0x00000006, + 0x00000402,0x00000401,0x000000bb,0x00050084,0x00000006,0x00000403,0x00000402,0x000000c9, + 0x00050080,0x00000006,0x00000404,0x000003fc,0x00000403,0x0003003e,0x000000c0,0x00000404, + 0x0004003d,0x00000006,0x00000405,0x00000054,0x00050084,0x00000006,0x00000406,0x00000405, + 0x00000021,0x00050080,0x00000006,0x00000407,0x00000406,0x0000009b,0x00050087,0x00000006, + 0x00000409,0x00000407,0x00000408,0x0003003e,0x000000cc,0x00000409,0x0004003d,0x00000006, + 0x0000040a,0x000000c0,0x000500af,0x00000033,0x0000040b,0x0000040a,0x00000055,0x0004003d, + 0x00000006,0x0000040c,0x000000c0,0x000500b1,0x00000033,0x0000040d,0x0000040c,0x00000026, + 0x000500a7,0x00000033,0x0000040e,0x0000040b,0x0000040d,0x0004003d,0x00000006,0x0000040f, + 0x000000b6,0x000500af,0x00000033,0x00000410,0x0000040f,0x00000055,0x000500a7,0x00000033, + 0x00000411,0x0000040e,0x00000410,0x0004003d,0x00000006,0x00000412,0x000000b6,0x000500b1, + 0x00000033,0x00000413,0x00000412,0x00000028,0x000500a7,0x00000033,0x00000414,0x00000411, + 0x00000413,0x000300f7,0x00000416,0x00000000,0x000400fa,0x00000414,0x00000415,0x00000416, + 0x000200f8,0x00000415,0x0004003d,0x00000006,0x00000417,0x00000024,0x0004003d,0x00000006, + 0x00000418,0x000000cc,0x00050084,0x00000006,0x0000041a,0x00000418,0x00000419,0x00050080, + 0x00000006,0x0000041b,0x00000417,0x0000041a,0x0004003d,0x00000006,0x0000041c,0x000000c0, + 0x00050084,0x00000006,0x0000041d,0x0000041c,0x00000028,0x00050080,0x00000006,0x0000041e, + 0x0000041b,0x0000041d,0x0004003d,0x00000006,0x0000041f,0x000000b6,0x00050080,0x00000006, + 0x00000420,0x0000041e,0x0000041f,0x00060041,0x000000ed,0x00000421,0x000000e2,0x00000055, + 0x00000420,0x0004003d,0x00000047,0x00000422,0x00000421,0x00050041,0x000000f0,0x00000423, + 0x0000005d,0x00000013,0x0003003e,0x00000423,0x00000422,0x000200f9,0x00000416,0x000200f8, + 0x00000416,0x0004003d,0x00000006,0x00000424,0x000000b0,0x0004003d,0x00000006,0x00000425, + 0x00000054,0x00050084,0x00000006,0x00000426,0x00000425,0x00000021,0x00050080,0x00000006, + 0x00000427,0x00000426,0x00000124,0x0005008b,0x00000006,0x00000428,0x00000427,0x000000bb, + 0x00050084,0x00000006,0x00000429,0x00000428,0x000000bd,0x00050080,0x00000006,0x0000042a, + 0x00000424,0x00000429,0x0003003e,0x000000b6,0x0000042a,0x0004003d,0x00000006,0x0000042b, + 0x000000aa,0x0004003d,0x00000006,0x0000042c,0x00000054,0x00050084,0x00000006,0x0000042d, + 0x0000042c,0x00000021,0x00050080,0x00000006,0x0000042e,0x0000042d,0x00000124,0x0005008b, + 0x00000006,0x00000430,0x0000042e,0x0000042f,0x00050087,0x00000006,0x00000431,0x00000430, + 0x000000bb,0x00050084,0x00000006,0x00000432,0x00000431,0x000000c9,0x00050080,0x00000006, + 0x00000433,0x0000042b,0x00000432,0x0003003e,0x000000c0,0x00000433,0x0004003d,0x00000006, + 0x00000434,0x00000054,0x00050084,0x00000006,0x00000435,0x00000434,0x00000021,0x00050080, + 0x00000006,0x00000436,0x00000435,0x00000124,0x00050087,0x00000006,0x00000438,0x00000436, + 0x00000437,0x0003003e,0x000000cc,0x00000438,0x0004003d,0x00000006,0x00000439,0x000000c0, + 0x000500af,0x00000033,0x0000043a,0x00000439,0x00000055,0x0004003d,0x00000006,0x0000043b, + 0x000000c0,0x000500b1,0x00000033,0x0000043c,0x0000043b,0x00000026,0x000500a7,0x00000033, + 0x0000043d,0x0000043a,0x0000043c,0x0004003d,0x00000006,0x0000043e,0x000000b6,0x000500af, + 0x00000033,0x0000043f,0x0000043e,0x00000055,0x000500a7,0x00000033,0x00000440,0x0000043d, + 0x0000043f,0x0004003d,0x00000006,0x00000441,0x000000b6,0x000500b1,0x00000033,0x00000442, + 0x00000441,0x00000028,0x000500a7,0x00000033,0x00000443,0x00000440,0x00000442,0x000300f7, + 0x00000445,0x00000000,0x000400fa,0x00000443,0x00000444,0x00000445,0x000200f8,0x00000444, + 0x0004003d,0x00000006,0x00000446,0x00000024,0x0004003d,0x00000006,0x00000447,0x000000cc, + 0x00050084,0x00000006,0x00000449,0x00000447,0x00000448,0x00050080,0x00000006,0x0000044a, + 0x00000446,0x00000449,0x0004003d,0x00000006,0x0000044b,0x000000c0,0x00050084,0x00000006, + 0x0000044c,0x0000044b,0x00000028,0x00050080,0x00000006,0x0000044d,0x0000044a,0x0000044c, + 0x0004003d,0x00000006,0x0000044e,0x000000b6,0x00050080,0x00000006,0x0000044f,0x0000044d, + 0x0000044e,0x00060041,0x000000ed,0x00000450,0x000000e2,0x00000055,0x0000044f,0x0004003d, + 0x00000047,0x00000451,0x00000450,0x00050041,0x000000f0,0x00000452,0x0000005d,0x00000018, + 0x0003003e,0x00000452,0x00000451,0x000200f9,0x00000445,0x000200f8,0x00000445,0x0004003d, + 0x00000006,0x00000453,0x000000b0,0x0004003d,0x00000006,0x00000454,0x00000054,0x00050084, + 0x00000006,0x00000455,0x00000454,0x00000021,0x00050080,0x00000006,0x00000456,0x00000455, + 0x00000154,0x0005008b,0x00000006,0x00000457,0x00000456,0x000000bb,0x00050084,0x00000006, + 0x00000458,0x00000457,0x000000bd,0x00050080,0x00000006,0x00000459,0x00000453,0x00000458, + 0x0003003e,0x000000b6,0x00000459,0x0004003d,0x00000006,0x0000045a,0x000000aa,0x0004003d, + 0x00000006,0x0000045b,0x00000054,0x00050084,0x00000006,0x0000045c,0x0000045b,0x00000021, + 0x00050080,0x00000006,0x0000045d,0x0000045c,0x00000154,0x0005008b,0x00000006,0x0000045f, + 0x0000045d,0x0000045e,0x00050087,0x00000006,0x00000460,0x0000045f,0x000000bb,0x00050084, + 0x00000006,0x00000461,0x00000460,0x000000c9,0x00050080,0x00000006,0x00000462,0x0000045a, + 0x00000461,0x0003003e,0x000000c0,0x00000462,0x0004003d,0x00000006,0x00000463,0x00000054, + 0x00050084,0x00000006,0x00000464,0x00000463,0x00000021,0x00050080,0x00000006,0x00000465, + 0x00000464,0x00000154,0x00050087,0x00000006,0x00000467,0x00000465,0x00000466,0x0003003e, + 0x000000cc,0x00000467,0x0004003d,0x00000006,0x00000468,0x000000c0,0x000500af,0x00000033, + 0x00000469,0x00000468,0x00000055,0x0004003d,0x00000006,0x0000046a,0x000000c0,0x000500b1, + 0x00000033,0x0000046b,0x0000046a,0x00000026,0x000500a7,0x00000033,0x0000046c,0x00000469, + 0x0000046b,0x0004003d,0x00000006,0x0000046d,0x000000b6,0x000500af,0x00000033,0x0000046e, + 0x0000046d,0x00000055,0x000500a7,0x00000033,0x0000046f,0x0000046c,0x0000046e,0x0004003d, + 0x00000006,0x00000470,0x000000b6,0x000500b1,0x00000033,0x00000471,0x00000470,0x00000028, + 0x000500a7,0x00000033,0x00000472,0x0000046f,0x00000471,0x000300f7,0x00000474,0x00000000, + 0x000400fa,0x00000472,0x00000473,0x00000474,0x000200f8,0x00000473,0x0004003d,0x00000006, + 0x00000475,0x00000024,0x0004003d,0x00000006,0x00000476,0x000000cc,0x00050084,0x00000006, + 0x00000478,0x00000476,0x00000477,0x00050080,0x00000006,0x00000479,0x00000475,0x00000478, + 0x0004003d,0x00000006,0x0000047a,0x000000c0,0x00050084,0x00000006,0x0000047b,0x0000047a, + 0x00000028,0x00050080,0x00000006,0x0000047c,0x00000479,0x0000047b,0x0004003d,0x00000006, + 0x0000047d,0x000000b6,0x00050080,0x00000006,0x0000047e,0x0000047c,0x0000047d,0x00060041, + 0x000000ed,0x0000047f,0x000000e2,0x00000055,0x0000047e,0x0004003d,0x00000047,0x00000480, + 0x0000047f,0x00050041,0x000000f0,0x00000481,0x0000005d,0x00000180,0x0003003e,0x00000481, + 0x00000480,0x000200f9,0x00000474,0x000200f8,0x00000474,0x0004003d,0x00000048,0x00000482, + 0x0000005e,0x0004003d,0x00000048,0x00000483,0x0000005d,0x00050094,0x00000047,0x00000484, + 0x00000482,0x00000483,0x00050041,0x000000f0,0x00000485,0x0000004a,0x00000180,0x0004003d, + 0x00000047,0x00000486,0x00000485,0x00050081,0x00000047,0x00000487,0x00000486,0x00000484, + 0x00050041,0x000000f0,0x00000488,0x0000004a,0x00000180,0x0003003e,0x00000488,0x00000487, + 0x0004003d,0x00000048,0x00000489,0x0000006a,0x0004003d,0x00000048,0x0000048a,0x0000005d, + 0x00050094,0x00000047,0x0000048b,0x00000489,0x0000048a,0x00050041,0x000000f0,0x0000048c, + 0x0000004d,0x00000180,0x0004003d,0x00000047,0x0000048d,0x0000048c,0x00050081,0x00000047, + 0x0000048e,0x0000048d,0x0000048b,0x00050041,0x000000f0,0x0000048f,0x0000004d,0x00000180, + 0x0003003e,0x0000048f,0x0000048e,0x0004003d,0x00000048,0x00000490,0x00000071,0x0004003d, + 0x00000048,0x00000491,0x0000005d,0x00050094,0x00000047,0x00000492,0x00000490,0x00000491, + 0x00050041,0x000000f0,0x00000493,0x0000004e,0x00000180,0x0004003d,0x00000047,0x00000494, + 0x00000493,0x00050081,0x00000047,0x00000495,0x00000494,0x00000492,0x00050041,0x000000f0, + 0x00000496,0x0000004e,0x00000180,0x0003003e,0x00000496,0x00000495,0x0004003d,0x00000048, + 0x00000497,0x00000078,0x0004003d,0x00000048,0x00000498,0x0000005d,0x00050094,0x00000047, + 0x00000499,0x00000497,0x00000498,0x00050041,0x000000f0,0x0000049a,0x0000004f,0x00000180, + 0x0004003d,0x00000047,0x0000049b,0x0000049a,0x00050081,0x00000047,0x0000049c,0x0000049b, + 0x00000499,0x00050041,0x000000f0,0x0000049d,0x0000004f,0x00000180,0x0003003e,0x0000049d, + 0x0000049c,0x0004003d,0x00000048,0x0000049e,0x0000007f,0x0004003d,0x00000048,0x0000049f, + 0x0000005d,0x00050094,0x00000047,0x000004a0,0x0000049e,0x0000049f,0x00050041,0x000000f0, + 0x000004a1,0x00000050,0x00000180,0x0004003d,0x00000047,0x000004a2,0x000004a1,0x00050081, + 0x00000047,0x000004a3,0x000004a2,0x000004a0,0x00050041,0x000000f0,0x000004a4,0x00000050, + 0x00000180,0x0003003e,0x000004a4,0x000004a3,0x0004003d,0x00000048,0x000004a5,0x00000086, + 0x0004003d,0x00000048,0x000004a6,0x0000005d,0x00050094,0x00000047,0x000004a7,0x000004a5, + 0x000004a6,0x00050041,0x000000f0,0x000004a8,0x00000051,0x00000180,0x0004003d,0x00000047, + 0x000004a9,0x000004a8,0x00050081,0x00000047,0x000004aa,0x000004a9,0x000004a7,0x00050041, + 0x000000f0,0x000004ab,0x00000051,0x00000180,0x0003003e,0x000004ab,0x000004aa,0x0004003d, + 0x00000048,0x000004ac,0x0000008d,0x0004003d,0x00000048,0x000004ad,0x0000005d,0x00050094, + 0x00000047,0x000004ae,0x000004ac,0x000004ad,0x00050041,0x000000f0,0x000004af,0x00000052, + 0x00000180,0x0004003d,0x00000047,0x000004b0,0x000004af,0x00050081,0x00000047,0x000004b1, + 0x000004b0,0x000004ae,0x00050041,0x000000f0,0x000004b2,0x00000052,0x00000180,0x0003003e, + 0x000004b2,0x000004b1,0x0004003d,0x00000048,0x000004b3,0x00000094,0x0004003d,0x00000048, + 0x000004b4,0x0000005d,0x00050094,0x00000047,0x000004b5,0x000004b3,0x000004b4,0x00050041, + 0x000000f0,0x000004b6,0x00000053,0x00000180,0x0004003d,0x00000047,0x000004b7,0x000004b6, + 0x00050081,0x00000047,0x000004b8,0x000004b7,0x000004b5,0x00050041,0x000000f0,0x000004b9, + 0x00000053,0x00000180,0x0003003e,0x000004b9,0x000004b8,0x0004003d,0x00000006,0x000004ba, + 0x00000054,0x00050080,0x00000006,0x000004bb,0x000004ba,0x0000009b,0x0003003e,0x00000054, + 0x000004bb,0x000200f9,0x00000059,0x000200f8,0x00000059,0x0004003d,0x00000006,0x000004bc, + 0x00000054,0x0004003d,0x00000006,0x000004bd,0x0000003e,0x000500b1,0x00000033,0x000004be, + 0x000004bc,0x000004bd,0x000400fa,0x000004be,0x00000056,0x00000058,0x000200f8,0x00000058, + 0x0004003d,0x00000006,0x000004c3,0x0000002c,0x0004003d,0x00000006,0x000004c4,0x0000001c, + 0x00050080,0x00000006,0x000004c5,0x000004c4,0x00000055,0x00050084,0x00000006,0x000004c6, + 0x000004c5,0x0000002e,0x00050087,0x00000006,0x000004c7,0x000004c6,0x00000021,0x00050080, + 0x00000006,0x000004c8,0x000004c3,0x000004c7,0x0004003d,0x00000006,0x000004c9,0x00000012, + 0x00050080,0x00000006,0x000004ca,0x000004c8,0x000004c9,0x0004003d,0x00000048,0x000004cb, + 0x0000004a,0x00060041,0x00000064,0x000004cc,0x000004c2,0x00000055,0x000004ca,0x0003003e, + 0x000004cc,0x000004cb,0x0004003d,0x00000006,0x000004cd,0x0000002c,0x0004003d,0x00000006, + 0x000004ce,0x0000001c,0x00050080,0x00000006,0x000004cf,0x000004ce,0x0000009b,0x00050084, + 0x00000006,0x000004d0,0x000004cf,0x0000002e,0x00050087,0x00000006,0x000004d1,0x000004d0, + 0x00000021,0x00050080,0x00000006,0x000004d2,0x000004cd,0x000004d1,0x0004003d,0x00000006, + 0x000004d3,0x00000012,0x00050080,0x00000006,0x000004d4,0x000004d2,0x000004d3,0x0004003d, + 0x00000048,0x000004d5,0x0000004d,0x00060041,0x00000064,0x000004d6,0x000004c2,0x00000055, + 0x000004d4,0x0003003e,0x000004d6,0x000004d5,0x0004003d,0x00000006,0x000004d7,0x0000002c, + 0x0004003d,0x00000006,0x000004d8,0x0000001c,0x00050080,0x00000006,0x000004d9,0x000004d8, + 0x00000124,0x00050084,0x00000006,0x000004da,0x000004d9,0x0000002e,0x00050087,0x00000006, + 0x000004db,0x000004da,0x00000021,0x00050080,0x00000006,0x000004dc,0x000004d7,0x000004db, + 0x0004003d,0x00000006,0x000004dd,0x00000012,0x00050080,0x00000006,0x000004de,0x000004dc, + 0x000004dd,0x0004003d,0x00000048,0x000004df,0x0000004e,0x00060041,0x00000064,0x000004e0, + 0x000004c2,0x00000055,0x000004de,0x0003003e,0x000004e0,0x000004df,0x0004003d,0x00000006, + 0x000004e1,0x0000002c,0x0004003d,0x00000006,0x000004e2,0x0000001c,0x00050080,0x00000006, + 0x000004e3,0x000004e2,0x00000154,0x00050084,0x00000006,0x000004e4,0x000004e3,0x0000002e, + 0x00050087,0x00000006,0x000004e5,0x000004e4,0x00000021,0x00050080,0x00000006,0x000004e6, + 0x000004e1,0x000004e5,0x0004003d,0x00000006,0x000004e7,0x00000012,0x00050080,0x00000006, + 0x000004e8,0x000004e6,0x000004e7,0x0004003d,0x00000048,0x000004e9,0x0000004f,0x00060041, + 0x00000064,0x000004ea,0x000004c2,0x00000055,0x000004e8,0x0003003e,0x000004ea,0x000004e9, + 0x0004003d,0x00000006,0x000004eb,0x0000002c,0x0004003d,0x00000006,0x000004ec,0x0000001c, + 0x00050080,0x00000006,0x000004ed,0x000004ec,0x00000021,0x00050084,0x00000006,0x000004ee, + 0x000004ed,0x0000002e,0x00050087,0x00000006,0x000004ef,0x000004ee,0x00000021,0x00050080, + 0x00000006,0x000004f0,0x000004eb,0x000004ef,0x0004003d,0x00000006,0x000004f1,0x00000012, + 0x00050080,0x00000006,0x000004f2,0x000004f0,0x000004f1,0x0004003d,0x00000048,0x000004f3, + 0x00000050,0x00060041,0x00000064,0x000004f4,0x000004c2,0x00000055,0x000004f2,0x0003003e, + 0x000004f4,0x000004f3,0x0004003d,0x00000006,0x000004f5,0x0000002c,0x0004003d,0x00000006, + 0x000004f6,0x0000001c,0x00050080,0x00000006,0x000004f8,0x000004f6,0x000004f7,0x00050084, + 0x00000006,0x000004f9,0x000004f8,0x0000002e,0x00050087,0x00000006,0x000004fa,0x000004f9, + 0x00000021,0x00050080,0x00000006,0x000004fb,0x000004f5,0x000004fa,0x0004003d,0x00000006, + 0x000004fc,0x00000012,0x00050080,0x00000006,0x000004fd,0x000004fb,0x000004fc,0x0004003d, + 0x00000048,0x000004fe,0x00000051,0x00060041,0x00000064,0x000004ff,0x000004c2,0x00000055, + 0x000004fd,0x0003003e,0x000004ff,0x000004fe,0x0004003d,0x00000006,0x00000500,0x0000002c, + 0x0004003d,0x00000006,0x00000501,0x0000001c,0x00050080,0x00000006,0x00000503,0x00000501, + 0x00000502,0x00050084,0x00000006,0x00000504,0x00000503,0x0000002e,0x00050087,0x00000006, + 0x00000505,0x00000504,0x00000021,0x00050080,0x00000006,0x00000506,0x00000500,0x00000505, + 0x0004003d,0x00000006,0x00000507,0x00000012,0x00050080,0x00000006,0x00000508,0x00000506, + 0x00000507,0x0004003d,0x00000048,0x00000509,0x00000052,0x00060041,0x00000064,0x0000050a, + 0x000004c2,0x00000055,0x00000508,0x0003003e,0x0000050a,0x00000509,0x0004003d,0x00000006, + 0x0000050b,0x0000002c,0x0004003d,0x00000006,0x0000050c,0x0000001c,0x00050080,0x00000006, + 0x0000050e,0x0000050c,0x0000050d,0x00050084,0x00000006,0x0000050f,0x0000050e,0x0000002e, + 0x00050087,0x00000006,0x00000510,0x0000050f,0x00000021,0x00050080,0x00000006,0x00000511, + 0x0000050b,0x00000510,0x0004003d,0x00000006,0x00000512,0x00000012,0x00050080,0x00000006, + 0x00000513,0x00000511,0x00000512,0x0004003d,0x00000048,0x00000514,0x00000053,0x00060041, + 0x00000064,0x00000515,0x000004c2,0x00000055,0x00000513,0x0003003e,0x00000515,0x00000514, + 0x000200f9,0x0000003d,0x000200f8,0x0000003d,0x000100fd,0x00010038 +}; + +}}} // namespace cv::dnn::vkcom diff --git a/modules/dnn/src/vkcom/shader/spv_shader.hpp b/modules/dnn/src/vkcom/shader/spv_shader.hpp index 248983103f..9f341d6873 100644 --- a/modules/dnn/src/vkcom/shader/spv_shader.hpp +++ b/modules/dnn/src/vkcom/shader/spv_shader.hpp @@ -14,6 +14,7 @@ namespace cv { namespace dnn { namespace vkcom { extern const unsigned int dw_conv_spv[1760]; extern const unsigned int permute_spv[765]; extern const unsigned int conv48_spv[7458]; +extern const unsigned int conv48_nobias_spv[7182]; extern const unsigned int lrn_spv[1845]; extern const unsigned int concat_spv[541]; extern const unsigned int avg_pool_spv[1538]; diff --git a/modules/dnn/src/vkcom/src/op_conv.cpp b/modules/dnn/src/vkcom/src/op_conv.cpp index 4445558b2c..bd982b3eb5 100644 --- a/modules/dnn/src/vkcom/src/op_conv.cpp +++ b/modules/dnn/src/vkcom/src/op_conv.cpp @@ -167,7 +167,8 @@ bool OpConv::forward(Tensor& in, Tensor& filter_weights, Tensor& bias, Tensor& o config_.local_size_z = 1; config_.block_height = 4; config_.block_width = 8; - createShaderModule(conv48_spv, sizeof(conv48_spv)); + has_bias_ ? createShaderModule(conv48_spv, sizeof(conv48_spv)) + : createShaderModule(conv48_nobias_spv, sizeof(conv48_nobias_spv)); // specialization constants VkSpecializationInfo spec_info; ShaderConstant shader_constant; diff --git a/modules/dnn/test/pascal_semsegm_test_fcn.py b/modules/dnn/test/pascal_semsegm_test_fcn.py index df905d5a58..8945ed3c2f 100644 --- a/modules/dnn/test/pascal_semsegm_test_fcn.py +++ b/modules/dnn/test/pascal_semsegm_test_fcn.py @@ -32,7 +32,7 @@ def eval_segm_result(net_out): channels_dim = 1 y_dim = channels_dim + 1 x_dim = y_dim + 1 - res = np.zeros(net_out.shape).astype(np.int) + res = np.zeros(net_out.shape).astype(int) for i in range(net_out.shape[y_dim]): for j in range(net_out.shape[x_dim]): max_ch = np.argmax(net_out[..., i, j]) @@ -88,7 +88,7 @@ class DatasetImageFetch(object): @staticmethod def color_to_gt(color_img, colors): num_classes = len(colors) - gt = np.zeros((num_classes, color_img.shape[0], color_img.shape[1])).astype(np.int) + gt = np.zeros((num_classes, color_img.shape[0], color_img.shape[1])).astype(int) for img_y in range(color_img.shape[0]): for img_x in range(color_img.shape[1]): c = DatasetImageFetch.pix_to_c(color_img[img_y][img_x]) diff --git a/modules/dnn/test/test_backends.cpp b/modules/dnn/test/test_backends.cpp index dd2ff1db38..7aa9c756fb 100644 --- a/modules/dnn/test/test_backends.cpp +++ b/modules/dnn/test/test_backends.cpp @@ -29,7 +29,7 @@ public: void processNet(std::string weights, std::string proto, Mat inp, const std::string& outputLayer = "", std::string halideScheduler = "", - double l1 = 0.0, double lInf = 0.0, double detectionConfThresh = 0.2) + double l1 = 0.0, double lInf = 0.0, double detectionConfThresh = 0.2, bool useWinograd = true) { checkBackend(); l1 = l1 ? l1 : default_l1; @@ -49,6 +49,7 @@ public: net.setInput(inp); net.setPreferableBackend(backend); net.setPreferableTarget(target); + net.enableWinograd(useWinograd); if (backend == DNN_BACKEND_HALIDE && !halideScheduler.empty()) { halideScheduler = findDataFile(halideScheduler); @@ -101,9 +102,6 @@ public: TEST_P(DNNTestNetwork, AlexNet) { applyTestTag(CV_TEST_TAG_MEMORY_1GB); - if (backend == DNN_BACKEND_HALIDE) // Realization contains wrong number of Images (1) for realizing pipeline with 2 outputs - applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE); - processNet("dnn/bvlc_alexnet.caffemodel", "dnn/bvlc_alexnet.prototxt", Size(227, 227), "prob", target == DNN_TARGET_OPENCL ? "dnn/halide_scheduler_opencl_alexnet.yml" : @@ -118,8 +116,6 @@ TEST_P(DNNTestNetwork, ResNet_50) (target == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_512MB : CV_TEST_TAG_MEMORY_1GB), CV_TEST_TAG_DEBUG_LONG ); - if (backend == DNN_BACKEND_HALIDE) // Realization contains wrong number of Images (1) for realizing pipeline with 2 outputs - applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE); processNet("dnn/ResNet-50-model.caffemodel", "dnn/ResNet-50-deploy.prototxt", Size(224, 224), "prob", @@ -131,9 +127,6 @@ TEST_P(DNNTestNetwork, ResNet_50) TEST_P(DNNTestNetwork, SqueezeNet_v1_1) { - if (backend == DNN_BACKEND_HALIDE) // Realization contains wrong number of Images (1) for realizing pipeline with 2 outputs - applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE); - processNet("dnn/squeezenet_v1.1.caffemodel", "dnn/squeezenet_v1.1.prototxt", Size(227, 227), "prob", target == DNN_TARGET_OPENCL ? "dnn/halide_scheduler_opencl_squeezenet_v1_1.yml" : @@ -145,8 +138,6 @@ TEST_P(DNNTestNetwork, SqueezeNet_v1_1) TEST_P(DNNTestNetwork, GoogLeNet) { applyTestTag(target == DNN_TARGET_CPU ? "" : CV_TEST_TAG_MEMORY_512MB); - if (backend == DNN_BACKEND_HALIDE) // Realization contains wrong number of Images (1) for realizing pipeline with 2 outputs - applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE); processNet("dnn/bvlc_googlenet.caffemodel", "dnn/bvlc_googlenet.prototxt", Size(224, 224), "prob"); @@ -157,8 +148,6 @@ TEST_P(DNNTestNetwork, GoogLeNet) TEST_P(DNNTestNetwork, Inception_5h) { applyTestTag(CV_TEST_TAG_MEMORY_512MB); - if (backend == DNN_BACKEND_HALIDE) // Realization contains wrong number of Images (1) for realizing pipeline with 2 outputs - applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE); double l1 = default_l1, lInf = default_lInf; if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019 && (target == DNN_TARGET_CPU || target == DNN_TARGET_OPENCL)) @@ -177,8 +166,6 @@ TEST_P(DNNTestNetwork, Inception_5h) TEST_P(DNNTestNetwork, ENet) { applyTestTag(target == DNN_TARGET_CPU ? "" : CV_TEST_TAG_MEMORY_512MB); - if (backend == DNN_BACKEND_HALIDE) // Realization contains wrong number of Images (1) for realizing pipeline with 2 outputs - applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE); if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); @@ -215,12 +202,6 @@ TEST_P(DNNTestNetwork, MobileNet_SSD_Caffe_Different_Width_Height) if (backend == DNN_BACKEND_HALIDE) applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE); #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // May hang on some configurations if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -367,7 +348,8 @@ TEST_P(DNNTestNetwork, SSD_VGG16) } processNet("dnn/VGG_ILSVRC2016_SSD_300x300_iter_440000.caffemodel", - "dnn/ssd_vgg16.prototxt", inp, "detection_out", "", scoreDiff, iouDiff); + "dnn/ssd_vgg16.prototxt", inp, "detection_out", "", scoreDiff, + iouDiff, 0.2, false); expectNoFallbacksFromIE(net); } @@ -545,11 +527,11 @@ TEST_P(DNNTestNetwork, FastNeuralStyle_eccv16) Mat img = imread(findDataFile("dnn/googlenet_1.png")); Mat inp = blobFromImage(img, 1.0, Size(320, 240), Scalar(103.939, 116.779, 123.68), false, false); // Output image has values in range [-143.526, 148.539]. - float l1 = 1e-4, lInf = 2e-3; + float l1 = 2e-4, lInf = 2e-3; if (target == DNN_TARGET_OPENCL_FP16 || target == DNN_TARGET_MYRIAD) { l1 = 0.4; - lInf = 7.45; + lInf = 7.46; } else if (target == DNN_TARGET_CUDA_FP16) { diff --git a/modules/dnn/test/test_caffe_importer.cpp b/modules/dnn/test/test_caffe_importer.cpp index ddaa51dcbf..8059fc6888 100644 --- a/modules/dnn/test/test_caffe_importer.cpp +++ b/modules/dnn/test/test_caffe_importer.cpp @@ -284,7 +284,7 @@ TEST(Reproducibility_SSD, Accuracy) Mat out = net.forward("detection_out"); Mat ref = blobFromNPY(_tf("ssd_out.npy")); - normAssertDetections(ref, out, "", FLT_MIN); + normAssertDetections(ref, out, "", 0.06); } typedef testing::TestWithParam > Reproducibility_MobileNet_SSD; @@ -725,22 +725,21 @@ TEST_P(Test_Caffe_nets, FasterRCNN_vgg16) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); #endif + double scoreDiff = 0.0; #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Check 'backward_compatible_check || in_out_elements_equal' failed at core/src/op/reshape.cpp:427: // While validating node 'v1::Reshape bbox_pred_reshape (bbox_pred[0]:f32{1,84}, Constant_265242[0]:i64{4}) -> (f32{?,?,?,?})' with friendly_name 'bbox_pred_reshape': // Requested output shape {1,6300,4,1} is incompatible with input shape {1, 84} if (target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); + if (target == DNN_TARGET_OPENCL_FP16) + scoreDiff = 0.02; #endif static Mat ref = (Mat_(3, 7) << 0, 2, 0.949398, 99.2454, 210.141, 601.205, 462.849, 0, 7, 0.997022, 481.841, 92.3218, 722.685, 175.953, 0, 12, 0.993028, 133.221, 189.377, 350.994, 563.166); - testFaster("faster_rcnn_vgg16.prototxt", "VGG16_faster_rcnn_final.caffemodel", ref); + testFaster("faster_rcnn_vgg16.prototxt", "VGG16_faster_rcnn_final.caffemodel", ref, scoreDiff); } TEST_P(Test_Caffe_nets, FasterRCNN_zf) @@ -761,12 +760,6 @@ TEST_P(Test_Caffe_nets, FasterRCNN_zf) ); #endif -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#endif - if ((backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019 || backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) && target == DNN_TARGET_OPENCL_FP16) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16); @@ -802,15 +795,6 @@ TEST_P(Test_Caffe_nets, RFCN) } #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Sporadic: "Cannot get memory!" - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL_FP16) { scoreDiff = 0.1f; diff --git a/modules/dnn/test/test_common.hpp b/modules/dnn/test/test_common.hpp index 3d616e17da..df93e50c91 100644 --- a/modules/dnn/test/test_common.hpp +++ b/modules/dnn/test/test_common.hpp @@ -49,6 +49,7 @@ #define CV_TEST_TAG_DNN_SKIP_PARSER "dnn_skip_parser" #define CV_TEST_TAG_DNN_SKIP_TIMVX "dnn_skip_timvx" +#define CV_TEST_TAG_DNN_SKIP_CANN "dnn_skip_cann" #ifdef HAVE_INF_ENGINE #if INF_ENGINE_VER_MAJOR_EQ(2018050000) @@ -130,9 +131,7 @@ void normAssertTextDetections( void readFileContent(const std::string& filename, CV_OUT std::vector& content); -#ifdef HAVE_INF_ENGINE bool validateVPUType(); -#endif testing::internal::ParamGenerator< tuple > dnnBackendsAndTargets( bool withInferenceEngine = true, @@ -141,7 +140,8 @@ testing::internal::ParamGenerator< tuple > dnnBackendsAndTarget bool withVkCom = true, bool withCUDA = true, bool withNgraph = true, - bool withWebnn = true + bool withWebnn = true, + bool withCann = true ); testing::internal::ParamGenerator< tuple > dnnBackendsAndTargetsIE(); diff --git a/modules/dnn/test/test_common.impl.hpp b/modules/dnn/test/test_common.impl.hpp index 35f658cc90..bad6a8d082 100644 --- a/modules/dnn/test/test_common.impl.hpp +++ b/modules/dnn/test/test_common.impl.hpp @@ -31,6 +31,7 @@ void PrintTo(const cv::dnn::Backend& v, std::ostream* os) case DNN_BACKEND_INFERENCE_ENGINE_NGRAPH: *os << "NGRAPH"; return; case DNN_BACKEND_WEBNN: *os << "WEBNN"; return; case DNN_BACKEND_TIMVX: *os << "TIMVX"; return; + case DNN_BACKEND_CANN: *os << "CANN"; return; } // don't use "default:" to emit compiler warnings *os << "DNN_BACKEND_UNKNOWN(" << (int)v << ")"; } @@ -251,12 +252,11 @@ testing::internal::ParamGenerator< tuple > dnnBackendsAndTarget bool withVkCom /*= true*/, bool withCUDA /*= true*/, bool withNgraph /*= true*/, - bool withWebnn /*= false*/ + bool withWebnn /*= false*/, + bool withCann /*= true*/ ) { -#ifdef HAVE_INF_ENGINE bool withVPU = validateVPUType(); -#endif std::vector< tuple > targets; std::vector< Target > available; @@ -266,7 +266,6 @@ testing::internal::ParamGenerator< tuple > dnnBackendsAndTarget for (std::vector< Target >::const_iterator i = available.begin(); i != available.end(); ++i) targets.push_back(make_tuple(DNN_BACKEND_HALIDE, *i)); } -#ifdef HAVE_INF_ENGINE if (withInferenceEngine) { available = getAvailableTargets(DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019); @@ -288,9 +287,6 @@ testing::internal::ParamGenerator< tuple > dnnBackendsAndTarget } } -#else - CV_UNUSED(withInferenceEngine); -#endif if (withVkCom) { available = getAvailableTargets(DNN_BACKEND_VKCOM); @@ -317,6 +313,16 @@ testing::internal::ParamGenerator< tuple > dnnBackendsAndTarget CV_UNUSED(withWebnn); #endif +#ifdef HAVE_CANN + if (withCann) + { + for (auto target : getAvailableTargets(DNN_BACKEND_CANN)) + targets.push_back(make_tuple(DNN_BACKEND_CANN, target)); + } +#else + CV_UNUSED(withCann); +#endif // HAVE_CANN + { available = getAvailableTargets(DNN_BACKEND_OPENCV); for (std::vector< Target >::const_iterator i = available.begin(); i != available.end(); ++i) @@ -356,7 +362,6 @@ testing::internal::ParamGenerator< tuple > dnnBackendsAndTarget #endif } -#ifdef HAVE_INF_ENGINE static std::string getTestInferenceEngineVPUType() { static std::string param_vpu_type = utils::getConfigurationParameterString("OPENCV_TEST_DNN_IE_VPU_TYPE", ""); @@ -419,7 +424,6 @@ bool validateVPUType() static bool result = validateVPUType_(); return result; } -#endif // HAVE_INF_ENGINE void initDNNTests() @@ -485,6 +489,11 @@ void initDNNTests() registerGlobalSkipTag( CV_TEST_TAG_DNN_SKIP_TIMVX ); +#endif +#ifdef HAVE_CANN + registerGlobalSkipTag( + CV_TEST_TAG_DNN_SKIP_CANN + ); #endif registerGlobalSkipTag( CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE, diff --git a/modules/dnn/test/test_darknet_importer.cpp b/modules/dnn/test/test_darknet_importer.cpp index f0765c955d..2160c81fad 100644 --- a/modules/dnn/test/test_darknet_importer.cpp +++ b/modules/dnn/test/test_darknet_importer.cpp @@ -81,6 +81,7 @@ TEST(Test_Darknet, read_yolo_voc_stream) Net net = readNetFromDarknet(cfgFile, weightsFile); net.setInput(inp); net.setPreferableBackend(DNN_BACKEND_OPENCV); + net.enableWinograd(false); ref = net.forward(); } // Import from bytes array. @@ -92,6 +93,7 @@ TEST(Test_Darknet, read_yolo_voc_stream) Net net = readNetFromDarknet(cfg.data(), cfg.size(), weights.data(), weights.size()); net.setInput(inp); net.setPreferableBackend(DNN_BACKEND_OPENCV); + net.enableWinograd(false); Mat out = net.forward(); normAssert(ref, out); } @@ -178,7 +180,8 @@ public: const std::vector >& refClassIds, const std::vector >& refConfidences, const std::vector >& refBoxes, - double scoreDiff, double iouDiff, float confThreshold = 0.24, float nmsThreshold = 0.4) + double scoreDiff, double iouDiff, float confThreshold = 0.24, + float nmsThreshold = 0.4, bool useWinograd = true) { checkBackend(); @@ -198,6 +201,7 @@ public: findDataFile("dnn/" + weights, false)); net.setPreferableBackend(backend); net.setPreferableTarget(target); + net.enableWinograd(useWinograd); net.setInput(inp); std::vector outs; net.forward(outs, net.getUnconnectedOutLayersNames()); @@ -280,18 +284,19 @@ public: const std::vector& refClassIds, const std::vector& refConfidences, const std::vector& refBoxes, - double scoreDiff, double iouDiff, float confThreshold = 0.24, float nmsThreshold = 0.4) + double scoreDiff, double iouDiff, float confThreshold = 0.24, + float nmsThreshold = 0.4, bool useWinograd = true) { testDarknetModel(cfg, weights, std::vector >(1, refClassIds), std::vector >(1, refConfidences), std::vector >(1, refBoxes), - scoreDiff, iouDiff, confThreshold, nmsThreshold); + scoreDiff, iouDiff, confThreshold, nmsThreshold, useWinograd); } void testDarknetModel(const std::string& cfg, const std::string& weights, const cv::Mat& ref, double scoreDiff, double iouDiff, - float confThreshold = 0.24, float nmsThreshold = 0.4) + float confThreshold = 0.24, float nmsThreshold = 0.4, bool useWinograd = true) { CV_Assert(ref.cols == 7); std::vector > refClassIds; @@ -318,7 +323,7 @@ public: refBoxes[batchId].push_back(box); } testDarknetModel(cfg, weights, refClassIds, refScores, refBoxes, - scoreDiff, iouDiff, confThreshold, nmsThreshold); + scoreDiff, iouDiff, confThreshold, nmsThreshold, useWinograd); } }; @@ -396,7 +401,7 @@ TEST_P(Test_Darknet_nets, YoloVoc) { SCOPED_TRACE("batch size 1"); - testDarknetModel(config_file, weights_file, ref.rowRange(0, 3), scoreDiff, iouDiff); + testDarknetModel(config_file, weights_file, ref.rowRange(0, 3), scoreDiff, iouDiff, 0.24, 0.4, false); } #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) @@ -410,7 +415,7 @@ TEST_P(Test_Darknet_nets, YoloVoc) #endif { SCOPED_TRACE("batch size 2"); - testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff, 0.24, nmsThreshold); + testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff, 0.24, nmsThreshold, false); } #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) @@ -557,12 +562,12 @@ TEST_P(Test_Darknet_nets_async, Accuracy) l1 = 0.001; lInf = 0.005; } - if (INF_ENGINE_VER_MAJOR_EQ(2021040000) && targetId == DNN_TARGET_OPENCL_FP16 && prefix == "yolov4-tiny") // FIXIT: 4.x only, 3.4 branch works well + if (INF_ENGINE_VER_MAJOR_EQ(2021040000) && targetId == DNN_TARGET_OPENCL_FP16 && prefix == "yolov4-tiny-2020-12") // FIXIT: 4.x only, 3.4 branch works well { l1 = 0.001; lInf = 0.005; } - if (INF_ENGINE_VER_MAJOR_EQ(2022010000) && targetId == DNN_TARGET_OPENCL_FP16 && prefix == "yolov4-tiny") // FIXIT: 4.x only, 3.4 branch works well + if (INF_ENGINE_VER_MAJOR_EQ(2022010000) && targetId == DNN_TARGET_OPENCL_FP16 && prefix == "yolov4-tiny-2020-12") // FIXIT: 4.x only, 3.4 branch works well { l1 = 0.001; lInf = 0.005; @@ -589,7 +594,7 @@ TEST_P(Test_Darknet_nets_async, Accuracy) } INSTANTIATE_TEST_CASE_P(/**/, Test_Darknet_nets_async, Combine( - Values("yolo-voc", "tiny-yolo-voc", "yolov3", "yolov4", "yolov4-tiny"), + Values("yolo-voc", "tiny-yolo-voc", "yolov3", "yolov4", "yolov4-tiny-2020-12"), dnnBackendsAndTargets() )); @@ -599,7 +604,7 @@ TEST_P(Test_Darknet_nets, YOLOv3) { applyTestTag( CV_TEST_TAG_LONG, - (target == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_1GB : CV_TEST_TAG_MEMORY_2GB), + CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_DEBUG_VERYLONG ); @@ -633,6 +638,11 @@ TEST_P(Test_Darknet_nets, YOLOv3) double scoreDiff = 8e-5, iouDiff = 3e-4; if (target == DNN_TARGET_OPENCL_FP16 || target == DNN_TARGET_MYRIAD) { +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_GE(2022010000) + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) + scoreDiff = 0.009; + else +#endif scoreDiff = 0.006; iouDiff = 0.042; } @@ -656,7 +666,7 @@ TEST_P(Test_Darknet_nets, YOLOv3) { SCOPED_TRACE("batch size 1"); - testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, iouDiff); + testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, iouDiff, 0.24, 0.4, false); } #if defined(INF_ENGINE_RELEASE) @@ -674,7 +684,7 @@ TEST_P(Test_Darknet_nets, YOLOv3) { SCOPED_TRACE("batch size 2"); - testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff); + testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff, 0.24, 0.4, false); } } @@ -682,7 +692,7 @@ TEST_P(Test_Darknet_nets, YOLOv4) { applyTestTag( CV_TEST_TAG_LONG, - (target == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_1GB : CV_TEST_TAG_MEMORY_2GB), + CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_DEBUG_VERYLONG ); @@ -756,7 +766,7 @@ TEST_P(Test_Darknet_nets, YOLOv4) { SCOPED_TRACE("batch size 1"); - testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, iouDiff); + testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, iouDiff, 0.24, 0.4, false); } { @@ -766,6 +776,7 @@ TEST_P(Test_Darknet_nets, YOLOv4) // accuracy (batch 2) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL_FP16) { + scoreDiff = 0.008f; iouDiff = 0.05f; } // accuracy @@ -792,7 +803,7 @@ TEST_P(Test_Darknet_nets, YOLOv4) } #endif - testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff); + testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff, 0.24, 0.4, false); } #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) @@ -822,25 +833,26 @@ TEST_P(Test_Darknet_nets, YOLOv4_tiny) const double confThreshold = 0.5; // batchId, classId, confidence, left, top, right, bottom - const int N0 = 2; + const int N0 = 3; const int N1 = 3; static const float ref_[/* (N0 + N1) * 7 */] = { -0, 7, 0.85935f, 0.593484f, 0.141211f, 0.920356f, 0.291593f, -0, 16, 0.795188f, 0.169207f, 0.386886f, 0.423753f, 0.933004f, +0, 16, 0.889883f, 0.177204f, 0.356279f, 0.417204f, 0.937517f, +0, 7, 0.816615f, 0.604293f, 0.137345f, 0.918016f, 0.295708f, +0, 1, 0.595912f, 0.0940107f, 0.178122f, 0.750619f, 0.829336f, -1, 2, 0.996832f, 0.653802f, 0.464573f, 0.815193f, 0.653292f, -1, 2, 0.963325f, 0.451151f, 0.458915f, 0.496255f, 0.52241f, -1, 0, 0.926244f, 0.194851f, 0.361743f, 0.260277f, 0.632364f, +1, 2, 0.998224f, 0.652883f, 0.463477f, 0.813952f, 0.657163f, +1, 2, 0.967396f, 0.4539f, 0.466368f, 0.497716f, 0.520299f, +1, 0, 0.807866f, 0.205039f, 0.361842f, 0.260984f, 0.643621f, }; Mat ref(N0 + N1, 7, CV_32FC1, (void*)ref_); - double scoreDiff = 0.01f; + double scoreDiff = 0.012f; double iouDiff = (target == DNN_TARGET_OPENCL_FP16 || target == DNN_TARGET_MYRIAD) ? 0.15 : 0.01f; if (target == DNN_TARGET_CUDA_FP16) iouDiff = 0.02; - std::string config_file = "yolov4-tiny.cfg"; - std::string weights_file = "yolov4-tiny.weights"; + std::string config_file = "yolov4-tiny-2020-12.cfg"; + std::string weights_file = "yolov4-tiny-2020-12.weights"; #if defined(INF_ENGINE_RELEASE) if (target == DNN_TARGET_MYRIAD) // bad accuracy @@ -877,7 +889,7 @@ TEST_P(Test_Darknet_nets, YOLOv4x_mish) { applyTestTag( CV_TEST_TAG_LONG, - (target == DNN_TARGET_CPU ? CV_TEST_TAG_MEMORY_1GB : CV_TEST_TAG_MEMORY_2GB), + CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_DEBUG_VERYLONG ); @@ -939,7 +951,7 @@ TEST_P(Test_Darknet_nets, YOLOv4x_mish) { SCOPED_TRACE("batch size 1"); - testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, iouDiff); + testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, iouDiff, 0.24, 0.4, false); } { @@ -958,7 +970,7 @@ TEST_P(Test_Darknet_nets, YOLOv4x_mish) } #endif - testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff); + testDarknetModel(config_file, weights_file, ref, scoreDiff, iouDiff, 0.24, 0.4, false); } } @@ -975,20 +987,10 @@ TEST_P(Test_Darknet_layers, shortcut_leaky) } TEST_P(Test_Darknet_layers, shortcut_unequal) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#endif testDarknetLayer("shortcut_unequal"); } TEST_P(Test_Darknet_layers, shortcut_unequal_2) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#endif testDarknetLayer("shortcut_unequal_2"); } diff --git a/modules/dnn/test/test_halide_layers.cpp b/modules/dnn/test/test_halide_layers.cpp index 104d7e7347..6a7958ecee 100644 --- a/modules/dnn/test/test_halide_layers.cpp +++ b/modules/dnn/test/test_halide_layers.cpp @@ -643,12 +643,6 @@ TEST_P(NoParamActivation, Accuracy) Target targetId = get<1>(get<1>(GetParam())); std::string layer_type = get<0>(GetParam()); -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && targetId == DNN_TARGET_CPU && layer_type == "BNLL") - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#endif - LayerParams lp; lp.type = layer_type; lp.name = "testLayer"; diff --git a/modules/dnn/test/test_ie_models.cpp b/modules/dnn/test/test_ie_models.cpp index 3622f69bdb..135caa9064 100644 --- a/modules/dnn/test/test_ie_models.cpp +++ b/modules/dnn/test/test_ie_models.cpp @@ -438,6 +438,9 @@ TEST_P(DNNTestOpenVINO, models) { auto dstIt = cvOutputsMap.find(srcIt.first); CV_Assert(dstIt != cvOutputsMap.end()); + + dstIt->second.convertTo(dstIt->second, srcIt.second.type()); + double normInf = cvtest::norm(srcIt.second, dstIt->second, cv::NORM_INF); EXPECT_LE(normInf, eps) << "output=" << srcIt.first; } diff --git a/modules/dnn/test/test_int8_layers.cpp b/modules/dnn/test/test_int8_layers.cpp index 54db5a39ea..3551ee239f 100644 --- a/modules/dnn/test/test_int8_layers.cpp +++ b/modules/dnn/test/test_int8_layers.cpp @@ -1029,7 +1029,7 @@ TEST_P(Test_Int8_nets, FasterRCNN_resnet50) Mat blob = blobFromImage(inp, 1.0, Size(800, 600), Scalar(), true, false); Mat ref = blobFromNPY(_tf("tensorflow/faster_rcnn_resnet50_coco_2018_01_28.detection_out.npy")); - float confThreshold = 0.5, scoreDiff = 0.05, iouDiff = 0.15; + float confThreshold = 0.8, scoreDiff = 0.05, iouDiff = 0.15; testDetectionNet(net, blob, ref, confThreshold, scoreDiff, iouDiff); } @@ -1320,19 +1320,19 @@ TEST_P(Test_Int8_nets, YOLOv4_tiny) const int N0 = 2; const int N1 = 3; static const float ref_[/* (N0 + N1) * 7 */] = { -0, 7, 0.85935f, 0.593484f, 0.141211f, 0.920356f, 0.291593f, -0, 16, 0.795188f, 0.169207f, 0.386886f, 0.423753f, 0.933004f, +0, 16, 0.912199f, 0.169926f, 0.350896f, 0.422704f, 0.941837f, +0, 7, 0.845388f, 0.617568f, 0.13961f, 0.9008f, 0.29315f, -1, 2, 0.996832f, 0.653802f, 0.464573f, 0.815193f, 0.653292f, -1, 2, 0.963325f, 0.451151f, 0.458915f, 0.496255f, 0.52241f, -1, 0, 0.926244f, 0.194851f, 0.361743f, 0.260277f, 0.632364f, +1, 2, 0.997789f, 0.657455f, 0.459714f, 0.809122f, 0.656829f, +1, 2, 0.924423f, 0.442872f, 0.470127f, 0.49816f, 0.516516f, +1, 0, 0.728307f, 0.202607f, 0.369828f, 0.259445f, 0.613846f, }; Mat ref(N0 + N1, 7, CV_32FC1, (void*)ref_); - std::string config_file = "yolov4-tiny.cfg"; - std::string weights_file = "yolov4-tiny.weights"; + std::string config_file = "yolov4-tiny-2020-12.cfg"; + std::string weights_file = "yolov4-tiny-2020-12.weights"; double scoreDiff = 0.12; - double iouDiff = target == DNN_TARGET_OPENCL_FP16 ? 0.2 : 0.082; + double iouDiff = target == DNN_TARGET_OPENCL_FP16 ? 0.2 : 0.118; { SCOPED_TRACE("batch size 1"); @@ -1340,7 +1340,7 @@ TEST_P(Test_Int8_nets, YOLOv4_tiny) { SCOPED_TRACE("Per-tensor quantize"); - testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, 0.16, 0.7, 0.4, false); + testDarknetModel(config_file, weights_file, ref.rowRange(0, N0), scoreDiff, 0.224, 0.7, 0.4, false); } } diff --git a/modules/dnn/test/test_layers.cpp b/modules/dnn/test/test_layers.cpp index 14a19266cc..6afb3a8aa0 100644 --- a/modules/dnn/test/test_layers.cpp +++ b/modules/dnn/test/test_layers.cpp @@ -1221,6 +1221,7 @@ TEST_P(Layer_Test_DWconv_Prelu, Accuracy) Mat in_blob(4, &shape[0], CV_32FC1, Scalar(1)); net.setPreferableBackend(DNN_BACKEND_OPENCV); + net.enableWinograd(false); net.setInput(in_blob); Mat out = net.forward(); @@ -1291,7 +1292,7 @@ TEST_P(Layer_Test_Convolution_DLDT, Accuracy) if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) ASSERT_EQ(net.getLayer(outLayers[0])->type, "Convolution"); else - ASSERT_EQ(net.getLayer(outLayers[0])->type, "Add"); + ASSERT_EQ(net.getLayer(outLayers[0])->type, "Result"); } TEST_P(Layer_Test_Convolution_DLDT, setInput_uint8) @@ -1485,12 +1486,6 @@ static void test_dldt_fused_output(Backend backend, Target target) TEST_P(Test_DLDT_layers, fused_output) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#endif - CV_DNN_REGISTER_LAYER_CLASS(Unsupported, UnsupportedLayer); try { @@ -1650,16 +1645,7 @@ TEST_P(Test_Caffe_layers, Interp) TEST_P(Test_Caffe_layers, DISABLED_Interp) // requires patched protobuf (available in OpenCV source tree only) #endif { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021030000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021030000) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); // exception #endif diff --git a/modules/dnn/test/test_model.cpp b/modules/dnn/test/test_model.cpp index 25d1a18d52..d3217a0e49 100644 --- a/modules/dnn/test/test_model.cpp +++ b/modules/dnn/test/test_model.cpp @@ -407,19 +407,11 @@ TEST_P(Test_Model, DetectRegionWithNmsAcrossClasses) TEST_P(Test_Model, DetectionOutput) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // Check 'backward_compatible_check || in_out_elements_equal' failed at core/src/op/reshape.cpp:427: // While validating node 'v1::Reshape bbox_pred_reshape (ave_bbox_pred_rois[0]:f32{1,8,1,1}, Constant_388[0]:i64{4}) -> (f32{?,?,?,?})' with friendly_name 'bbox_pred_reshape': // Requested output shape {1,300,8,1} is incompatible with input shape {1, 8, 1, 1} if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); #elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // Exception: Function contains several inputs and outputs with one friendly name! (HETERO bug?) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target != DNN_TARGET_CPU) @@ -455,6 +447,10 @@ TEST_P(Test_Model, DetectionOutput) { if (backend == DNN_BACKEND_OPENCV) scoreDiff = 4e-3; +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_GE(2022010000) + else if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) + scoreDiff = 4e-2; +#endif else scoreDiff = 2e-2; iouDiff = 1.8e-1; @@ -634,9 +630,6 @@ TEST_P(Test_Model, Segmentation) float norm = 0; #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // Failed to allocate graph: NC_ERROR if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); diff --git a/modules/dnn/test/test_nms.cpp b/modules/dnn/test/test_nms.cpp index 5ba240287c..6149125119 100644 --- a/modules/dnn/test/test_nms.cpp +++ b/modules/dnn/test/test_nms.cpp @@ -37,6 +37,36 @@ TEST(NMS, Accuracy) ASSERT_EQ(indices[i], ref_indices[i]); } +TEST(BatchedNMS, Accuracy) +{ + //reference results obtained using tf.image.non_max_suppression with iou_threshold=0.5 + std::string dataPath = findDataFile("dnn/batched_nms_reference.yml"); + FileStorage fs(dataPath, FileStorage::READ); + + std::vector bboxes; + std::vector scores; + std::vector idxs; + std::vector ref_indices; + + fs["boxes"] >> bboxes; + fs["probs"] >> scores; + fs["idxs"] >> idxs; + fs["output"] >> ref_indices; + + const float nms_thresh = .5f; + const float score_thresh = .05f; + std::vector indices; + cv::dnn::NMSBoxesBatched(bboxes, scores, idxs, score_thresh, nms_thresh, indices); + + ASSERT_EQ(ref_indices.size(), indices.size()); + + std::sort(indices.begin(), indices.end()); + std::sort(ref_indices.begin(), ref_indices.end()); + + for(size_t i = 0; i < indices.size(); i++) + ASSERT_EQ(indices[i], ref_indices[i]); +} + TEST(SoftNMS, Accuracy) { //reference results are obtained using TF v2.7 tf.image.non_max_suppression_with_scores diff --git a/modules/dnn/test/test_onnx_conformance.cpp b/modules/dnn/test/test_onnx_conformance.cpp index e9bc0e4187..fc766c2b81 100644 --- a/modules/dnn/test/test_onnx_conformance.cpp +++ b/modules/dnn/test/test_onnx_conformance.cpp @@ -666,11 +666,15 @@ static const TestCase testConformanceConfig[] = { {"test_scatter_elements_with_axis", 3, 1}, {"test_scatter_elements_with_duplicate_indices", 3, 1}, {"test_scatter_elements_with_negative_indices", 3, 1}, + {"test_scatter_elements_with_reduction_max", 3, 1}, + {"test_scatter_elements_with_reduction_min", 3, 1}, {"test_scatter_elements_without_axis", 3, 1}, {"test_scatter_with_axis", 3, 1}, {"test_scatter_without_axis", 3, 1}, {"test_scatternd", 3, 1}, {"test_scatternd_add", 3, 1}, + {"test_scatternd_max", 3, 1}, + {"test_scatternd_min", 3, 1}, {"test_scatternd_multiply", 3, 1}, {"test_sce_NCd1_mean_weight_negative_ii", 3, 1}, {"test_sce_NCd1_mean_weight_negative_ii_expanded", 3, 1}, diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__cuda_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__cuda_denylist.inl.hpp index c18ced0c59..4c05f10305 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__cuda_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__cuda_denylist.inl.hpp @@ -82,3 +82,16 @@ "test_sub_uint8", "test_tan", // FP16 only "test_upsample_nearest", +"test_scatter_elements_with_axis", +"test_scatter_elements_with_duplicate_indices", +"test_scatter_elements_with_negative_indices", +"test_scatter_elements_with_reduction_max", +"test_scatter_elements_with_reduction_min", +"test_scatter_elements_without_axis", +"test_scatter_with_axis", +"test_scatter_without_axis", +"test_scatternd", +"test_scatternd_add", +"test_scatternd_max", +"test_scatternd_min", +"test_scatternd_multiply", diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__halide_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__halide_denylist.inl.hpp index 72900a8194..4924aaf9da 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__halide_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__halide_denylist.inl.hpp @@ -95,3 +95,16 @@ "test_sub_uint8", "test_tanh", "test_upsample_nearest", +"test_scatter_elements_with_axis", +"test_scatter_elements_with_duplicate_indices", +"test_scatter_elements_with_negative_indices", +"test_scatter_elements_with_reduction_max", +"test_scatter_elements_with_reduction_min", +"test_scatter_elements_without_axis", +"test_scatter_with_axis", +"test_scatter_without_axis", +"test_scatternd", +"test_scatternd_add", +"test_scatternd_max", +"test_scatternd_min", +"test_scatternd_multiply", diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp index cad914d05a..e6a35dfab9 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp @@ -1588,6 +1588,10 @@ CASE(test_scatter_elements_with_duplicate_indices) // no filter CASE(test_scatter_elements_with_negative_indices) // no filter +CASE(test_scatter_elements_with_reduction_max) + // no filter +CASE(test_scatter_elements_with_reduction_min) + // no filter CASE(test_scatter_elements_without_axis) // no filter CASE(test_scatter_with_axis) @@ -1598,6 +1602,10 @@ CASE(test_scatternd) // no filter CASE(test_scatternd_add) // no filter +CASE(test_scatternd_max) + // no filter +CASE(test_scatternd_min) + // no filter CASE(test_scatternd_multiply) // no filter CASE(test_sce_NCd1_mean_weight_negative_ii) diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__vulkan_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__vulkan_denylist.inl.hpp index 101d44cbf0..8156686428 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__vulkan_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__vulkan_denylist.inl.hpp @@ -63,3 +63,16 @@ "test_sub_uint8", "test_transpose_all_permutations_0", "test_upsample_nearest", +"test_scatter_elements_with_axis", +"test_scatter_elements_with_duplicate_indices", +"test_scatter_elements_with_negative_indices", +"test_scatter_elements_with_reduction_max", +"test_scatter_elements_with_reduction_min", +"test_scatter_elements_without_axis", +"test_scatter_with_axis", +"test_scatter_without_axis", +"test_scatternd", +"test_scatternd_add", +"test_scatternd_max", +"test_scatternd_min", +"test_scatternd_multiply", diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp16_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp16_denylist.inl.hpp index c2425d469f..9b6b2414db 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp16_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp16_denylist.inl.hpp @@ -30,4 +30,17 @@ "test_reduce_sum_square_default_axes_keepdims_random", // Expected: (normL1) <= (l1), actual: 0.0183411 vs 0.004 "test_reduce_sum_square_do_not_keepdims_random", // Expected: (normL1) <= (l1), actual: 0.010789 vs 0.004, Expected: (normInf) <= (lInf), actual: 0.0290298 vs 0.02 "test_reduce_sum_square_keepdims_random", // Expected: (normL1) <= (l1), actual: 0.010789 vs 0.004, Expected: (normInf) <= (lInf), actual: 0.0290298 vs 0.02 -"test_reduce_sum_square_negative_axes_keepdims_random", // Expected: (normL1) <= (l1), actual: 0.010789 vs 0.004, Expected: (normInf) <= (lInf), actual: 0.0290298 vs 0.02 \ No newline at end of file +"test_reduce_sum_square_negative_axes_keepdims_random", // Expected: (normL1) <= (l1), actual: 0.010789 vs 0.004, Expected: (normInf) <= (lInf), actual: 0.0290298 vs 0.02 +"test_scatter_elements_with_axis", +"test_scatter_elements_with_duplicate_indices", +"test_scatter_elements_with_negative_indices", +"test_scatter_elements_with_reduction_max", +"test_scatter_elements_with_reduction_min", +"test_scatter_elements_without_axis", +"test_scatter_with_axis", +"test_scatter_without_axis", +"test_scatternd", +"test_scatternd_add", +"test_scatternd_max", +"test_scatternd_min", +"test_scatternd_multiply", diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp32_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp32_denylist.inl.hpp index 9a7a21f393..7fe58a07fd 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp32_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter_opencv_ocl_fp32_denylist.inl.hpp @@ -1,2 +1,15 @@ "test_averagepool_3d_default", "test_maxpool_3d_default", +"test_scatter_elements_with_axis", +"test_scatter_elements_with_duplicate_indices", +"test_scatter_elements_with_negative_indices", +"test_scatter_elements_with_reduction_max", +"test_scatter_elements_with_reduction_min", +"test_scatter_elements_without_axis", +"test_scatter_with_axis", +"test_scatter_without_axis", +"test_scatternd", +"test_scatternd_add", +"test_scatternd_max", +"test_scatternd_min", +"test_scatternd_multiply", diff --git a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp index 1437e5475b..0630833b1f 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp @@ -384,15 +384,6 @@ "test_roialign_aligned_true", "test_scan9_sum", "test_scan_sum", -"test_scatter_elements_with_axis", -"test_scatter_elements_with_duplicate_indices", -"test_scatter_elements_with_negative_indices", -"test_scatter_elements_without_axis", -"test_scatter_with_axis", -"test_scatter_without_axis", -"test_scatternd", -"test_scatternd_add", -"test_scatternd_multiply", "test_sce_NCd1_mean_weight_negative_ii", "test_sce_NCd1_mean_weight_negative_ii_expanded", "test_sce_NCd1_mean_weight_negative_ii_log_prob", diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index cbeb059ea7..12dc3987b9 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -106,12 +106,6 @@ TEST_P(Test_ONNX_layers, MaxPooling) } TEST_P(Test_ONNX_layers, MaxPooling_2) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#endif - testONNXModels("two_maxpooling", npy, 0, 0, false, false); } @@ -205,22 +199,39 @@ TEST_P(Test_ONNX_layers, Convolution_variable_weight_bias) TEST_P(Test_ONNX_layers, Gather) { - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019 && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); - testONNXModels("gather"); + testONNXModels("gather", npy, 0, 0, false, false); +} + +TEST_P(Test_ONNX_layers, Gather_Scalar) +{ + testONNXModels("gather_scalar", npy, 0, 0, false, false); +} + +TEST_P(Test_ONNX_layers, GatherMulti) +{ // GPU plugin unsupported slice for constant if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL, CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); - testONNXModels("gather_scalar", npy, 0, 0, false, false); + testONNXModels("gather_multi", npy, 0, 0, false, false); } TEST_P(Test_ONNX_layers, Convolution3D) { + if (backend == DNN_BACKEND_CUDA && target == DNN_TARGET_CUDA_FP16) + { + // CUDA_FP16: cuDNN did not return a suitable algorithm for convolution. + applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA_FP16); + } testONNXModels("conv3d"); } TEST_P(Test_ONNX_layers, Convolution3D_bias) { + if (backend == DNN_BACKEND_CUDA && target == DNN_TARGET_CUDA_FP16) + { + // CUDA_FP16: cuDNN did not return a suitable algorithm for convolution. + applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA_FP16); + } testONNXModels("conv3d_bias"); } @@ -581,19 +592,7 @@ TEST_P(Test_ONNX_layers, Elementwise_Sqrt) TEST_P(Test_ONNX_layers, Elementwise_not) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) @@ -604,11 +603,7 @@ TEST_P(Test_ONNX_layers, Elementwise_not) TEST_P(Test_ONNX_layers, Compare_EQ) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // IE exception: Function contains several inputs and outputs with one friendly name! if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -629,11 +624,7 @@ TEST_P(Test_ONNX_layers, Compare_EQ) TEST_P(Test_ONNX_layers, Compare_GT) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // IE exception: Function contains several inputs and outputs with one friendly name! if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -654,11 +645,7 @@ TEST_P(Test_ONNX_layers, Compare_GT) TEST_P(Test_ONNX_layers, Compare_LT) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // IE exception: Function contains several inputs and outputs with one friendly name! if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -677,13 +664,19 @@ TEST_P(Test_ONNX_layers, Compare_LT) testONNXModels("less"); } +TEST_P(Test_ONNX_layers, Compare_GTorEQ) +{ + testONNXModels("greater_or_equal"); +} + +TEST_P(Test_ONNX_layers, Compare_LEorEQ) +{ + testONNXModels("less_or_equal"); +} + TEST_P(Test_ONNX_layers, CompareSameDims_EQ) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // IE exception: Function contains several inputs and outputs with one friendly name! if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -704,11 +697,7 @@ TEST_P(Test_ONNX_layers, CompareSameDims_EQ) TEST_P(Test_ONNX_layers, CompareSameDims_GT) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // IE exception: Function contains several inputs and outputs with one friendly name! if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -729,11 +718,7 @@ TEST_P(Test_ONNX_layers, CompareSameDims_GT) TEST_P(Test_ONNX_layers, CompareSameDims_LT) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // IE exception: Function contains several inputs and outputs with one friendly name! if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -853,6 +838,12 @@ TEST_P(Test_ONNX_layers, PoolConv3D) if (backend == DNN_BACKEND_VKCOM) applyTestTag(CV_TEST_TAG_DNN_SKIP_VULKAN); + if (backend == DNN_BACKEND_CUDA && target == DNN_TARGET_CUDA_FP16) + { + // CUDA_FP16: cuDNN did not return a suitable algorithm for convolution. + applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA_FP16); + } + testONNXModels("pool_conv_3d"); } @@ -915,16 +906,40 @@ TEST_P(Test_ONNX_layers, Multiplication) testONNXModels("mul"); } -TEST_P(Test_ONNX_layers, MatMul) +TEST_P(Test_ONNX_layers, MatMul_2d) { - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); - testONNXModels("matmul_2d"); +} +TEST_P(Test_ONNX_layers, MatMul_3d) +{ testONNXModels("matmul_3d"); +} +TEST_P(Test_ONNX_layers, MatMul_4d) +{ testONNXModels("matmul_4d"); } +TEST_P(Test_ONNX_layers, MatMul_2d_init) +{ + testONNXModels("matmul_2d_init"); +} +TEST_P(Test_ONNX_layers, MatMul_3d_init) +{ + testONNXModels("matmul_3d_init"); +} +TEST_P(Test_ONNX_layers, MatMul_4d_init) +{ + testONNXModels("matmul_4d_init"); +} +TEST_P(Test_ONNX_layers, MatMul_init_2) +{ + testONNXModels("matmul_init_2"); +} +TEST_P(Test_ONNX_layers, MatMul_init_bcast) +{ + testONNXModels("matmul_init_bcast"); +} + TEST_P(Test_ONNX_layers, MatMulAdd) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) @@ -1058,10 +1073,9 @@ TEST_P(Test_ONNX_layers, Div) Mat out = net.forward(); normAssert(ref, out, "", default_l1, default_lInf); - expectNoFallbacksFromIE(net); - expectNoFallbacksFromCUDA(net); - testONNXModels("div_test_1x1",npy, 0, 0, false, true, 2); + // NaryEltwise layer suuports only CPU for now + testONNXModels("div_test_1x1", npy, 0, 0, false, false, 2); } TEST_P(Test_ONNX_layers, DynamicReshape) @@ -1081,11 +1095,17 @@ TEST_P(Test_ONNX_layers, Reshape) testONNXModels("unsqueeze_opset_13"); } +TEST_P(Test_ONNX_layers, Unsqueeze_Neg_Axes) +{ + testONNXModels("unsqueeze_neg_axes"); +} + TEST_P(Test_ONNX_layers, Squeeze) { if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019 && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); testONNXModels("squeeze"); + testONNXModels("squeeze_axes_op13"); } TEST_P(Test_ONNX_layers, ReduceL2) @@ -1106,10 +1126,19 @@ TEST_P(Test_ONNX_layers, Split) testONNXModels("split_2"); testONNXModels("split_3"); testONNXModels("split_4"); - testONNXModels("split_sizes"); testONNXModels("split_neg_axis"); } +// Mul inside with 0-d tensor, output should be A x 1, but is 1 x A. PR #22652 +TEST_P(Test_ONNX_layers, DISABLED_Split_sizes_0d) +{ + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) + applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) + applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NGRAPH); + testONNXModels("split_sizes"); +} + TEST_P(Test_ONNX_layers, Slice) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2019010000) @@ -1118,6 +1147,7 @@ TEST_P(Test_ONNX_layers, Slice) testONNXModels("slice"); testONNXModels("slice_neg_starts"); testONNXModels("slice_opset_11"); + testONNXModels("slice_neg_steps", pb); #endif } @@ -1128,17 +1158,6 @@ TEST_P(Test_ONNX_layers, Slice_Steps_2DInput) TEST_P(Test_ONNX_layers, Slice_Steps_3DInput) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); -#endif - testONNXModels("slice_opset_11_steps_3d"); } @@ -1149,23 +1168,23 @@ TEST_P(Test_ONNX_layers, Slice_Steps_4DInput) TEST_P(Test_ONNX_layers, Slice_Steps_5DInput) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // IE exception: Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#endif - testONNXModels("slice_opset_11_steps_5d"); } +TEST_P(Test_ONNX_layers, Slice_Nonseq_Axes) +{ + testONNXModels("slice_nonseq_axes"); + testONNXModels("slice_nonseq_axes_steps"); + testONNXModels("slice_nonseq_miss_axes_steps"); +} + +TEST_P(Test_ONNX_layers, Slice_Neg_Axes) +{ + testONNXModels("slice_neg_axes"); + testONNXModels("slice_neg_axes_steps"); + testONNXModels("slice_neg_miss_axes_steps"); +} + TEST_P(Test_ONNX_layers, Softmax) { testONNXModels("softmax"); @@ -1185,9 +1204,6 @@ TEST_P(Test_ONNX_layers, Split_EltwiseMax) TEST_P(Test_ONNX_layers, LSTM_Activations) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // IE exception: Node Block1326/lstm/reshape_0/permute was not assigned on any pointed device if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -1224,9 +1240,6 @@ TEST_P(Test_ONNX_layers, LSTM_hidden) TEST_P(Test_ONNX_layers, LSTM_hidden_bidirectional) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // IE exception: Node Transpose_45 was not assigned on any pointed device. if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -1240,9 +1253,6 @@ TEST_P(Test_ONNX_layers, LSTM_hidden_bidirectional) TEST_P(Test_ONNX_layers, GRU) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // IE exception: Node GRU_22 was not assigned on any pointed device if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, @@ -1274,11 +1284,7 @@ TEST_P(Test_ONNX_layers, LSTM_cell_forward) } TEST_P(Test_ONNX_layers, LSTM_cell_bidirectional) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2021040000) // Ngraph operation Reshape with name LSTM_16/lstm_y/reshape has dynamic output shape on 0 port, but CPU plug-in supports only static shape if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -1423,7 +1429,7 @@ TEST_P(Test_ONNX_layers, GatherMultiOutput) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE); #endif - testONNXModels("gather_multi_output"); + testONNXModels("gather_multi_output", npy, 0, 0, false, false); } TEST_P(Test_ONNX_layers, DynamicAxes_squeeze_and_conv) @@ -1474,7 +1480,7 @@ TEST_P(Test_ONNX_layers, DynamicAxes_gather) } #endif #endif - testONNXModels("gather_dynamic_axes"); + testONNXModels("gather_dynamic_axes", npy, 0, 0, false, false); } TEST_P(Test_ONNX_layers, DynamicAxes_gather_scalar) @@ -1503,7 +1509,7 @@ TEST_P(Test_ONNX_layers, DynamicAxes_gather_scalar) } #endif #endif - testONNXModels("gather_scalar_dynamic_axes"); + testONNXModels("gather_scalar_dynamic_axes", npy, 0, 0, false, false); } TEST_P(Test_ONNX_layers, DynamicAxes_slice) @@ -1651,16 +1657,7 @@ TEST_P(Test_ONNX_layers, MaxPoolSigmoid1d) TEST_P(Test_ONNX_layers, MaxPool1d_Twise) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) { if (target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); @@ -1675,11 +1672,7 @@ TEST_P(Test_ONNX_layers, MaxPool1d_Twise) TEST_P(Test_ONNX_layers, AvePool1d) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) { if (target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); @@ -1694,19 +1687,7 @@ TEST_P(Test_ONNX_layers, AvePool1d) TEST_P(Test_ONNX_layers, PoolConv1d) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); -#elif defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) +#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2021040000) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) { if (target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); @@ -1761,15 +1742,27 @@ TEST_P(Test_ONNX_layers, Gemm) { testONNXModels("gemm_no_transB"); testONNXModels("gemm_transB_0"); + testONNXModels("gemm_first_const"); } TEST_P(Test_ONNX_layers, Quantized_Convolution) { - testONNXModels("quantized_conv_uint8_weights", npy, 0.004, 0.02); - testONNXModels("quantized_conv_int8_weights", npy, 0.03, 0.5); - testONNXModels("quantized_conv_per_channel_weights", npy, 0.06, 0.4); + // The difference of QOperator and QDQ format: + // https://onnxruntime.ai/docs/performance/quantization.html#onnx-quantization-representation-format. + { + SCOPED_TRACE("QOperator quantized model."); + testONNXModels("quantized_conv_uint8_weights", npy, 0.004, 0.02); + testONNXModels("quantized_conv_int8_weights", npy, 0.03, 0.5); + testONNXModels("quantized_conv_per_channel_weights", npy, 0.06, 0.4); + testONNXModels("quantized_conv_asymmetric_pads_int8_weights"); + } - testONNXModels("quantized_conv_asymmetric_pads_int8_weights"); + { + SCOPED_TRACE("QDQ quantized model."); + testONNXModels("quantized_conv_uint8_weights_qdq", npy, 0.004, 0.02); + testONNXModels("quantized_conv_int8_weights_qdq", npy, 0.03, 0.5); + testONNXModels("quantized_conv_per_channel_weights_qdq", npy, 0.06, 0.4); + } } TEST_P(Test_ONNX_layers, Quantized_MatMul) @@ -1779,6 +1772,11 @@ TEST_P(Test_ONNX_layers, Quantized_MatMul) testONNXModels("quantized_matmul_per_channel_weights", npy, 0.06, 0.22); } +TEST_P(Test_ONNX_layers, Quantized_Gemm) +{ + testONNXModels("quantized_gemm", npy); +} + TEST_P(Test_ONNX_layers, Quantized_MatMul_Variable_Weights) { // Unsupported @@ -2102,6 +2100,11 @@ TEST_P(Test_ONNX_nets, MobileNet_v2) testONNXModels("mobilenetv2", pb, default_l1, default_lInf, true); } +TEST_P(Test_ONNX_nets, MobileNet_v2_FP16) +{ + testONNXModels("mobilenetv2_fp16", npy, default_l1, default_lInf, true); +} + TEST_P(Test_ONNX_nets, LResNet100E_IR) { applyTestTag( @@ -2139,7 +2142,7 @@ TEST_P(Test_ONNX_nets, LResNet100E_IR) } else if (target == DNN_TARGET_CUDA_FP16) { - l1 = 0.008; + l1 = 0.009; lInf = 0.04; } testONNXModels("LResNet100E_IR", pb, l1, lInf); @@ -2319,6 +2322,95 @@ TEST_P(Test_ONNX_layers, CumSum) testONNXModels("cumsum_3d_dim_2"); } +// This test is mainly to test: +// 1. identity node with constant input +// 2. limited support to range operator (all inputs are constant) +// 3. parseExpand with multiple broadcast axes +// 4. 1D mat dimension issue with the output of range operator +TEST_P(Test_ONNX_layers, YOLOv7) +{ + std::string weightPath = _tf("models/yolov7_not_simplified.onnx", false); + std::string imgPath = _tf("../dog_orig_size.png"); + + Size targetSize{640, 640}; + float conf_threshold = 0.3; + float iou_threshold = 0.5; + + // Reference, which is collected with input size of 640x640 + std::vector refClassIds{1, 16, 7}; + std::vector refScores{0.9614331f, 0.9589417f, 0.8679074f}; + // [x1, y1, x2, y2] x 3 + std::vector refBoxes{Rect2d(105.973236f, 150.16716f, 472.59012f, 466.48834f), + Rect2d(109.97953f, 246.17862f, 259.83676f, 600.76624f), + Rect2d(385.96185f, 83.02809f, 576.07355f, 189.82793f)}; + + Mat img = imread(imgPath); + Mat inp = blobFromImage(img, 1/255.0, targetSize, Scalar(0, 0, 0), true, false); + + Net net = readNet(weightPath); + + net.setInput(inp); + std::vector outs; + net.forward(outs, net.getUnconnectedOutLayersNames()); + + Mat preds = outs[3].reshape(1, outs[3].size[1]); // [1, 25200, 85] + + // Retrieve + std::vector classIds; + std::vector confidences; + std::vector boxes; + // each row is [cx, cy, w, h, conf_obj, conf_class1, ..., conf_class80] + for (int i = 0; i < preds.rows; ++i) + { + // filter out non objects + float obj_conf = preds.row(i).at(4); + if (obj_conf < conf_threshold) + continue; + + // get class id and conf + Mat scores = preds.row(i).colRange(5, preds.cols); + double conf; + Point maxLoc; + minMaxLoc(scores, 0, &conf, 0, &maxLoc); + conf *= obj_conf; + if (conf < conf_threshold) + continue; + + // get bbox coords + float* det = preds.ptr(i); + double cx = det[0]; + double cy = det[1]; + double w = det[2]; + double h = det[3]; + // [x1, y1, x2, y2] + boxes.push_back(Rect2d(cx - 0.5 * w, cy - 0.5 * h, + cx + 0.5 * w, cy + 0.5 * h)); + classIds.push_back(maxLoc.x); + confidences.push_back(conf); + } + + // NMS + std::vector keep_idx; + NMSBoxes(boxes, confidences, conf_threshold, iou_threshold, keep_idx); + + std::vector keep_classIds; + std::vector keep_confidences; + std::vector keep_boxes; + for (auto i : keep_idx) + { + keep_classIds.push_back(classIds[i]); + keep_confidences.push_back(confidences[i]); + keep_boxes.push_back(boxes[i]); + } + + normAssertDetections(refClassIds, refScores, refBoxes, keep_classIds, keep_confidences, keep_boxes); +} + +TEST_P(Test_ONNX_layers, Tile) +{ + testONNXModels("tile", pb); +} + INSTANTIATE_TEST_CASE_P(/**/, Test_ONNX_nets, dnnBackendsAndTargets()); }} // namespace diff --git a/modules/dnn/test/test_tf_importer.cpp b/modules/dnn/test/test_tf_importer.cpp index 72a8989f6a..bcedcffe15 100644 --- a/modules/dnn/test/test_tf_importer.cpp +++ b/modules/dnn/test/test_tf_importer.cpp @@ -216,9 +216,6 @@ TEST_P(Test_TensorFlow_layers, conv_keras_atrous_conv2d_same) TEST_P(Test_TensorFlow_layers, conv_pool_nchw) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: !expired() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -337,6 +334,12 @@ TEST_P(Test_TensorFlow_layers, eltwise_mul_vec) runTensorFlowNet("eltwise_mul_vec"); } +TEST_P(Test_TensorFlow_layers, tf_reshape_nhwc) +{ + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) + applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); + runTensorFlowNet("tf_reshape_nhwc"); +} TEST_P(Test_TensorFlow_layers, channel_broadcast) { @@ -490,9 +493,6 @@ TEST_P(Test_TensorFlow_layers, slim_batch_norm) TEST_P(Test_TensorFlow_layers, pooling_max_pool_even) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: !expired() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -505,9 +505,6 @@ TEST_P(Test_TensorFlow_layers, pooling_max_pool_even) TEST_P(Test_TensorFlow_layers, pooling_max_pool_odd_valid) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: !expired() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -520,9 +517,6 @@ TEST_P(Test_TensorFlow_layers, pooling_max_pool_odd_valid) TEST_P(Test_TensorFlow_layers, pooling_max_pool_odd_same) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: !expired() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -843,14 +837,6 @@ TEST_P(Test_TensorFlow_layers, ExpandDims) TEST_P(Test_TensorFlow_layers, l2_normalize_3d) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); // accuracy if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -1112,9 +1098,6 @@ TEST_P(Test_TensorFlow_nets, Faster_RCNN_resnet50_coco_2018_01_28) ); #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: subgraphTopoSortsStep < subgraphs.size() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -1354,9 +1337,6 @@ TEST_P(Test_TensorFlow_layers, fp16_weights_fp16_single_conv) TEST_P(Test_TensorFlow_layers, fp16_weights_fp16_max_pool_odd_same) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: !expired() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -1385,9 +1365,6 @@ TEST_P(Test_TensorFlow_layers, fp16_weights_fp16_padding_valid) TEST_P(Test_TensorFlow_layers, fp16_weights_fp16_max_pool_even) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: !expired() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -1412,9 +1389,6 @@ TEST_P(Test_TensorFlow_layers, fp16_weights_fp16_deconvolution) TEST_P(Test_TensorFlow_layers, fp16_weights_fp16_max_pool_odd_valid) { #if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); // [ GENERAL_ERROR ] AssertionFailed: !expired() if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); @@ -1501,16 +1475,6 @@ TEST_P(Test_TensorFlow_layers, split_equals) TEST_P(Test_TensorFlow_layers, resize_nearest_neighbor) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); -#endif runTensorFlowNet("resize_nearest_neighbor"); } TEST_P(Test_TensorFlow_layers, resize_nearest_neighbor_keras_upsampling2d) @@ -1580,14 +1544,7 @@ TEST_P(Test_TensorFlow_layers, relu6) TEST_P(Test_TensorFlow_layers, subpixel) { -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); -#elif defined(INF_ENGINE_RELEASE) +#if defined(INF_ENGINE_RELEASE) if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_NN_BUILDER); if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) diff --git a/modules/dnn/test/test_torch_importer.cpp b/modules/dnn/test/test_torch_importer.cpp index 520887480d..2e18ac8c48 100644 --- a/modules/dnn/test/test_torch_importer.cpp +++ b/modules/dnn/test/test_torch_importer.cpp @@ -288,20 +288,6 @@ TEST_P(Test_Torch_layers, net_normalize) if(backend == DNN_BACKEND_CUDA) applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA); /* only L1 and L2 norms are supported */ -#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_EQ(2022010000) - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_CPU) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_CPU, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_MYRIAD) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - // Cannot get memory! - if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16)) - applyTestTag(target == DNN_TARGET_OPENCL ? CV_TEST_TAG_DNN_SKIP_IE_OPENCL : CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, - CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION - ); -#endif - runTorchNet("net_normalize", "", false, true); } @@ -476,6 +462,7 @@ TEST_P(Test_Torch_nets, ENet_accuracy) ASSERT_TRUE(!net.empty()); } + net.enableWinograd(false); net.setPreferableBackend(backend); net.setPreferableTarget(target); @@ -488,7 +475,7 @@ TEST_P(Test_Torch_nets, ENet_accuracy) // Due to numerical instability in Pooling-Unpooling layers (indexes jittering) // thresholds for ENet must be changed. Accuracy of results was checked on // Cityscapes dataset and difference in mIOU with Torch is 10E-4% - normAssert(ref, out, "", 0.00044, /*target == DNN_TARGET_CPU ? 0.453 : */0.552); + normAssert(ref, out, "", 0.0005, /*target == DNN_TARGET_CPU ? 0.453 : */0.552); normAssertSegmentation(ref, out); const int N = 3; @@ -496,7 +483,7 @@ TEST_P(Test_Torch_nets, ENet_accuracy) { net.setInput(inputBlob, ""); Mat out = net.forward(); - normAssert(ref, out, "", 0.00044, /*target == DNN_TARGET_CPU ? 0.453 : */0.552); + normAssert(ref, out, "", 0.0005, /*target == DNN_TARGET_CPU ? 0.453 : */0.552); normAssertSegmentation(ref, out); } } diff --git a/modules/features2d/include/opencv2/features2d.hpp b/modules/features2d/include/opencv2/features2d.hpp index 76064f331a..a657687cc9 100644 --- a/modules/features2d/include/opencv2/features2d.hpp +++ b/modules/features2d/include/opencv2/features2d.hpp @@ -107,6 +107,10 @@ public: * Remove keypoints from some image by mask for pixels of this image. */ static void runByPixelsMask( std::vector& keypoints, const Mat& mask ); + /* + * Remove objects from some image and a vector of points by mask for pixels of this image + */ + static void runByPixelsMask2VectorPoint(std::vector &keypoints, std::vector > &removeFrom, const Mat &mask); /* * Remove duplicated keypoints. */ @@ -208,7 +212,7 @@ public: CV_WRAP virtual String getDefaultName() const CV_OVERRIDE; // see corresponding cv::Algorithm method - CV_WRAP inline void write(const Ptr& fs, const String& name = String()) const { Algorithm::write(fs, name); } + CV_WRAP inline void write(FileStorage& fs, const String& name) const { Algorithm::write(fs, name); } }; /** Feature detectors in OpenCV have wrappers with a common interface that enables you to easily switch @@ -308,6 +312,21 @@ public: double sigma, int descriptorType); CV_WRAP virtual String getDefaultName() const CV_OVERRIDE; + + CV_WRAP virtual void setNFeatures(int maxFeatures) = 0; + CV_WRAP virtual int getNFeatures() const = 0; + + CV_WRAP virtual void setNOctaveLayers(int nOctaveLayers) = 0; + CV_WRAP virtual int getNOctaveLayers() const = 0; + + CV_WRAP virtual void setContrastThreshold(double contrastThreshold) = 0; + CV_WRAP virtual double getContrastThreshold() const = 0; + + CV_WRAP virtual void setEdgeThreshold(double edgeThreshold) = 0; + CV_WRAP virtual double getEdgeThreshold() const = 0; + + CV_WRAP virtual void setSigma(double sigma) = 0; + CV_WRAP virtual double getSigma() const = 0; }; typedef SIFT SiftFeatureDetector; @@ -363,14 +382,20 @@ public: /** @brief Set detection threshold. @param threshold AGAST detection threshold score. */ - CV_WRAP virtual void setThreshold(int threshold) { CV_UNUSED(threshold); return; } - CV_WRAP virtual int getThreshold() const { return -1; } + CV_WRAP virtual void setThreshold(int threshold) = 0; + CV_WRAP virtual int getThreshold() const = 0; /** @brief Set detection octaves. @param octaves detection octaves. Use 0 to do single scale. */ - CV_WRAP virtual void setOctaves(int octaves) { CV_UNUSED(octaves); return; } - CV_WRAP virtual int getOctaves() const { return -1; } + CV_WRAP virtual void setOctaves(int octaves) = 0; + CV_WRAP virtual int getOctaves() const = 0; + /** @brief Set detection patternScale. + @param patternScale apply this scale to the pattern used for sampling the neighbourhood of a + keypoint. + */ + CV_WRAP virtual void setPatternScale(float patternScale) = 0; + CV_WRAP virtual float getPatternScale() const = 0; }; /** @brief Class implementing the ORB (*oriented BRIEF*) keypoint detector and descriptor extractor @@ -503,11 +528,27 @@ public: CV_WRAP virtual void setMaxArea(int maxArea) = 0; CV_WRAP virtual int getMaxArea() const = 0; + CV_WRAP virtual void setMaxVariation(double maxVariation) = 0; + CV_WRAP virtual double getMaxVariation() const = 0; + CV_WRAP virtual void setMinDiversity(double minDiversity) = 0; CV_WRAP virtual double getMinDiversity() const = 0; + CV_WRAP virtual void setMaxEvolution(int maxEvolution) = 0; + CV_WRAP virtual int getMaxEvolution() const = 0; + + CV_WRAP virtual void setAreaThreshold(double areaThreshold) = 0; + CV_WRAP virtual double getAreaThreshold() const = 0; + + CV_WRAP virtual void setMinMargin(double min_margin) = 0; + CV_WRAP virtual double getMinMargin() const = 0; + + CV_WRAP virtual void setEdgeBlurSize(int edge_blur_size) = 0; + CV_WRAP virtual int getEdgeBlurSize() const = 0; + CV_WRAP virtual void setPass2Only(bool f) = 0; CV_WRAP virtual bool getPass2Only() const = 0; + CV_WRAP virtual String getDefaultName() const CV_OVERRIDE; }; @@ -642,6 +683,9 @@ public: CV_WRAP virtual void setBlockSize(int blockSize) = 0; CV_WRAP virtual int getBlockSize() const = 0; + CV_WRAP virtual void setGradientSize(int gradientSize_) = 0; + CV_WRAP virtual int getGradientSize() = 0; + CV_WRAP virtual void setHarrisDetector(bool val) = 0; CV_WRAP virtual bool getHarrisDetector() const = 0; @@ -708,13 +752,20 @@ public: CV_PROP_RW bool filterByConvexity; CV_PROP_RW float minConvexity, maxConvexity; + CV_PROP_RW bool collectContours; + void read( const FileNode& fn ); void write( FileStorage& fs ) const; }; CV_WRAP static Ptr create(const SimpleBlobDetector::Params ¶meters = SimpleBlobDetector::Params()); + + CV_WRAP virtual void setParams(const SimpleBlobDetector::Params& params ) = 0; + CV_WRAP virtual SimpleBlobDetector::Params getParams() const = 0; + CV_WRAP virtual String getDefaultName() const CV_OVERRIDE; + CV_WRAP virtual const std::vector >& getBlobContours() const = 0; }; //! @} features2d_main @@ -1113,7 +1164,10 @@ public: // see corresponding cv::Algorithm method - CV_WRAP inline void write(const Ptr& fs, const String& name = String()) const { Algorithm::write(fs, name); } + CV_WRAP inline void write(FileStorage& fs, const String& name) const { Algorithm::write(fs, name); } +#if CV_VERSION_MAJOR < 5 + inline void write(const Ptr& fs, const String& name) const { CV_Assert(fs); Algorithm::write(*fs, name); } +#endif protected: /** diff --git a/modules/features2d/misc/java/test/AGASTFeatureDetectorTest.java b/modules/features2d/misc/java/test/AGASTFeatureDetectorTest.java new file mode 100644 index 0000000000..4158219f59 --- /dev/null +++ b/modules/features2d/misc/java/test/AGASTFeatureDetectorTest.java @@ -0,0 +1,85 @@ +package org.opencv.test.features2d; + +import org.opencv.test.OpenCVTestCase; +import org.opencv.test.OpenCVTestRunner; +import org.opencv.features2d.AgastFeatureDetector; + +public class AGASTFeatureDetectorTest extends OpenCVTestCase { + + AgastFeatureDetector detector; + + @Override + protected void setUp() throws Exception { + super.setUp(); + detector = AgastFeatureDetector.create(); // default (10,true,3) + } + + public void testCreate() { + assertNotNull(detector); + } + + public void testDetectListOfMatListOfListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectListOfMatListOfListOfKeyPointListOfMat() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPointMat() { + fail("Not yet implemented"); + } + + public void testEmpty() { + fail("Not yet implemented"); + } + + public void testRead() { + String filename = OpenCVTestRunner.getTempFileName("xml"); + writeFile(filename, "\n\nFeature2D.AgastFeatureDetector\n11\n0\n2\n\n"); + + detector.read(filename); + + assertEquals(11, detector.getThreshold()); + assertEquals(false, detector.getNonmaxSuppression()); + assertEquals(2, detector.getType()); + } + + public void testReadYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + writeFile(filename, "%YAML:1.0\n---\nname: \"Feature2D.AgastFeatureDetector\"\nthreshold: 11\nnonmaxSuppression: 0\ntype: 2\n"); + + detector.read(filename); + + assertEquals(11, detector.getThreshold()); + assertEquals(false, detector.getNonmaxSuppression()); + assertEquals(2, detector.getType()); + } + + public void testWrite() { + String filename = OpenCVTestRunner.getTempFileName("xml"); + + detector.write(filename); + + String truth = "\n\nFeature2D.AgastFeatureDetector\n10\n1\n3\n\n"; + String actual = readFile(filename); + actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation + assertEquals(truth, actual); + } + + public void testWriteYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + detector.write(filename); + + String truth = "%YAML:1.0\n---\nname: \"Feature2D.AgastFeatureDetector\"\nthreshold: 10\nnonmaxSuppression: 1\ntype: 3\n"; + String actual = readFile(filename); + actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation + assertEquals(truth, actual); + } + +} diff --git a/modules/features2d/misc/java/test/AKAZEDescriptorExtractorTest.java b/modules/features2d/misc/java/test/AKAZEDescriptorExtractorTest.java new file mode 100644 index 0000000000..fd98cddee1 --- /dev/null +++ b/modules/features2d/misc/java/test/AKAZEDescriptorExtractorTest.java @@ -0,0 +1,67 @@ +package org.opencv.test.features2d; + +import org.opencv.test.OpenCVTestCase; +import org.opencv.test.OpenCVTestRunner; +import org.opencv.features2d.AKAZE; + +public class AKAZEDescriptorExtractorTest extends OpenCVTestCase { + + AKAZE extractor; + + @Override + protected void setUp() throws Exception { + super.setUp(); + extractor = AKAZE.create(); // default (5,0,3,0.001f,4,4,1) + } + + public void testCreate() { + assertNotNull(extractor); + } + + public void testDetectListOfMatListOfListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectListOfMatListOfListOfKeyPointListOfMat() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPointMat() { + fail("Not yet implemented"); + } + + public void testEmpty() { + fail("Not yet implemented"); + } + + public void testReadYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + writeFile(filename, "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.AKAZE\"\ndescriptor: 4\ndescriptor_channels: 2\ndescriptor_size: 32\nthreshold: 0.125\noctaves: 3\nsublevels: 5\ndiffusivity: 2\n"); + + extractor.read(filename); + + assertEquals(4, extractor.getDescriptorType()); + assertEquals(2, extractor.getDescriptorChannels()); + assertEquals(32, extractor.getDescriptorSize()); + assertEquals(0.125, extractor.getThreshold()); + assertEquals(3, extractor.getNOctaves()); + assertEquals(5, extractor.getNOctaveLayers()); + assertEquals(2, extractor.getDiffusivity()); + } + + public void testWriteYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + extractor.write(filename); + + String truth = "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.AKAZE\"\ndescriptor: 5\ndescriptor_channels: 3\ndescriptor_size: 0\nthreshold: 1.0000000474974513e-03\noctaves: 4\nsublevels: 4\ndiffusivity: 1\n"; + String actual = readFile(filename); + actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation + assertEquals(truth, actual); + } + +} diff --git a/modules/features2d/misc/java/test/BRIEFDescriptorExtractorTest.java b/modules/features2d/misc/java/test/BRIEFDescriptorExtractorTest.java index 2c6e543e3d..a8744635ee 100644 --- a/modules/features2d/misc/java/test/BRIEFDescriptorExtractorTest.java +++ b/modules/features2d/misc/java/test/BRIEFDescriptorExtractorTest.java @@ -86,7 +86,7 @@ public class BRIEFDescriptorExtractorTest extends OpenCVTestCase { extractor.write(filename); - String truth = "\n\n32\n\n"; + String truth = "\n\nFeature2D.BRIEF\n32\n0\n\n"; assertEquals(truth, readFile(filename)); } @@ -95,7 +95,7 @@ public class BRIEFDescriptorExtractorTest extends OpenCVTestCase { extractor.write(filename); - String truth = "%YAML:1.0\n---\ndescriptorSize: 32\n"; + String truth = "%YAML:1.0\n---\nname: \"Feature2D.BRIEF\"\ndescriptorSize: 32\nuse_orientation: 0\n"; assertEquals(truth, readFile(filename)); } diff --git a/modules/features2d/misc/java/test/BRISKDescriptorExtractorTest.java b/modules/features2d/misc/java/test/BRISKDescriptorExtractorTest.java new file mode 100644 index 0000000000..faa59dfb1a --- /dev/null +++ b/modules/features2d/misc/java/test/BRISKDescriptorExtractorTest.java @@ -0,0 +1,63 @@ +package org.opencv.test.features2d; + +import org.opencv.test.OpenCVTestCase; +import org.opencv.test.OpenCVTestRunner; +import org.opencv.features2d.BRISK; + +public class BRISKDescriptorExtractorTest extends OpenCVTestCase { + + BRISK extractor; + + @Override + protected void setUp() throws Exception { + super.setUp(); + extractor = BRISK.create(); // default (30,3,1) + } + + public void testCreate() { + assertNotNull(extractor); + } + + public void testDetectListOfMatListOfListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectListOfMatListOfListOfKeyPointListOfMat() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPointMat() { + fail("Not yet implemented"); + } + + public void testEmpty() { + fail("Not yet implemented"); + } + + public void testReadYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + writeFile(filename, "%YAML:1.0\n---\nname: \"Feature2D.BRISK\"\nthreshold: 31\noctaves: 4\npatternScale: 1.1\n"); + + extractor.read(filename); + + assertEquals(31, extractor.getThreshold()); + assertEquals(4, extractor.getOctaves()); + assertEquals(1.1f, extractor.getPatternScale()); + } + + public void testWriteYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + extractor.write(filename); + + String truth = "%YAML:1.0\n---\nname: \"Feature2D.BRISK\"\nthreshold: 30\noctaves: 3\npatternScale: 1.\n"; + String actual = readFile(filename); + actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation + assertEquals(truth, actual); + } + +} diff --git a/modules/features2d/misc/java/test/FASTFeatureDetectorTest.java b/modules/features2d/misc/java/test/FASTFeatureDetectorTest.java index 044e70d0ed..cce21837ad 100644 --- a/modules/features2d/misc/java/test/FASTFeatureDetectorTest.java +++ b/modules/features2d/misc/java/test/FASTFeatureDetectorTest.java @@ -8,7 +8,6 @@ import org.opencv.core.Mat; import org.opencv.core.MatOfKeyPoint; import org.opencv.core.Point; import org.opencv.core.Scalar; -import org.opencv.features2d.Feature2D; import org.opencv.features2d.FastFeatureDetector; import org.opencv.core.KeyPoint; import org.opencv.test.OpenCVTestCase; @@ -17,7 +16,7 @@ import org.opencv.imgproc.Imgproc; public class FASTFeatureDetectorTest extends OpenCVTestCase { - Feature2D detector; + FastFeatureDetector detector; KeyPoint[] truth; private Mat getMaskImg() { @@ -78,20 +77,24 @@ public class FASTFeatureDetectorTest extends OpenCVTestCase { public void testEmpty() { // assertFalse(detector.empty()); - fail("Not yet implemented"); //FAST does not override empty() method + fail("Not yet implemented"); // FAST does not override empty() method } public void testRead() { - String filename = OpenCVTestRunner.getTempFileName("yml"); + String filename = OpenCVTestRunner.getTempFileName("xml"); - writeFile(filename, "%YAML:1.0\n---\nthreshold: 130\nnonmaxSuppression: 1\n"); + writeFile(filename, "\n\nFeature2D.FastFeatureDetector\n10\n1\n2\n\n"); detector.read(filename); + assertEquals(10, detector.getThreshold()); + assertEquals(true, detector.getNonmaxSuppression()); + assertEquals(2, detector.getType()); + MatOfKeyPoint keypoints1 = new MatOfKeyPoint(); detector.detect(grayChess, keypoints1); - writeFile(filename, "%YAML:1.0\n---\nthreshold: 150\nnonmaxSuppression: 1\n"); + writeFile(filename, "\n\nFeature2D.FastFeatureDetector\n150\n1\n2\n\n"); detector.read(filename); MatOfKeyPoint keypoints2 = new MatOfKeyPoint(); @@ -104,16 +107,18 @@ public class FASTFeatureDetectorTest extends OpenCVTestCase { public void testReadYml() { String filename = OpenCVTestRunner.getTempFileName("yml"); - writeFile(filename, - "\n\n130\n1\n\n"); + writeFile(filename, "%YAML:1.0\n---\nthreshold: 130\nnonmaxSuppression: 1\ntype: 2\n"); detector.read(filename); + assertEquals(130, detector.getThreshold()); + assertEquals(true, detector.getNonmaxSuppression()); + assertEquals(2, detector.getType()); + MatOfKeyPoint keypoints1 = new MatOfKeyPoint(); detector.detect(grayChess, keypoints1); - writeFile(filename, - "\n\n150\n1\n\n"); + writeFile(filename, "%YAML:1.0\n---\nthreshold: 150\nnonmaxSuppression: 1\ntype: 2\n"); detector.read(filename); MatOfKeyPoint keypoints2 = new MatOfKeyPoint(); @@ -123,28 +128,14 @@ public class FASTFeatureDetectorTest extends OpenCVTestCase { assertTrue(keypoints2.total() <= keypoints1.total()); } - public void testWrite() { - String filename = OpenCVTestRunner.getTempFileName("xml"); - - detector.write(filename); - -// String truth = "\n\nFeature2D.FAST\n1\n10\n2\n\n"; - String truth = "\n\n\n"; - String data = readFile(filename); - //Log.d("qqq", "\"" + data + "\""); - assertEquals(truth, data); - } - public void testWriteYml() { String filename = OpenCVTestRunner.getTempFileName("yml"); detector.write(filename); -// String truth = "%YAML:1.0\n---\nname: \"Feature2D.FAST\"\nnonmaxSuppression: 1\nthreshold: 10\ntype: 2\n"; - String truth = "%YAML:1.0\n---\n"; + String truth = "%YAML:1.0\n---\nname: \"Feature2D.FastFeatureDetector\"\nthreshold: 10\nnonmaxSuppression: 1\ntype: 2\n"; String data = readFile(filename); - //Log.d("qqq", "\"" + data + "\""); assertEquals(truth, data); } } diff --git a/modules/features2d/misc/java/test/Features2dTest.java b/modules/features2d/misc/java/test/Features2dTest.java index f6979d7168..b9fe42d609 100644 --- a/modules/features2d/misc/java/test/Features2dTest.java +++ b/modules/features2d/misc/java/test/Features2dTest.java @@ -79,8 +79,8 @@ public class Features2dTest extends OpenCVTestCase { public void testPTOD() { - String detectorCfg = "%YAML:1.0\n---\nhessianThreshold: 4000.\noctaves: 3\noctaveLayers: 4\nupright: 0\n"; - String extractorCfg = "%YAML:1.0\n---\nnOctaves: 4\nnOctaveLayers: 2\nextended: 0\nupright: 0\n"; + String detectorCfg = "%YAML:1.0\n---\nhessianThreshold: 4000.\nextended: 0\nupright: 0\nOctaves: 4\nOctaveLayers: 3\n"; + String extractorCfg = "%YAML:1.0\n---\nhessianThreshold: 4000.\nextended: 0\nupright: 0\nOctaves: 4\nOctaveLayers: 3\n"; Feature2D detector = createClassInstance(XFEATURES2D+"SURF", DEFAULT_FACTORY, null, null); Feature2D extractor = createClassInstance(XFEATURES2D+"SURF", DEFAULT_FACTORY, null, null); diff --git a/modules/features2d/misc/java/test/GFTTFeatureDetectorTest.java b/modules/features2d/misc/java/test/GFTTFeatureDetectorTest.java index f05942d14a..86e42cbc1d 100644 --- a/modules/features2d/misc/java/test/GFTTFeatureDetectorTest.java +++ b/modules/features2d/misc/java/test/GFTTFeatureDetectorTest.java @@ -1,11 +1,21 @@ package org.opencv.test.features2d; import org.opencv.test.OpenCVTestCase; +import org.opencv.test.OpenCVTestRunner; +import org.opencv.features2d.GFTTDetector; public class GFTTFeatureDetectorTest extends OpenCVTestCase { + GFTTDetector detector; + + @Override + protected void setUp() throws Exception { + super.setUp(); + detector = GFTTDetector.create(); // default constructor have (1000, 0.01, 1, 3, 3, false, 0.04) + } + public void testCreate() { - fail("Not yet implemented"); + assertNotNull(detector); } public void testDetectListOfMatListOfListOfKeyPoint() { @@ -28,12 +38,30 @@ public class GFTTFeatureDetectorTest extends OpenCVTestCase { fail("Not yet implemented"); } - public void testRead() { - fail("Not yet implemented"); + public void testReadYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + writeFile(filename, "%YAML:1.0\n---\nname: \"Feature2D.GFTTDetector\"\nnfeatures: 500\nqualityLevel: 2.0000000000000000e-02\nminDistance: 2.\nblockSize: 4\ngradSize: 5\nuseHarrisDetector: 1\nk: 5.0000000000000000e-02\n"); + detector.read(filename); + + assertEquals(500, detector.getMaxFeatures()); + assertEquals(0.02, detector.getQualityLevel()); + assertEquals(2.0, detector.getMinDistance()); + assertEquals(4, detector.getBlockSize()); + assertEquals(5, detector.getGradientSize()); + assertEquals(true, detector.getHarrisDetector()); + assertEquals(0.05, detector.getK()); } - public void testWrite() { - fail("Not yet implemented"); + public void testWriteYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + detector.write(filename); + + String truth = "%YAML:1.0\n---\nname: \"Feature2D.GFTTDetector\"\nnfeatures: 1000\nqualityLevel: 1.0000000000000000e-02\nminDistance: 1.\nblockSize: 3\ngradSize: 3\nuseHarrisDetector: 0\nk: 4.0000000000000001e-02\n"; + String actual = readFile(filename); + actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation + assertEquals(truth, actual); } } diff --git a/modules/features2d/misc/java/test/KAZEDescriptorExtractorTest.java b/modules/features2d/misc/java/test/KAZEDescriptorExtractorTest.java new file mode 100644 index 0000000000..69ca35e015 --- /dev/null +++ b/modules/features2d/misc/java/test/KAZEDescriptorExtractorTest.java @@ -0,0 +1,66 @@ +package org.opencv.test.features2d; + +import org.opencv.test.OpenCVTestCase; +import org.opencv.test.OpenCVTestRunner; +import org.opencv.features2d.KAZE; + +public class KAZEDescriptorExtractorTest extends OpenCVTestCase { + + KAZE extractor; + + @Override + protected void setUp() throws Exception { + super.setUp(); + extractor = KAZE.create(); // default (false,false,0.001f,4,4,1) + } + + public void testCreate() { + assertNotNull(extractor); + } + + public void testDetectListOfMatListOfListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectListOfMatListOfListOfKeyPointListOfMat() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPoint() { + fail("Not yet implemented"); + } + + public void testDetectMatListOfKeyPointMat() { + fail("Not yet implemented"); + } + + public void testEmpty() { + fail("Not yet implemented"); + } + + public void testReadYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + writeFile(filename, "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.KAZE\"\nextended: 1\nupright: 1\nthreshold: 0.125\noctaves: 3\nsublevels: 5\ndiffusivity: 2\n"); + + extractor.read(filename); + + assertEquals(true, extractor.getExtended()); + assertEquals(true, extractor.getUpright()); + assertEquals(0.125, extractor.getThreshold()); + assertEquals(3, extractor.getNOctaves()); + assertEquals(5, extractor.getNOctaveLayers()); + assertEquals(2, extractor.getDiffusivity()); + } + + public void testWriteYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + extractor.write(filename); + + String truth = "%YAML:1.0\n---\nformat: 3\nname: \"Feature2D.KAZE\"\nextended: 0\nupright: 0\nthreshold: 1.0000000474974513e-03\noctaves: 4\nsublevels: 4\ndiffusivity: 1\n"; + String actual = readFile(filename); + actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation + assertEquals(truth, actual); + } + +} diff --git a/modules/features2d/misc/java/test/MSERFeatureDetectorTest.java b/modules/features2d/misc/java/test/MSERFeatureDetectorTest.java index 3710c78ad1..7f5f1c1849 100644 --- a/modules/features2d/misc/java/test/MSERFeatureDetectorTest.java +++ b/modules/features2d/misc/java/test/MSERFeatureDetectorTest.java @@ -1,11 +1,21 @@ package org.opencv.test.features2d; import org.opencv.test.OpenCVTestCase; +import org.opencv.test.OpenCVTestRunner; +import org.opencv.features2d.MSER; public class MSERFeatureDetectorTest extends OpenCVTestCase { + MSER detector; + + @Override + protected void setUp() throws Exception { + super.setUp(); + detector = MSER.create(); // default constructor have (5, 60, 14400, .25, .2, 200, 1.01, .003, 5) + } + public void testCreate() { - fail("Not yet implemented"); + assertNotNull(detector); } public void testDetectListOfMatListOfListOfKeyPoint() { @@ -28,12 +38,33 @@ public class MSERFeatureDetectorTest extends OpenCVTestCase { fail("Not yet implemented"); } - public void testRead() { - fail("Not yet implemented"); + public void testReadYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + writeFile(filename, "%YAML:1.0\n---\nname: \"Feature2D.MSER\"\ndelta: 6\nminArea: 62\nmaxArea: 14402\nmaxVariation: .26\nminDiversity: .3\nmaxEvolution: 201\nareaThreshold: 1.02\nminMargin: 3.0e-3\nedgeBlurSize: 3\npass2Only: 1\n"); + detector.read(filename); + + assertEquals(6, detector.getDelta()); + assertEquals(62, detector.getMinArea()); + assertEquals(14402, detector.getMaxArea()); + assertEquals(.26, detector.getMaxVariation()); + assertEquals(.3, detector.getMinDiversity()); + assertEquals(201, detector.getMaxEvolution()); + assertEquals(1.02, detector.getAreaThreshold()); + assertEquals(0.003, detector.getMinMargin()); + assertEquals(3, detector.getEdgeBlurSize()); + assertEquals(true, detector.getPass2Only()); } - public void testWrite() { - fail("Not yet implemented"); + public void testWriteYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + + detector.write(filename); + + String truth = "%YAML:1.0\n---\nname: \"Feature2D.MSER\"\ndelta: 5\nminArea: 60\nmaxArea: 14400\nmaxVariation: 2.5000000000000000e-01\nminDiversity: 2.0000000000000001e-01\nmaxEvolution: 200\nareaThreshold: 1.0100000000000000e+00\nminMargin: 3.0000000000000001e-03\nedgeBlurSize: 5\npass2Only: 0\n"; + String actual = readFile(filename); + actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation + assertEquals(truth, actual); } } diff --git a/modules/features2d/misc/java/test/ORBDescriptorExtractorTest.java b/modules/features2d/misc/java/test/ORBDescriptorExtractorTest.java index 78dea53620..6bc9bb6299 100644 --- a/modules/features2d/misc/java/test/ORBDescriptorExtractorTest.java +++ b/modules/features2d/misc/java/test/ORBDescriptorExtractorTest.java @@ -75,16 +75,25 @@ public class ORBDescriptorExtractorTest extends OpenCVTestCase { fail("Not yet implemented"); // ORB does not override empty() method } - public void testRead() { + public void testReadYml() { KeyPoint point = new KeyPoint(55.775577545166016f, 44.224422454833984f, 16, 9.754629f, 8617.863f, 1, -1); MatOfKeyPoint keypoints = new MatOfKeyPoint(point); Mat img = getTestImg(); Mat descriptors = new Mat(); -// String filename = OpenCVTestRunner.getTempFileName("yml"); -// writeFile(filename, "%YAML:1.0\n---\nscaleFactor: 1.1\nnLevels: 3\nfirstLevel: 0\nedgeThreshold: 31\npatchSize: 31\n"); -// extractor.read(filename); - extractor = ORB.create(500, 1.1f, 3, 31, 0, 2, ORB.HARRIS_SCORE, 31, 20); + String filename = OpenCVTestRunner.getTempFileName("yml"); + writeFile(filename, "%YAML:1.0\n---\nnfeatures: 500\nscaleFactor: 1.1\nnlevels: 3\nedgeThreshold: 31\nfirstLevel: 0\nwta_k: 2\nscoreType: 0\npatchSize: 31\nfastThreshold: 20\n"); + extractor.read(filename); + + assertEquals(500, extractor.getMaxFeatures()); + assertEquals(1.1, extractor.getScaleFactor()); + assertEquals(3, extractor.getNLevels()); + assertEquals(31, extractor.getEdgeThreshold()); + assertEquals(0, extractor.getFirstLevel()); + assertEquals(2, extractor.getWTA_K()); + assertEquals(0, extractor.getScoreType()); + assertEquals(31, extractor.getPatchSize()); + assertEquals(20, extractor.getFastThreshold()); extractor.compute(img, keypoints, descriptors); @@ -97,25 +106,13 @@ public class ORBDescriptorExtractorTest extends OpenCVTestCase { assertDescriptorsClose(truth, descriptors, 1); } - public void testWrite() { - String filename = OpenCVTestRunner.getTempFileName("xml"); - - extractor.write(filename); - -// String truth = "\n\nFeature2D.ORB\n2\n31\n0\n500\n8\n31\n1.2000000476837158e+00\n0\n\n"; - String truth = "\n\n\n"; - String actual = readFile(filename); - actual = actual.replaceAll("e\\+000", "e+00"); // NOTE: workaround for different platforms double representation - assertEquals(truth, actual); - } - public void testWriteYml() { String filename = OpenCVTestRunner.getTempFileName("yml"); extractor.write(filename); -// String truth = "%YAML:1.0\n---\nname: \"Feature2D.ORB\"\nWTA_K: 2\nedgeThreshold: 31\nfirstLevel: 0\nnFeatures: 500\nnLevels: 8\npatchSize: 31\nscaleFactor: 1.2000000476837158e+00\nscoreType: 0\n"; - String truth = "%YAML:1.0\n---\n"; + String truth = "%YAML:1.0\n---\nname: \"Feature2D.ORB\"\nnfeatures: 500\nscaleFactor: 1.2000000476837158e+00\nnlevels: 8\nedgeThreshold: 31\nfirstLevel: 0\nwta_k: 2\nscoreType: 0\npatchSize: 31\nfastThreshold: 20\n"; +// String truth = "%YAML:1.0\n---\n"; String actual = readFile(filename); actual = actual.replaceAll("e\\+000", "e+00"); // NOTE: workaround for different platforms double representation assertEquals(truth, actual); diff --git a/modules/features2d/misc/java/test/SIFTDescriptorExtractorTest.java b/modules/features2d/misc/java/test/SIFTDescriptorExtractorTest.java index c548ff7792..63a59aa58c 100644 --- a/modules/features2d/misc/java/test/SIFTDescriptorExtractorTest.java +++ b/modules/features2d/misc/java/test/SIFTDescriptorExtractorTest.java @@ -10,11 +10,11 @@ import org.opencv.features2d.SIFT; import org.opencv.test.OpenCVTestCase; import org.opencv.test.OpenCVTestRunner; import org.opencv.imgproc.Imgproc; -import org.opencv.features2d.Feature2D; +import org.opencv.features2d.SIFT; public class SIFTDescriptorExtractorTest extends OpenCVTestCase { - Feature2D extractor; + SIFT extractor; KeyPoint keypoint; int matSize; Mat truth; @@ -43,7 +43,7 @@ public class SIFTDescriptorExtractorTest extends OpenCVTestCase { 117, 112, 117, 76, 117, 54, 117, 25, 29, 22, 117, 117, 16, 11, 14, 1, 0, 0, 22, 26, 0, 0, 0, 0, 1, 4, 15, 2, 47, 8, 0, 0, 82, 56, 31, 17, 81, 12, 0, 0, 26, 23, 18, 23, 0, 0, 0, 0, 0, 0, 0, 0 - ); + ); } }; } @@ -76,23 +76,23 @@ public class SIFTDescriptorExtractorTest extends OpenCVTestCase { public void testEmpty() { // assertFalse(extractor.empty()); - fail("Not yet implemented"); //SIFT does not override empty() method + fail("Not yet implemented"); // SIFT does not override empty() method } - public void testRead() { - fail("Not yet implemented"); - } + public void testReadYml() { + String filename = OpenCVTestRunner.getTempFileName("yml"); + writeFile(filename, "%YAML:1.0\n---\nname: \"Feature2D.SIFT\"\nnfeatures: 100\nnOctaveLayers: 4\ncontrastThreshold: 5.0000000000000001e-02\nedgeThreshold: 11\nsigma: 1.7\ndescriptorType: 5\n"); - public void testWrite() { - String filename = OpenCVTestRunner.getTempFileName("xml"); + extractor.read(filename); - extractor.write(filename); + assertEquals(128, extractor.descriptorSize()); -// String truth = "\n\nFeature2D.SIFT\n4.0000000000000001e-02\n10.\n0\n3\n1.6000000000000001e+00\n\n"; - String truth = "\n\n\n"; - String actual = readFile(filename); - actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation - assertEquals(truth, actual); + assertEquals(100, extractor.getNFeatures()); + assertEquals(4, extractor.getNOctaveLayers()); + assertEquals(0.05, extractor.getContrastThreshold()); + assertEquals(11., extractor.getEdgeThreshold()); + assertEquals(1.7, extractor.getSigma()); + assertEquals(5, extractor.descriptorType()); } public void testWriteYml() { @@ -100,8 +100,7 @@ public class SIFTDescriptorExtractorTest extends OpenCVTestCase { extractor.write(filename); -// String truth = "%YAML:1.0\n---\nname: \"Feature2D.SIFT\"\ncontrastThreshold: 4.0000000000000001e-02\nedgeThreshold: 10.\nnFeatures: 0\nnOctaveLayers: 3\nsigma: 1.6000000000000001e+00\n"; - String truth = "%YAML:1.0\n---\n"; + String truth = "%YAML:1.0\n---\nname: \"Feature2D.SIFT\"\nnfeatures: 0\nnOctaveLayers: 3\ncontrastThreshold: 4.0000000000000001e-02\nedgeThreshold: 10.\nsigma: 1.6000000000000001e+00\ndescriptorType: 5\n"; String actual = readFile(filename); actual = actual.replaceAll("e([+-])0(\\d\\d)", "e$1$2"); // NOTE: workaround for different platforms double representation assertEquals(truth, actual); diff --git a/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java b/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java index 1d8517bbd4..a67a0e8c3a 100644 --- a/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java +++ b/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java @@ -11,12 +11,12 @@ import org.opencv.core.KeyPoint; import org.opencv.test.OpenCVTestCase; import org.opencv.test.OpenCVTestRunner; import org.opencv.imgproc.Imgproc; -import org.opencv.features2d.Feature2D; import org.opencv.features2d.SimpleBlobDetector; +import org.opencv.features2d.SimpleBlobDetector_Params; public class SIMPLEBLOBFeatureDetectorTest extends OpenCVTestCase { - Feature2D detector; + SimpleBlobDetector detector; int matSize; KeyPoint[] truth; @@ -47,8 +47,8 @@ public class SIMPLEBLOBFeatureDetectorTest extends OpenCVTestCase { detector = SimpleBlobDetector.create(); matSize = 200; truth = new KeyPoint[] { - new KeyPoint( 140, 100, 41.036568f, -1, 0, 0, -1), - new KeyPoint( 60, 100, 48.538486f, -1, 0, 0, -1), + new KeyPoint(140, 100, 41.036568f, -1, 0, 0, -1), + new KeyPoint(60, 100, 48.538486f, -1, 0, 0, -1), new KeyPoint(100, 60, 36.769554f, -1, 0, 0, -1), new KeyPoint(100, 140, 28.635643f, -1, 0, 0, -1), new KeyPoint(100, 100, 20.880613f, -1, 0, 0, -1) @@ -91,16 +91,38 @@ public class SIMPLEBLOBFeatureDetectorTest extends OpenCVTestCase { fail("Not yet implemented"); } - public void testRead() { + public void testReadYml() { Mat img = getTestImg(); MatOfKeyPoint keypoints1 = new MatOfKeyPoint(); detector.detect(img, keypoints1); String filename = OpenCVTestRunner.getTempFileName("yml"); - writeFile(filename, "%YAML:1.0\nthresholdStep: 10\nminThreshold: 50\nmaxThreshold: 220\nminRepeatability: 2\nfilterByArea: true\nminArea: 800\nmaxArea: 5000\n"); + writeFile(filename, "%YAML:1.0\nthresholdStep: 10.0\nminThreshold: 50\nmaxThreshold: 220\nminRepeatability: 2\nminDistBetweenBlobs: 10.\nfilterByColor: 1\nblobColor: 0\nfilterByArea: 1\nminArea: 800\nmaxArea: 6000\nfilterByCircularity: 0\nminCircularity: 0.7\nmaxCircularity: 10.\nfilterByInertia: 1\nminInertiaRatio: 0.2\nmaxInertiaRatio: 11.\nfilterByConvexity: true\nminConvexity: 0.9\nmaxConvexity: 12.\n"); detector.read(filename); + SimpleBlobDetector_Params params = detector.getParams(); + assertEquals(10.0f, params.get_thresholdStep()); + assertEquals(50f, params.get_minThreshold()); + assertEquals(220f, params.get_maxThreshold()); + assertEquals(2, params.get_minRepeatability()); + assertEquals(10.0f, params.get_minDistBetweenBlobs()); + assertEquals(true, params.get_filterByColor()); + // FIXME: blobColor field has uchar type in C++ and cannot be automatically wrapped to Java as it does not support unsigned types + //assertEquals(0, params.get_blobColor()); + assertEquals(true, params.get_filterByArea()); + assertEquals(800f, params.get_minArea()); + assertEquals(6000f, params.get_maxArea()); + assertEquals(false, params.get_filterByCircularity()); + assertEquals(0.7f, params.get_minCircularity()); + assertEquals(10.0f, params.get_maxCircularity()); + assertEquals(true, params.get_filterByInertia()); + assertEquals(0.2f, params.get_minInertiaRatio()); + assertEquals(11.0f, params.get_maxInertiaRatio()); + assertEquals(true, params.get_filterByConvexity()); + assertEquals(0.9f, params.get_minConvexity()); + assertEquals(12.0f, params.get_maxConvexity()); + MatOfKeyPoint keypoints2 = new MatOfKeyPoint(); detector.detect(img, keypoints2); @@ -112,7 +134,7 @@ public class SIMPLEBLOBFeatureDetectorTest extends OpenCVTestCase { detector.write(filename); - String truth = "\n\n3\n10.\n50.\n220.\n2\n10.\n1\n0\n1\n25.\n5000.\n0\n8.0000001192092896e-01\n3.4028234663852886e+38\n1\n1.0000000149011612e-01\n3.4028234663852886e+38\n1\n9.4999998807907104e-01\n3.4028234663852886e+38\n\n"; + String truth = "\n\n3\n10.\n50.\n220.\n2\n10.\n1\n0\n1\n25.\n5000.\n0\n8.0000001192092896e-01\n3.4028234663852886e+38\n1\n1.0000000149011612e-01\n3.4028234663852886e+38\n1\n9.4999998807907104e-01\n3.4028234663852886e+38\n0\n\n"; assertEquals(truth, readFile(filename)); } } diff --git a/modules/features2d/misc/java/test/STARFeatureDetectorTest.java b/modules/features2d/misc/java/test/STARFeatureDetectorTest.java deleted file mode 100644 index 327084211b..0000000000 --- a/modules/features2d/misc/java/test/STARFeatureDetectorTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.opencv.test.features2d; - -import java.util.Arrays; - -import org.opencv.core.CvType; -import org.opencv.core.Mat; -import org.opencv.core.MatOfKeyPoint; -import org.opencv.core.Point; -import org.opencv.core.Scalar; -import org.opencv.core.KeyPoint; -import org.opencv.test.OpenCVTestCase; -import org.opencv.test.OpenCVTestRunner; -import org.opencv.imgproc.Imgproc; -import org.opencv.features2d.Feature2D; - -public class STARFeatureDetectorTest extends OpenCVTestCase { - - Feature2D detector; - int matSize; - KeyPoint[] truth; - - private Mat getMaskImg() { - Mat mask = new Mat(matSize, matSize, CvType.CV_8U, new Scalar(255)); - Mat right = mask.submat(0, matSize, matSize / 2, matSize); - right.setTo(new Scalar(0)); - return mask; - } - - private Mat getTestImg() { - Scalar color = new Scalar(0); - int center = matSize / 2; - int radius = 6; - int offset = 40; - - Mat img = new Mat(matSize, matSize, CvType.CV_8U, new Scalar(255)); - Imgproc.circle(img, new Point(center - offset, center), radius, color, -1); - Imgproc.circle(img, new Point(center + offset, center), radius, color, -1); - Imgproc.circle(img, new Point(center, center - offset), radius, color, -1); - Imgproc.circle(img, new Point(center, center + offset), radius, color, -1); - Imgproc.circle(img, new Point(center, center), radius, color, -1); - return img; - } - - protected void setUp() throws Exception { - super.setUp(); - detector = createClassInstance(XFEATURES2D+"StarDetector", DEFAULT_FACTORY, null, null); - matSize = 200; - truth = new KeyPoint[] { - new KeyPoint( 95, 80, 22, -1, 31.5957f, 0, -1), - new KeyPoint(105, 80, 22, -1, 31.5957f, 0, -1), - new KeyPoint( 80, 95, 22, -1, 31.5957f, 0, -1), - new KeyPoint(120, 95, 22, -1, 31.5957f, 0, -1), - new KeyPoint(100, 100, 8, -1, 30.f, 0, -1), - new KeyPoint( 80, 105, 22, -1, 31.5957f, 0, -1), - new KeyPoint(120, 105, 22, -1, 31.5957f, 0, -1), - new KeyPoint( 95, 120, 22, -1, 31.5957f, 0, -1), - new KeyPoint(105, 120, 22, -1, 31.5957f, 0, -1) - }; - } - - public void testCreate() { - assertNotNull(detector); - } - - public void testDetectListOfMatListOfListOfKeyPoint() { - fail("Not yet implemented"); - } - - public void testDetectListOfMatListOfListOfKeyPointListOfMat() { - fail("Not yet implemented"); - } - - public void testDetectMatListOfKeyPoint() { - Mat img = getTestImg(); - MatOfKeyPoint keypoints = new MatOfKeyPoint(); - - detector.detect(img, keypoints); - - assertListKeyPointEquals(Arrays.asList(truth), keypoints.toList(), EPS); - } - - public void testDetectMatListOfKeyPointMat() { - Mat img = getTestImg(); - Mat mask = getMaskImg(); - MatOfKeyPoint keypoints = new MatOfKeyPoint(); - - detector.detect(img, keypoints, mask); - - assertListKeyPointEquals(Arrays.asList(truth[0], truth[2], truth[5], truth[7]), keypoints.toList(), EPS); - } - - public void testEmpty() { -// assertFalse(detector.empty()); - fail("Not yet implemented"); - } - - public void testRead() { - Mat img = getTestImg(); - - MatOfKeyPoint keypoints1 = new MatOfKeyPoint(); - detector.detect(img, keypoints1); - - String filename = OpenCVTestRunner.getTempFileName("yml"); - writeFile(filename, "%YAML:1.0\n---\nmaxSize: 45\nresponseThreshold: 150\nlineThresholdProjected: 10\nlineThresholdBinarized: 8\nsuppressNonmaxSize: 5\n"); - detector.read(filename); - - MatOfKeyPoint keypoints2 = new MatOfKeyPoint(); - detector.detect(img, keypoints2); - - assertTrue(keypoints2.total() <= keypoints1.total()); - } - - public void testWrite() { - String filename = OpenCVTestRunner.getTempFileName("xml"); - - detector.write(filename); - -// String truth = "\n\nFeature2D.STAR\n8\n10\n45\n30\n5\n\n"; - String truth = "\n\n\n"; - assertEquals(truth, readFile(filename)); - } - - public void testWriteYml() { - String filename = OpenCVTestRunner.getTempFileName("yml"); - - detector.write(filename); - -// String truth = "%YAML:1.0\n---\nname: \"Feature2D.STAR\"\nlineThresholdBinarized: 8\nlineThresholdProjected: 10\nmaxSize: 45\nresponseThreshold: 30\nsuppressNonmaxSize: 5\n"; - String truth = "%YAML:1.0\n---\n"; - assertEquals(truth, readFile(filename)); - } - -} diff --git a/modules/features2d/misc/java/test/SURFDescriptorExtractorTest.java b/modules/features2d/misc/java/test/SURFDescriptorExtractorTest.java deleted file mode 100644 index 35131ee254..0000000000 --- a/modules/features2d/misc/java/test/SURFDescriptorExtractorTest.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.opencv.test.features2d; - -import org.opencv.core.CvType; -import org.opencv.core.Mat; -import org.opencv.core.MatOfKeyPoint; -import org.opencv.core.Point; -import org.opencv.core.Scalar; -import org.opencv.core.KeyPoint; -import org.opencv.test.OpenCVTestCase; -import org.opencv.test.OpenCVTestRunner; -import org.opencv.imgproc.Imgproc; -import org.opencv.features2d.Feature2D; - -public class SURFDescriptorExtractorTest extends OpenCVTestCase { - - Feature2D extractor; - int matSize; - - private Mat getTestImg() { - Mat cross = new Mat(matSize, matSize, CvType.CV_8U, new Scalar(255)); - Imgproc.line(cross, new Point(20, matSize / 2), new Point(matSize - 21, matSize / 2), new Scalar(100), 2); - Imgproc.line(cross, new Point(matSize / 2, 20), new Point(matSize / 2, matSize - 21), new Scalar(100), 2); - - return cross; - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - - Class[] cParams = {double.class, int.class, int.class, boolean.class, boolean.class}; - Object[] oValues = {100, 2, 4, true, false}; - extractor = createClassInstance(XFEATURES2D+"SURF", DEFAULT_FACTORY, cParams, oValues); - - matSize = 100; - } - - public void testComputeListOfMatListOfListOfKeyPointListOfMat() { - fail("Not yet implemented"); - } - - public void testComputeMatListOfKeyPointMat() { - KeyPoint point = new KeyPoint(55.775577545166016f, 44.224422454833984f, 16, 9.754629f, 8617.863f, 1, -1); - MatOfKeyPoint keypoints = new MatOfKeyPoint(point); - Mat img = getTestImg(); - Mat descriptors = new Mat(); - - extractor.compute(img, keypoints, descriptors); - - Mat truth = new Mat(1, 128, CvType.CV_32FC1) { - { - put(0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0.058821894, 0.058821894, -0.045962855, 0.046261817, 0.0085156476, - 0.0085754395, -0.0064509804, 0.0064509804, 0.00044069235, 0.00044069235, 0, 0, 0.00025723741, - 0.00025723741, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.00025723741, 0.00025723741, -0.00044069235, - 0.00044069235, 0, 0, 0.36278215, 0.36278215, -0.24688604, 0.26173124, 0.052068226, 0.052662034, - -0.032815345, 0.032815345, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.0064523756, - 0.0064523756, 0.0082002236, 0.0088908644, -0.059001274, 0.059001274, 0.045789491, 0.04648013, - 0.11961588, 0.22789426, -0.01322381, 0.18291828, -0.14042182, 0.23973691, 0.073782086, 0.23769434, - -0.027880307, 0.027880307, 0.049587864, 0.049587864, -0.33991757, 0.33991757, 0.21437603, 0.21437603, - -0.0020763327, 0.0020763327, 0.006245892, 0.006245892, -0.04067041, 0.04067041, 0.019361559, - 0.019361559, 0, 0, -0.0035977389, 0.0035977389, 0, 0, -0.00099993451, 0.00099993451, 0.040670406, - 0.040670406, -0.019361559, 0.019361559, 0.006245892, 0.006245892, -0.0020763327, 0.0020763327, - -0.00034532088, 0.00034532088, 0, 0, 0, 0, 0.00034532088, 0.00034532088, -0.00099993451, - 0.00099993451, 0, 0, 0, 0, 0.0035977389, 0.0035977389 - ); - } - }; - - assertMatEqual(truth, descriptors, EPS); - } - - public void testCreate() { - assertNotNull(extractor); - } - - public void testDescriptorSize() { - assertEquals(128, extractor.descriptorSize()); - } - - public void testDescriptorType() { - assertEquals(CvType.CV_32F, extractor.descriptorType()); - } - - public void testEmpty() { -// assertFalse(extractor.empty()); - fail("Not yet implemented"); - } - - public void testRead() { - String filename = OpenCVTestRunner.getTempFileName("yml"); - writeFile(filename, "%YAML:1.0\n---\nnOctaves: 4\nnOctaveLayers: 2\nextended: 1\nupright: 0\n"); - - extractor.read(filename); - - assertEquals(128, extractor.descriptorSize()); - } - - public void testWrite() { - String filename = OpenCVTestRunner.getTempFileName("xml"); - - extractor.write(filename); - -// String truth = "\n\nFeature2D.SURF\n1\n100.\n2\n4\n0\n\n"; - String truth = "\n\n\n"; - assertEquals(truth, readFile(filename)); - } - - public void testWriteYml() { - String filename = OpenCVTestRunner.getTempFileName("yml"); - - extractor.write(filename); - -// String truth = "%YAML:1.0\n---\nname: \"Feature2D.SURF\"\nextended: 1\nhessianThreshold: 100.\nnOctaveLayers: 2\nnOctaves: 4\nupright: 0\n"; - String truth = "%YAML:1.0\n---\n"; - assertEquals(truth, readFile(filename)); - } - -} diff --git a/modules/features2d/misc/java/test/SURFFeatureDetectorTest.java b/modules/features2d/misc/java/test/SURFFeatureDetectorTest.java deleted file mode 100644 index f15426cdb8..0000000000 --- a/modules/features2d/misc/java/test/SURFFeatureDetectorTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.opencv.test.features2d; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import org.opencv.core.CvType; -import org.opencv.core.Mat; -import org.opencv.core.MatOfKeyPoint; -import org.opencv.core.Point; -import org.opencv.core.Scalar; -import org.opencv.core.KeyPoint; -import org.opencv.test.OpenCVTestCase; -import org.opencv.test.OpenCVTestRunner; -import org.opencv.imgproc.Imgproc; -import org.opencv.features2d.Feature2D; - -public class SURFFeatureDetectorTest extends OpenCVTestCase { - - Feature2D detector; - int matSize; - KeyPoint[] truth; - - private Mat getMaskImg() { - Mat mask = new Mat(matSize, matSize, CvType.CV_8U, new Scalar(255)); - Mat right = mask.submat(0, matSize, matSize / 2, matSize); - right.setTo(new Scalar(0)); - return mask; - } - - private Mat getTestImg() { - Mat cross = new Mat(matSize, matSize, CvType.CV_8U, new Scalar(255)); - Imgproc.line(cross, new Point(20, matSize / 2), new Point(matSize - 21, matSize / 2), new Scalar(100), 2); - Imgproc.line(cross, new Point(matSize / 2, 20), new Point(matSize / 2, matSize - 21), new Scalar(100), 2); - - return cross; - } - - private void order(List points) { - Collections.sort(points, new Comparator() { - public int compare(KeyPoint p1, KeyPoint p2) { - if (p1.angle < p2.angle) - return -1; - if (p1.angle > p2.angle) - return 1; - return 0; - } - }); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - detector = createClassInstance(XFEATURES2D+"SURF", DEFAULT_FACTORY, null, null); - matSize = 100; - truth = new KeyPoint[] { - new KeyPoint(55.775578f, 55.775578f, 16, 80.245735f, 8617.8633f, 0, -1), - new KeyPoint(44.224422f, 55.775578f, 16, 170.24574f, 8617.8633f, 0, -1), - new KeyPoint(44.224422f, 44.224422f, 16, 260.24573f, 8617.8633f, 0, -1), - new KeyPoint(55.775578f, 44.224422f, 16, 350.24573f, 8617.8633f, 0, -1) - }; - } - - public void testCreate() { - assertNotNull(detector); - } - - public void testDetectListOfMatListOfListOfKeyPoint() { - - setProperty(detector, "hessianThreshold", "double", 8000); - setProperty(detector, "nOctaves", "int", 3); - setProperty(detector, "nOctaveLayers", "int", 4); - setProperty(detector, "upright", "boolean", false); - - List keypoints = new ArrayList(); - Mat cross = getTestImg(); - List crosses = new ArrayList(3); - crosses.add(cross); - crosses.add(cross); - crosses.add(cross); - - detector.detect(crosses, keypoints); - - assertEquals(3, keypoints.size()); - - for (MatOfKeyPoint mkp : keypoints) { - List lkp = mkp.toList(); - order(lkp); - assertListKeyPointEquals(Arrays.asList(truth), lkp, EPS); - } - } - - public void testDetectListOfMatListOfListOfKeyPointListOfMat() { - fail("Not yet implemented"); - } - - public void testDetectMatListOfKeyPoint() { - - setProperty(detector, "hessianThreshold", "double", 8000); - setProperty(detector, "nOctaves", "int", 3); - setProperty(detector, "nOctaveLayers", "int", 4); - setProperty(detector, "upright", "boolean", false); - - MatOfKeyPoint keypoints = new MatOfKeyPoint(); - Mat cross = getTestImg(); - - detector.detect(cross, keypoints); - - List lkp = keypoints.toList(); - order(lkp); - assertListKeyPointEquals(Arrays.asList(truth), lkp, EPS); - } - - public void testDetectMatListOfKeyPointMat() { - - setProperty(detector, "hessianThreshold", "double", 8000); - setProperty(detector, "nOctaves", "int", 3); - setProperty(detector, "nOctaveLayers", "int", 4); - setProperty(detector, "upright", "boolean", false); - - Mat img = getTestImg(); - Mat mask = getMaskImg(); - MatOfKeyPoint keypoints = new MatOfKeyPoint(); - - detector.detect(img, keypoints, mask); - - List lkp = keypoints.toList(); - order(lkp); - assertListKeyPointEquals(Arrays.asList(truth[1], truth[2]), lkp, EPS); - } - - public void testEmpty() { -// assertFalse(detector.empty()); - fail("Not yet implemented"); - } - - public void testRead() { - Mat cross = getTestImg(); - - MatOfKeyPoint keypoints1 = new MatOfKeyPoint(); - detector.detect(cross, keypoints1); - - String filename = OpenCVTestRunner.getTempFileName("yml"); - writeFile(filename, "%YAML:1.0\n---\nhessianThreshold: 8000.\noctaves: 3\noctaveLayers: 4\nupright: 0\n"); - detector.read(filename); - - MatOfKeyPoint keypoints2 = new MatOfKeyPoint(); - detector.detect(cross, keypoints2); - - assertTrue(keypoints2.total() <= keypoints1.total()); - } - - public void testWrite() { - String filename = OpenCVTestRunner.getTempFileName("xml"); - - detector.write(filename); - -// String truth = "\n\nFeature2D.SURF\n0\n100.\n3\n4\n0\n\n"; - String truth = "\n\n\n"; - assertEquals(truth, readFile(filename)); - } - - public void testWriteYml() { - String filename = OpenCVTestRunner.getTempFileName("yml"); - - detector.write(filename); - -// String truth = "%YAML:1.0\n---\nname: \"Feature2D.SURF\"\nextended: 0\nhessianThreshold: 100.\nnOctaveLayers: 3\nnOctaves: 4\nupright: 0\n"; - String truth = "%YAML:1.0\n---\n"; - assertEquals(truth, readFile(filename)); - } - -} diff --git a/modules/features2d/misc/objc/gen_dict.json b/modules/features2d/misc/objc/gen_dict.json index f7abcb8837..220a2c74df 100644 --- a/modules/features2d/misc/objc/gen_dict.json +++ b/modules/features2d/misc/objc/gen_dict.json @@ -1,4 +1,10 @@ { + "ManualFuncs" : { + "SimpleBlobDetector": { + "setParams": { "declaration" : [""], "implementation" : [""] }, + "getParams": { "declaration" : [""], "implementation" : [""] } + } + }, "enum_fix" : { "FastFeatureDetector" : { "DetectorType": "FastDetectorType" }, "AgastFeatureDetector" : { "DetectorType": "AgastDetectorType" } diff --git a/modules/features2d/src/agast.cpp b/modules/features2d/src/agast.cpp index aed092ec3e..d44608e4c1 100644 --- a/modules/features2d/src/agast.cpp +++ b/modules/features2d/src/agast.cpp @@ -7941,6 +7941,27 @@ public: : threshold(_threshold), nonmaxSuppression(_nonmaxSuppression), type(_type) {} + void read( const FileNode& fn) CV_OVERRIDE + { + // if node is empty, keep previous value + if (!fn["threshold"].empty()) + fn["threshold"] >> threshold; + if (!fn["nonmaxSuppression"].empty()) + fn["nonmaxSuppression"] >> nonmaxSuppression; + if (!fn["type"].empty()) + fn["type"] >> type; + } + void write( FileStorage& fs) const CV_OVERRIDE + { + if(fs.isOpened()) + { + fs << "name" << getDefaultName(); + fs << "threshold" << threshold; + fs << "nonmaxSuppression" << nonmaxSuppression; + fs << "type" << type; + } + } + void detect( InputArray _image, std::vector& keypoints, InputArray _mask ) CV_OVERRIDE { CV_INSTRUMENT_REGION(); diff --git a/modules/features2d/src/akaze.cpp b/modules/features2d/src/akaze.cpp index 623738eed4..7aa97dae36 100644 --- a/modules/features2d/src/akaze.cpp +++ b/modules/features2d/src/akaze.cpp @@ -207,6 +207,7 @@ namespace cv void write(FileStorage& fs) const CV_OVERRIDE { writeFormat(fs); + fs << "name" << getDefaultName(); fs << "descriptor" << descriptor; fs << "descriptor_channels" << descriptor_channels; fs << "descriptor_size" << descriptor_size; @@ -218,13 +219,21 @@ namespace cv void read(const FileNode& fn) CV_OVERRIDE { - descriptor = static_cast((int)fn["descriptor"]); - descriptor_channels = (int)fn["descriptor_channels"]; - descriptor_size = (int)fn["descriptor_size"]; - threshold = (float)fn["threshold"]; - octaves = (int)fn["octaves"]; - sublevels = (int)fn["sublevels"]; - diffusivity = static_cast((int)fn["diffusivity"]); + // if node is empty, keep previous value + if (!fn["descriptor"].empty()) + descriptor = static_cast((int)fn["descriptor"]); + if (!fn["descriptor_channels"].empty()) + descriptor_channels = (int)fn["descriptor_channels"]; + if (!fn["descriptor_size"].empty()) + descriptor_size = (int)fn["descriptor_size"]; + if (!fn["threshold"].empty()) + threshold = (float)fn["threshold"]; + if (!fn["octaves"].empty()) + octaves = (int)fn["octaves"]; + if (!fn["sublevels"].empty()) + sublevels = (int)fn["sublevels"]; + if (!fn["diffusivity"].empty()) + diffusivity = static_cast((int)fn["diffusivity"]); } DescriptorType descriptor; diff --git a/modules/features2d/src/blobdetector.cpp b/modules/features2d/src/blobdetector.cpp index f40bbf22f9..c2b366c465 100644 --- a/modules/features2d/src/blobdetector.cpp +++ b/modules/features2d/src/blobdetector.cpp @@ -65,6 +65,37 @@ public: virtual void read( const FileNode& fn ) CV_OVERRIDE; virtual void write( FileStorage& fs ) const CV_OVERRIDE; + void setParams(const SimpleBlobDetector::Params& _params ) CV_OVERRIDE { + SimpleBlobDetectorImpl::validateParameters(_params); + params = _params; + } + + SimpleBlobDetector::Params getParams() const CV_OVERRIDE { return params; } + + static void validateParameters(const SimpleBlobDetector::Params& p) + { + if (p.thresholdStep <= 0) + CV_Error(Error::StsBadArg, "thresholdStep>0"); + + if (p.minThreshold > p.maxThreshold || p.minThreshold < 0) + CV_Error(Error::StsBadArg, "0<=minThreshold<=maxThreshold"); + + if (p.minDistBetweenBlobs <=0 ) + CV_Error(Error::StsBadArg, "minDistBetweenBlobs>0"); + + if (p.minArea > p.maxArea || p.minArea <=0) + CV_Error(Error::StsBadArg, "0 p.maxCircularity || p.minCircularity <= 0) + CV_Error(Error::StsBadArg, "0 p.maxInertiaRatio || p.minInertiaRatio <= 0) + CV_Error(Error::StsBadArg, "0 p.maxConvexity || p.minConvexity <= 0) + CV_Error(Error::StsBadArg, "0& keypoints, InputArray mask=noArray() ) CV_OVERRIDE; - virtual void findBlobs(InputArray image, InputArray binaryImage, std::vector
¢ers) const; + virtual void findBlobs(InputArray image, InputArray binaryImage, std::vector
¢ers, + std::vector > &contours, std::vector &moments) const; + virtual const std::vector >& getBlobContours() const CV_OVERRIDE; Params params; + std::vector > blobContours; }; /* @@ -110,6 +144,8 @@ SimpleBlobDetector::Params::Params() //minConvexity = 0.8; minConvexity = 0.95f; maxConvexity = std::numeric_limits::max(); + + collectContours = false; } void SimpleBlobDetector::Params::read(const cv::FileNode& fn ) @@ -139,6 +175,8 @@ void SimpleBlobDetector::Params::read(const cv::FileNode& fn ) filterByConvexity = (int)fn["filterByConvexity"] != 0 ? true : false; minConvexity = fn["minConvexity"]; maxConvexity = fn["maxConvexity"]; + + collectContours = (int)fn["collectContours"] != 0 ? true : false; } void SimpleBlobDetector::Params::write(cv::FileStorage& fs) const @@ -168,6 +206,8 @@ void SimpleBlobDetector::Params::write(cv::FileStorage& fs) const fs << "filterByConvexity" << (int)filterByConvexity; fs << "minConvexity" << minConvexity; fs << "maxConvexity" << maxConvexity; + + fs << "collectContours" << (int)collectContours; } SimpleBlobDetectorImpl::SimpleBlobDetectorImpl(const SimpleBlobDetector::Params ¶meters) : @@ -177,7 +217,10 @@ params(parameters) void SimpleBlobDetectorImpl::read( const cv::FileNode& fn ) { - params.read(fn); + SimpleBlobDetector::Params rp; + rp.read(fn); + SimpleBlobDetectorImpl::validateParameters(rp); + params = rp; } void SimpleBlobDetectorImpl::write( cv::FileStorage& fs ) const @@ -186,13 +229,16 @@ void SimpleBlobDetectorImpl::write( cv::FileStorage& fs ) const params.write(fs); } -void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImage, std::vector
¢ers) const +void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImage, std::vector
¢ers, + std::vector > &contoursOut, std::vector &momentss) const { CV_INSTRUMENT_REGION(); Mat image = _image.getMat(), binaryImage = _binaryImage.getMat(); CV_UNUSED(image); centers.clear(); + contoursOut.clear(); + momentss.clear(); std::vector < std::vector > contours; findContours(binaryImage, contours, RETR_LIST, CHAIN_APPROX_NONE); @@ -291,7 +337,11 @@ void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImag } centers.push_back(center); - + if (params.collectContours) + { + contoursOut.push_back(contours[contourIdx]); + momentss.push_back(moms); + } #ifdef DEBUG_BLOB_DETECTOR circle( keypointsImage, center.location, 1, Scalar(0,0,255), 1 ); @@ -308,6 +358,8 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& CV_INSTRUMENT_REGION(); keypoints.clear(); + blobContours.clear(); + CV_Assert(params.minRepeatability != 0); Mat grayscaleImage; if (image.channels() == 3 || image.channels() == 4) @@ -328,14 +380,19 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& } std::vector < std::vector
> centers; + std::vector momentss; for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep) { Mat binarizedImage; threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY); std::vector < Center > curCenters; - findBlobs(grayscaleImage, binarizedImage, curCenters); + std::vector > curContours; + std::vector curMomentss; + findBlobs(grayscaleImage, binarizedImage, curCenters, curContours, curMomentss); std::vector < std::vector
> newCenters; + std::vector > newContours; + std::vector newMomentss; for (size_t i = 0; i < curCenters.size(); i++) { bool isNew = true; @@ -353,15 +410,37 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& centers[j][k] = centers[j][k-1]; k--; } + + if (params.collectContours) + { + if (curCenters[i].confidence > centers[j][k].confidence + || (curCenters[i].confidence == centers[j][k].confidence && curMomentss[i].m00 > momentss[j].m00)) + { + blobContours[j] = curContours[i]; + momentss[j] = curMomentss[i]; + } + } centers[j][k] = curCenters[i]; break; } } if (isNew) + { newCenters.push_back(std::vector
(1, curCenters[i])); + if (params.collectContours) + { + newContours.push_back(curContours[i]); + newMomentss.push_back(curMomentss[i]); + } + } } std::copy(newCenters.begin(), newCenters.end(), std::back_inserter(centers)); + if (params.collectContours) + { + std::copy(newContours.begin(), newContours.end(), std::back_inserter(blobContours)); + std::copy(newMomentss.begin(), newMomentss.end(), std::back_inserter(momentss)); + } } for (size_t i = 0; i < centers.size(); i++) @@ -382,12 +461,24 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& if (!mask.empty()) { - KeyPointsFilter::runByPixelsMask(keypoints, mask.getMat()); + if (params.collectContours) + { + KeyPointsFilter::runByPixelsMask2VectorPoint(keypoints, blobContours, mask.getMat()); + } + else + { + KeyPointsFilter::runByPixelsMask(keypoints, mask.getMat()); + } } } +const std::vector >& SimpleBlobDetectorImpl::getBlobContours() const { + return blobContours; +} + Ptr SimpleBlobDetector::create(const SimpleBlobDetector::Params& params) { + SimpleBlobDetectorImpl::validateParameters(params); return makePtr(params); } diff --git a/modules/features2d/src/brisk.cpp b/modules/features2d/src/brisk.cpp index adb8b43d30..59a86470f6 100644 --- a/modules/features2d/src/brisk.cpp +++ b/modules/features2d/src/brisk.cpp @@ -54,7 +54,7 @@ namespace cv class BRISK_Impl CV_FINAL : public BRISK { public: - explicit BRISK_Impl(int thresh=30, int octaves=3, float patternScale=1.0f); + explicit BRISK_Impl(int _threshold=30, int _octaves=3, float _patternScale=1.0f); // custom setup explicit BRISK_Impl(const std::vector &radiusList, const std::vector &numberList, float dMax=5.85f, float dMin=8.2f, const std::vector indexChange=std::vector()); @@ -65,6 +65,9 @@ public: virtual ~BRISK_Impl(); + void read( const FileNode& fn) CV_OVERRIDE; + void write( FileStorage& fs) const CV_OVERRIDE; + int descriptorSize() const CV_OVERRIDE { return strings_; @@ -99,6 +102,35 @@ public: { return octaves; } + virtual void setPatternScale(float _patternScale) CV_OVERRIDE + { + patternScale = _patternScale; + std::vector rList; + std::vector nList; + + // this is the standard pattern found to be suitable also + rList.resize(5); + nList.resize(5); + const double f = 0.85 * patternScale; + + rList[0] = (float)(f * 0.); + rList[1] = (float)(f * 2.9); + rList[2] = (float)(f * 4.9); + rList[3] = (float)(f * 7.4); + rList[4] = (float)(f * 10.8); + + nList[0] = 1; + nList[1] = 10; + nList[2] = 14; + nList[3] = 15; + nList[4] = 20; + + generateKernel(rList, nList, (float)(5.85 * patternScale), (float)(8.2 * patternScale)); + } + virtual float getPatternScale() const CV_OVERRIDE + { + return patternScale; + } // call this to generate the kernel: // circle of radius r (pixels), with n points; @@ -122,6 +154,7 @@ protected: // Feature parameters CV_PROP_RW int threshold; CV_PROP_RW int octaves; + CV_PROP_RW float patternScale; // some helper structures for the Brisk pattern representation struct BriskPatternPoint{ @@ -309,32 +342,12 @@ const float BriskScaleSpace::safetyFactor_ = 1.0f; const float BriskScaleSpace::basicSize_ = 12.0f; // constructors -BRISK_Impl::BRISK_Impl(int thresh, int octaves_in, float patternScale) +BRISK_Impl::BRISK_Impl(int _threshold, int _octaves, float _patternScale) { - threshold = thresh; - octaves = octaves_in; + threshold = _threshold; + octaves = _octaves; - std::vector rList; - std::vector nList; - - // this is the standard pattern found to be suitable also - rList.resize(5); - nList.resize(5); - const double f = 0.85 * patternScale; - - rList[0] = (float)(f * 0.); - rList[1] = (float)(f * 2.9); - rList[2] = (float)(f * 4.9); - rList[3] = (float)(f * 7.4); - rList[4] = (float)(f * 10.8); - - nList[0] = 1; - nList[1] = 10; - nList[2] = 14; - nList[3] = 15; - nList[4] = 20; - - generateKernel(rList, nList, (float)(5.85 * patternScale), (float)(8.2 * patternScale)); + setPatternScale(_patternScale); } BRISK_Impl::BRISK_Impl(const std::vector &radiusList, @@ -359,6 +372,31 @@ BRISK_Impl::BRISK_Impl(int thresh, octaves = octaves_in; } +void BRISK_Impl::read( const FileNode& fn) +{ + // if node is empty, keep previous value + if (!fn["threshold"].empty()) + fn["threshold"] >> threshold; + if (!fn["octaves"].empty()) + fn["octaves"] >> octaves; + if (!fn["patternScale"].empty()) + { + float _patternScale; + fn["patternScale"] >> _patternScale; + setPatternScale(_patternScale); + } +} +void BRISK_Impl::write( FileStorage& fs) const +{ + if(fs.isOpened()) + { + fs << "name" << getDefaultName(); + fs << "threshold" << threshold; + fs << "octaves" << octaves; + fs << "patternScale" << patternScale; + } +} + void BRISK_Impl::generateKernel(const std::vector &radiusList, const std::vector &numberList, diff --git a/modules/features2d/src/fast.avx2.cpp b/modules/features2d/src/fast.avx2.cpp index ca0bcb9269..72e7d66924 100644 --- a/modules/features2d/src/fast.avx2.cpp +++ b/modules/features2d/src/fast.avx2.cpp @@ -165,7 +165,7 @@ public: _mm256_zeroupper(); } - virtual ~FAST_t_patternSize16_AVX2_Impl() CV_OVERRIDE {}; + virtual ~FAST_t_patternSize16_AVX2_Impl() CV_OVERRIDE {} private: int cols; diff --git a/modules/features2d/src/fast.cpp b/modules/features2d/src/fast.cpp index 42e5188bc0..3e88f3e3f5 100644 --- a/modules/features2d/src/fast.cpp +++ b/modules/features2d/src/fast.cpp @@ -531,6 +531,27 @@ public: : threshold(_threshold), nonmaxSuppression(_nonmaxSuppression), type(_type) {} + void read( const FileNode& fn) CV_OVERRIDE + { + // if node is empty, keep previous value + if (!fn["threshold"].empty()) + fn["threshold"] >> threshold; + if (!fn["nonmaxSuppression"].empty()) + fn["nonmaxSuppression"] >> nonmaxSuppression; + if (!fn["type"].empty()) + fn["type"] >> type; + } + void write( FileStorage& fs) const CV_OVERRIDE + { + if(fs.isOpened()) + { + fs << "name" << getDefaultName(); + fs << "threshold" << threshold; + fs << "nonmaxSuppression" << nonmaxSuppression; + fs << "type" << type; + } + } + void detect( InputArray _image, std::vector& keypoints, InputArray _mask ) CV_OVERRIDE { CV_INSTRUMENT_REGION(); diff --git a/modules/features2d/src/fast.hpp b/modules/features2d/src/fast.hpp index 6f750fea8d..1e221cbdf4 100644 --- a/modules/features2d/src/fast.hpp +++ b/modules/features2d/src/fast.hpp @@ -54,7 +54,7 @@ class FAST_t_patternSize16_AVX2 public: static Ptr getImpl(int _cols, int _threshold, bool _nonmax_suppression, const int* _pixel); virtual void process(int &j, const uchar* &ptr, uchar* curr, int* cornerpos, int &ncorners) = 0; - virtual ~FAST_t_patternSize16_AVX2() {}; + virtual ~FAST_t_patternSize16_AVX2() {} }; #endif } diff --git a/modules/features2d/src/gftt.cpp b/modules/features2d/src/gftt.cpp index bc97fc1677..0f98efce61 100644 --- a/modules/features2d/src/gftt.cpp +++ b/modules/features2d/src/gftt.cpp @@ -55,6 +55,39 @@ public: { } + void read( const FileNode& fn) CV_OVERRIDE + { + // if node is empty, keep previous value + if (!fn["nfeatures"].empty()) + fn["nfeatures"] >> nfeatures; + if (!fn["qualityLevel"].empty()) + fn["qualityLevel"] >> qualityLevel; + if (!fn["minDistance"].empty()) + fn["minDistance"] >> minDistance; + if (!fn["blockSize"].empty()) + fn["blockSize"] >> blockSize; + if (!fn["gradSize"].empty()) + fn["gradSize"] >> gradSize; + if (!fn["useHarrisDetector"].empty()) + fn["useHarrisDetector"] >> useHarrisDetector; + if (!fn["k"].empty()) + fn["k"] >> k; + } + void write( FileStorage& fs) const CV_OVERRIDE + { + if(fs.isOpened()) + { + fs << "name" << getDefaultName(); + fs << "nfeatures" << nfeatures; + fs << "qualityLevel" << qualityLevel; + fs << "minDistance" << minDistance; + fs << "blockSize" << blockSize; + fs << "gradSize" << gradSize; + fs << "useHarrisDetector" << useHarrisDetector; + fs << "k" << k; + } + } + void setMaxFeatures(int maxFeatures) CV_OVERRIDE { nfeatures = maxFeatures; } int getMaxFeatures() const CV_OVERRIDE { return nfeatures; } @@ -67,8 +100,8 @@ public: void setBlockSize(int blockSize_) CV_OVERRIDE { blockSize = blockSize_; } int getBlockSize() const CV_OVERRIDE { return blockSize; } - //void setGradientSize(int gradientSize_) { gradSize = gradientSize_; } - //int getGradientSize() { return gradSize; } + void setGradientSize(int gradientSize_) CV_OVERRIDE { gradSize = gradientSize_; } + int getGradientSize() CV_OVERRIDE { return gradSize; } void setHarrisDetector(bool val) CV_OVERRIDE { useHarrisDetector = val; } bool getHarrisDetector() const CV_OVERRIDE { return useHarrisDetector; } diff --git a/modules/features2d/src/kaze.cpp b/modules/features2d/src/kaze.cpp index 0d841b7f7b..881c5aab1e 100644 --- a/modules/features2d/src/kaze.cpp +++ b/modules/features2d/src/kaze.cpp @@ -163,6 +163,7 @@ namespace cv void write(FileStorage& fs) const CV_OVERRIDE { writeFormat(fs); + fs << "name" << getDefaultName(); fs << "extended" << (int)extended; fs << "upright" << (int)upright; fs << "threshold" << threshold; @@ -173,12 +174,19 @@ namespace cv void read(const FileNode& fn) CV_OVERRIDE { - extended = (int)fn["extended"] != 0; - upright = (int)fn["upright"] != 0; - threshold = (float)fn["threshold"]; - octaves = (int)fn["octaves"]; - sublevels = (int)fn["sublevels"]; - diffusivity = static_cast((int)fn["diffusivity"]); + // if node is empty, keep previous value + if (!fn["extended"].empty()) + extended = (int)fn["extended"] != 0; + if (!fn["upright"].empty()) + upright = (int)fn["upright"] != 0; + if (!fn["threshold"].empty()) + threshold = (float)fn["threshold"]; + if (!fn["octaves"].empty()) + octaves = (int)fn["octaves"]; + if (!fn["sublevels"].empty()) + sublevels = (int)fn["sublevels"]; + if (!fn["diffusivity"].empty()) + diffusivity = static_cast((int)fn["diffusivity"]); } bool extended; diff --git a/modules/features2d/src/keypoint.cpp b/modules/features2d/src/keypoint.cpp index 21d9eb30f7..4d2007f6d7 100644 --- a/modules/features2d/src/keypoint.cpp +++ b/modules/features2d/src/keypoint.cpp @@ -165,6 +165,29 @@ void KeyPointsFilter::runByPixelsMask( std::vector& keypoints, const M keypoints.erase(std::remove_if(keypoints.begin(), keypoints.end(), MaskPredicate(mask)), keypoints.end()); } +/* + * Remove objects from some image and a vector by mask for pixels of this image + */ +template +void runByPixelsMask2(std::vector &keypoints, std::vector &removeFrom, const Mat &mask) +{ + if (mask.empty()) + return; + + MaskPredicate maskPredicate(mask); + removeFrom.erase(std::remove_if(removeFrom.begin(), removeFrom.end(), + [&](const T &x) + { + auto index = &x - &removeFrom.front(); + return maskPredicate(keypoints[index]); + }), + removeFrom.end()); + keypoints.erase(std::remove_if(keypoints.begin(), keypoints.end(), maskPredicate), keypoints.end()); +} +void KeyPointsFilter::runByPixelsMask2VectorPoint(std::vector &keypoints, std::vector > &removeFrom, const Mat &mask) +{ + runByPixelsMask2(keypoints, removeFrom, mask); +} struct KeyPoint_LessThan { diff --git a/modules/features2d/src/mser.cpp b/modules/features2d/src/mser.cpp index c9b2e1be90..39bcbf6938 100644 --- a/modules/features2d/src/mser.cpp +++ b/modules/features2d/src/mser.cpp @@ -87,6 +87,48 @@ public: virtual ~MSER_Impl() CV_OVERRIDE {} + void read( const FileNode& fn) CV_OVERRIDE + { + // if node is empty, keep previous value + if (!fn["delta"].empty()) + fn["delta"] >> params.delta; + if (!fn["minArea"].empty()) + fn["minArea"] >> params.minArea; + if (!fn["maxArea"].empty()) + fn["maxArea"] >> params.maxArea; + if (!fn["maxVariation"].empty()) + fn["maxVariation"] >> params.maxVariation; + if (!fn["minDiversity"].empty()) + fn["minDiversity"] >> params.minDiversity; + if (!fn["maxEvolution"].empty()) + fn["maxEvolution"] >> params.maxEvolution; + if (!fn["areaThreshold"].empty()) + fn["areaThreshold"] >> params.areaThreshold; + if (!fn["minMargin"].empty()) + fn["minMargin"] >> params.minMargin; + if (!fn["edgeBlurSize"].empty()) + fn["edgeBlurSize"] >> params.edgeBlurSize; + if (!fn["pass2Only"].empty()) + fn["pass2Only"] >> params.pass2Only; + } + void write( FileStorage& fs) const CV_OVERRIDE + { + if(fs.isOpened()) + { + fs << "name" << getDefaultName(); + fs << "delta" << params.delta; + fs << "minArea" << params.minArea; + fs << "maxArea" << params.maxArea; + fs << "maxVariation" << params.maxVariation; + fs << "minDiversity" << params.minDiversity; + fs << "maxEvolution" << params.maxEvolution; + fs << "areaThreshold" << params.areaThreshold; + fs << "minMargin" << params.minMargin; + fs << "edgeBlurSize" << params.edgeBlurSize; + fs << "pass2Only" << params.pass2Only; + } + } + void setDelta(int delta) CV_OVERRIDE { params.delta = delta; } int getDelta() const CV_OVERRIDE { return params.delta; } @@ -96,9 +138,24 @@ public: void setMaxArea(int maxArea) CV_OVERRIDE { params.maxArea = maxArea; } int getMaxArea() const CV_OVERRIDE { return params.maxArea; } + void setMaxVariation(double maxVariation) CV_OVERRIDE { params.maxVariation = maxVariation; } + double getMaxVariation() const CV_OVERRIDE { return params.maxVariation; } + void setMinDiversity(double minDiversity) CV_OVERRIDE { params.minDiversity = minDiversity; } double getMinDiversity() const CV_OVERRIDE { return params.minDiversity; } + void setMaxEvolution(int maxEvolution) CV_OVERRIDE { params.maxEvolution = maxEvolution; } + int getMaxEvolution() const CV_OVERRIDE { return params.maxEvolution; } + + void setAreaThreshold(double areaThreshold) CV_OVERRIDE { params.areaThreshold = areaThreshold; } + double getAreaThreshold() const CV_OVERRIDE { return params.areaThreshold; } + + void setMinMargin(double min_margin) CV_OVERRIDE { params.minMargin = min_margin; } + double getMinMargin() const CV_OVERRIDE { return params.minMargin; } + + void setEdgeBlurSize(int edge_blur_size) CV_OVERRIDE { params.edgeBlurSize = edge_blur_size; } + int getEdgeBlurSize() const CV_OVERRIDE { return params.edgeBlurSize; } + void setPass2Only(bool f) CV_OVERRIDE { params.pass2Only = f; } bool getPass2Only() const CV_OVERRIDE { return params.pass2Only; } diff --git a/modules/features2d/src/orb.cpp b/modules/features2d/src/orb.cpp index ae1e611127..b0d32002a1 100644 --- a/modules/features2d/src/orb.cpp +++ b/modules/features2d/src/orb.cpp @@ -666,6 +666,9 @@ public: scoreType(_scoreType), patchSize(_patchSize), fastThreshold(_fastThreshold) {} + void read( const FileNode& fn) CV_OVERRIDE; + void write( FileStorage& fs) const CV_OVERRIDE; + void setMaxFeatures(int maxFeatures) CV_OVERRIDE { nfeatures = maxFeatures; } int getMaxFeatures() const CV_OVERRIDE { return nfeatures; } @@ -717,6 +720,45 @@ protected: int fastThreshold; }; +void ORB_Impl::read( const FileNode& fn) +{ + // if node is empty, keep previous value + if (!fn["nfeatures"].empty()) + fn["nfeatures"] >> nfeatures; + if (!fn["scaleFactor"].empty()) + fn["scaleFactor"] >> scaleFactor; + if (!fn["nlevels"].empty()) + fn["nlevels"] >> nlevels; + if (!fn["edgeThreshold"].empty()) + fn["edgeThreshold"] >> edgeThreshold; + if (!fn["firstLevel"].empty()) + fn["firstLevel"] >> firstLevel; + if (!fn["wta_k"].empty()) + fn["wta_k"] >> wta_k; + if (!fn["scoreType"].empty()) + fn["scoreType"] >> scoreType; + if (!fn["patchSize"].empty()) + fn["patchSize"] >> patchSize; + if (!fn["fastThreshold"].empty()) + fn["fastThreshold"] >> fastThreshold; +} +void ORB_Impl::write( FileStorage& fs) const +{ + if(fs.isOpened()) + { + fs << "name" << getDefaultName(); + fs << "nfeatures" << nfeatures; + fs << "scaleFactor" << scaleFactor; + fs << "nlevels" << nlevels; + fs << "edgeThreshold" << edgeThreshold; + fs << "firstLevel" << firstLevel; + fs << "wta_k" << wta_k; + fs << "scoreType" << scoreType; + fs << "patchSize" << patchSize; + fs << "fastThreshold" << fastThreshold; + } +} + int ORB_Impl::descriptorSize() const { return kBytes; diff --git a/modules/features2d/src/sift.dispatch.cpp b/modules/features2d/src/sift.dispatch.cpp index 831750862b..7c72b37898 100644 --- a/modules/features2d/src/sift.dispatch.cpp +++ b/modules/features2d/src/sift.dispatch.cpp @@ -111,6 +111,24 @@ public: void findScaleSpaceExtrema( const std::vector& gauss_pyr, const std::vector& dog_pyr, std::vector& keypoints ) const; + void read( const FileNode& fn) CV_OVERRIDE; + void write( FileStorage& fs) const CV_OVERRIDE; + + void setNFeatures(int maxFeatures) CV_OVERRIDE { nfeatures = maxFeatures; } + int getNFeatures() const CV_OVERRIDE { return nfeatures; } + + void setNOctaveLayers(int nOctaveLayers_) CV_OVERRIDE { nOctaveLayers = nOctaveLayers_; } + int getNOctaveLayers() const CV_OVERRIDE { return nOctaveLayers; } + + void setContrastThreshold(double contrastThreshold_) CV_OVERRIDE { contrastThreshold = contrastThreshold_; } + double getContrastThreshold() const CV_OVERRIDE { return contrastThreshold; } + + void setEdgeThreshold(double edgeThreshold_) CV_OVERRIDE { edgeThreshold = edgeThreshold_; } + double getEdgeThreshold() const CV_OVERRIDE { return edgeThreshold; } + + void setSigma(double sigma_) CV_OVERRIDE { sigma = sigma_; } + double getSigma() const CV_OVERRIDE { return sigma; } + protected: CV_PROP_RW int nfeatures; CV_PROP_RW int nOctaveLayers; @@ -554,4 +572,34 @@ void SIFT_Impl::detectAndCompute(InputArray _image, InputArray _mask, } } +void SIFT_Impl::read( const FileNode& fn) +{ + // if node is empty, keep previous value + if (!fn["nfeatures"].empty()) + fn["nfeatures"] >> nfeatures; + if (!fn["nOctaveLayers"].empty()) + fn["nOctaveLayers"] >> nOctaveLayers; + if (!fn["contrastThreshold"].empty()) + fn["contrastThreshold"] >> contrastThreshold; + if (!fn["edgeThreshold"].empty()) + fn["edgeThreshold"] >> edgeThreshold; + if (!fn["sigma"].empty()) + fn["sigma"] >> sigma; + if (!fn["descriptorType"].empty()) + fn["descriptorType"] >> descriptor_type; +} +void SIFT_Impl::write( FileStorage& fs) const +{ + if(fs.isOpened()) + { + fs << "name" << getDefaultName(); + fs << "nfeatures" << nfeatures; + fs << "nOctaveLayers" << nOctaveLayers; + fs << "contrastThreshold" << contrastThreshold; + fs << "edgeThreshold" << edgeThreshold; + fs << "sigma" << sigma; + fs << "descriptorType" << descriptor_type; + } +} + } diff --git a/modules/features2d/test/test_blobdetector.cpp b/modules/features2d/test/test_blobdetector.cpp index c6ed09e6a2..2aa99b60f7 100644 --- a/modules/features2d/test/test_blobdetector.cpp +++ b/modules/features2d/test/test_blobdetector.cpp @@ -19,4 +19,28 @@ TEST(Features2d_BlobDetector, bug_6667) detector->detect(image, keypoints); ASSERT_NE((int) keypoints.size(), 0); } + +TEST(Features2d_BlobDetector, withContours) +{ + cv::Mat image = cv::Mat(cv::Size(100, 100), CV_8UC1, cv::Scalar(255, 255, 255)); + cv::circle(image, Point(50, 50), 20, cv::Scalar(0), -1); + SimpleBlobDetector::Params params; + params.minThreshold = 250; + params.maxThreshold = 260; + params.minRepeatability = 1; // https://github.com/opencv/opencv/issues/6667 + params.collectContours = true; + std::vector keypoints; + + Ptr detector = SimpleBlobDetector::create(params); + detector->detect(image, keypoints); + ASSERT_NE((int)keypoints.size(), 0); + + ASSERT_GT((int)detector->getBlobContours().size(), 0); + std::vector contour = detector->getBlobContours()[0]; + ASSERT_TRUE(std::any_of(contour.begin(), contour.end(), + [](Point p) + { + return abs(p.x - 30) < 2 && abs(p.y - 50) < 2; + })); +} }} // namespace diff --git a/modules/gapi/CMakeLists.txt b/modules/gapi/CMakeLists.txt index 0cac0a8789..f3979c0bbd 100644 --- a/modules/gapi/CMakeLists.txt +++ b/modules/gapi/CMakeLists.txt @@ -41,6 +41,9 @@ if(MSVC) # and IE deprecated code warning C4996 ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4503 /wd4996) endif() + if(MSVC_VERSION LESS 1920) # MSVS 2015/2017 + ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4702) # 'unreachable code' + endif() endif() file(GLOB gapi_ext_hdrs @@ -113,6 +116,8 @@ set(gapi_srcs src/compiler/passes/intrin.cpp # Executor + src/executor/gabstractexecutor.cpp + src/executor/gabstractstreamingexecutor.cpp src/executor/gexecutor.cpp src/executor/gtbbexecutor.cpp src/executor/gstreamingexecutor.cpp @@ -176,6 +181,7 @@ set(gapi_srcs # Python bridge src/backends/ie/bindings_ie.cpp + src/backends/onnx/bindings_onnx.cpp src/backends/python/gpythonbackend.cpp # OpenVPL Streaming source @@ -185,6 +191,7 @@ set(gapi_srcs src/streaming/onevpl/cfg_params.cpp src/streaming/onevpl/cfg_params_parser.cpp src/streaming/onevpl/utils.cpp + src/streaming/onevpl/default.cpp src/streaming/onevpl/data_provider_interface_exception.cpp src/streaming/onevpl/accelerators/surface/base_frame_adapter.cpp src/streaming/onevpl/accelerators/surface/cpu_frame_adapter.cpp @@ -315,22 +322,23 @@ if(HAVE_GAPI_ONEVPL) ocv_target_compile_definitions(${the_module} PRIVATE -DHAVE_ONEVPL) ocv_target_link_libraries(${the_module} PRIVATE ${VPL_IMPORTED_TARGETS}) + if(HAVE_DIRECTX AND HAVE_D3D11) + ocv_target_link_libraries(${the_module} PRIVATE d3d11 dxgi) + endif() + if(WIN32) + ocv_target_link_libraries(${the_module} PRIVATE mf mfuuid mfplat shlwapi mfreadwrite) + endif() if(HAVE_D3D11 AND HAVE_OPENCL) ocv_target_include_directories(${the_module} SYSTEM PRIVATE ${OPENCL_INCLUDE_DIRS}) endif() - if(UNIX) - if(WITH_VA) - include("${OpenCV_SOURCE_DIR}/cmake/OpenCVFindVA.cmake") - if(VA_INCLUDE_DIR) - ocv_target_include_directories(${the_module} SYSTEM PRIVATE ${VA_INCLUDE_DIR}) - ocv_target_include_directories(opencv_test_gapi SYSTEM PRIVATE ${VA_INCLUDE_DIR}) - ocv_target_link_libraries(${the_module} PRIVATE ${VA_LIBRARIES}) - ocv_target_link_libraries(opencv_test_gapi PRIVATE ${VA_LIBRARIES}) - endif() - else(WITH_VA) - message(FATAL_ERROR "libva not found: building HAVE_GAPI_ONEVPL without libVA support is impossible on UNIX systems") - endif(WITH_VA) + if(UNIX AND HAVE_VA) + ocv_target_include_directories(${the_module} SYSTEM PRIVATE ${VA_INCLUDE_DIR}) + ocv_target_link_libraries(${the_module} PRIVATE ${VA_LIBRARIES}) + if(TARGET opencv_test_gapi) + ocv_target_include_directories(opencv_test_gapi SYSTEM PRIVATE ${VA_INCLUDE_DIR}) + ocv_target_link_libraries(opencv_test_gapi PRIVATE ${VA_LIBRARIES}) + endif() endif() endif() @@ -363,17 +371,20 @@ ocv_add_samples() # Required for sample with inference on host -if(TARGET example_gapi_onevpl_infer_single_roi) +if(TARGET example_gapi_onevpl_infer_with_advanced_device_selection) if(TARGET ocv.3rdparty.openvino AND OPENCV_GAPI_WITH_OPENVINO) - ocv_target_link_libraries(example_gapi_onevpl_infer_single_roi PRIVATE ocv.3rdparty.openvino) + ocv_target_link_libraries(example_gapi_onevpl_infer_with_advanced_device_selection PRIVATE ocv.3rdparty.openvino) + endif() + if(HAVE_DIRECTX AND HAVE_D3D11) + ocv_target_link_libraries(example_gapi_onevpl_infer_with_advanced_device_selection PRIVATE d3d11 dxgi) endif() if(HAVE_D3D11 AND HAVE_OPENCL) - ocv_target_include_directories(example_gapi_onevpl_infer_single_roi SYSTEM PRIVATE ${OPENCL_INCLUDE_DIRS}) + ocv_target_include_directories(example_gapi_onevpl_infer_with_advanced_device_selection SYSTEM PRIVATE ${OPENCL_INCLUDE_DIRS}) endif() - if(WITH_VA AND UNIX) - message ("GAPI VPL samples with VAAPI") - ocv_target_include_directories(example_gapi_onevpl_infer_single_roi SYSTEM PRIVATE ${VA_INCLUDE_DIR}) - ocv_target_link_libraries(example_gapi_onevpl_infer_single_roi PRIVATE ${VA_LIBRARIES}) + if(UNIX AND HAVE_VA) + message(STATUS "GAPI VPL samples with VAAPI") + ocv_target_include_directories(example_gapi_onevpl_infer_with_advanced_device_selection SYSTEM PRIVATE ${VA_INCLUDE_DIR}) + ocv_target_link_libraries(example_gapi_onevpl_infer_with_advanced_device_selection PRIVATE ${VA_LIBRARIES}) endif() endif() diff --git a/modules/gapi/cmake/DownloadADE.cmake b/modules/gapi/cmake/DownloadADE.cmake index 7a54f62cae..3157436369 100644 --- a/modules/gapi/cmake/DownloadADE.cmake +++ b/modules/gapi/cmake/DownloadADE.cmake @@ -1,7 +1,7 @@ set(ade_src_dir "${OpenCV_BINARY_DIR}/3rdparty/ade") -set(ade_filename "v0.1.2.zip") -set(ade_subdir "ade-0.1.2") -set(ade_md5 "561c1e28ccf27ad0557a18e251c22226") +set(ade_filename "v0.1.2a.zip") +set(ade_subdir "ade-0.1.2a") +set(ade_md5 "fa4b3e25167319cb0fa9432ef8281945") ocv_download(FILENAME ${ade_filename} HASH ${ade_md5} URL diff --git a/modules/gapi/include/opencv2/gapi/core.hpp b/modules/gapi/include/opencv2/gapi/core.hpp index 4175da09cc..60bb2c5074 100644 --- a/modules/gapi/include/opencv2/gapi/core.hpp +++ b/modules/gapi/include/opencv2/gapi/core.hpp @@ -635,7 +635,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref */ GAPI_EXPORTS_W GMat addC(const GMat& src1, const GScalar& c, int ddepth = -1); //! @overload -GAPI_EXPORTS GMat addC(const GScalar& c, const GMat& src1, int ddepth = -1); +GAPI_EXPORTS_W GMat addC(const GScalar& c, const GMat& src1, int ddepth = -1); /** @brief Calculates the per-element difference between two matrices. @@ -660,7 +660,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix. @sa add, addC */ -GAPI_EXPORTS GMat sub(const GMat& src1, const GMat& src2, int ddepth = -1); +GAPI_EXPORTS_W GMat sub(const GMat& src1, const GMat& src2, int ddepth = -1); /** @brief Calculates the per-element difference between matrix and given scalar. @@ -679,7 +679,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix. @sa add, addC, subRC */ -GAPI_EXPORTS GMat subC(const GMat& src, const GScalar& c, int ddepth = -1); +GAPI_EXPORTS_W GMat subC(const GMat& src, const GScalar& c, int ddepth = -1); /** @brief Calculates the per-element difference between given scalar and the matrix. @@ -698,7 +698,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix. @sa add, addC, subC */ -GAPI_EXPORTS GMat subRC(const GScalar& c, const GMat& src, int ddepth = -1); +GAPI_EXPORTS_W GMat subRC(const GScalar& c, const GMat& src, int ddepth = -1); /** @brief Calculates the per-element scaled product of two matrices. @@ -719,7 +719,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix. @sa add, sub, div, addWeighted */ -GAPI_EXPORTS GMat mul(const GMat& src1, const GMat& src2, double scale = 1.0, int ddepth = -1); +GAPI_EXPORTS_W GMat mul(const GMat& src1, const GMat& src2, double scale = 1.0, int ddepth = -1); /** @brief Multiplies matrix by scalar. @@ -737,11 +737,11 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix. If -1, the depth of output matrix will be the same as input matrix depth. @sa add, sub, div, addWeighted */ -GAPI_EXPORTS GMat mulC(const GMat& src, double multiplier, int ddepth = -1); +GAPI_EXPORTS_W GMat mulC(const GMat& src, double multiplier, int ddepth = -1); //! @overload -GAPI_EXPORTS GMat mulC(const GMat& src, const GScalar& multiplier, int ddepth = -1); // FIXME: merge with mulc +GAPI_EXPORTS_W GMat mulC(const GMat& src, const GScalar& multiplier, int ddepth = -1); // FIXME: merge with mulc //! @overload -GAPI_EXPORTS GMat mulC(const GScalar& multiplier, const GMat& src, int ddepth = -1); // FIXME: merge with mulc +GAPI_EXPORTS_W GMat mulC(const GScalar& multiplier, const GMat& src, int ddepth = -1); // FIXME: merge with mulc /** @brief Performs per-element division of two matrices. @@ -764,7 +764,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix; you can only pass -1 when src1.depth() == src2.depth(). @sa mul, add, sub */ -GAPI_EXPORTS GMat div(const GMat& src1, const GMat& src2, double scale, int ddepth = -1); +GAPI_EXPORTS_W GMat div(const GMat& src1, const GMat& src2, double scale, int ddepth = -1); /** @brief Divides matrix by scalar. @@ -785,7 +785,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param scale scale factor. @sa add, sub, div, addWeighted */ -GAPI_EXPORTS GMat divC(const GMat& src, const GScalar& divisor, double scale, int ddepth = -1); +GAPI_EXPORTS_W GMat divC(const GMat& src, const GScalar& divisor, double scale, int ddepth = -1); /** @brief Divides scalar by matrix. @@ -806,7 +806,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param scale scale factor @sa add, sub, div, addWeighted */ -GAPI_EXPORTS GMat divRC(const GScalar& divident, const GMat& src, double scale, int ddepth = -1); +GAPI_EXPORTS_W GMat divRC(const GScalar& divident, const GMat& src, double scale, int ddepth = -1); /** @brief Applies a mask to a matrix. @@ -819,7 +819,7 @@ Supported src matrix data types are @ref CV_8UC1, @ref CV_16SC1, @ref CV_16UC1. @param src input matrix. @param mask input mask matrix. */ -GAPI_EXPORTS GMat mask(const GMat& src, const GMat& mask); +GAPI_EXPORTS_W GMat mask(const GMat& src, const GMat& mask); /** @brief Calculates an average (mean) of matrix elements. @@ -854,8 +854,8 @@ Both output must have the same size and depth as input matrices. degrees, otherwise, they are measured in radians. @sa cartToPolar, exp, log, pow, sqrt */ -GAPI_EXPORTS std::tuple polarToCart(const GMat& magnitude, const GMat& angle, - bool angleInDegrees = false); +GAPI_EXPORTS_W std::tuple polarToCart(const GMat& magnitude, const GMat& angle, + bool angleInDegrees = false); /** @brief Calculates the magnitude and angle of 2D vectors. @@ -878,8 +878,8 @@ x; the angles are measured in radians (from 0 to 2\*Pi) or in degrees (0 to 360 in radians (which is by default), or in degrees. @sa polarToCart */ -GAPI_EXPORTS std::tuple cartToPolar(const GMat& x, const GMat& y, - bool angleInDegrees = false); +GAPI_EXPORTS_W std::tuple cartToPolar(const GMat& x, const GMat& y, + bool angleInDegrees = false); /** @brief Calculates the rotation angle of 2D vectors. @@ -896,7 +896,7 @@ same size and the same type as x. degrees, otherwise, they are measured in radians. @return array of vector angles; it has the same size and same type as x. */ -GAPI_EXPORTS GMat phase(const GMat& x, const GMat &y, bool angleInDegrees = false); +GAPI_EXPORTS_W GMat phase(const GMat& x, const GMat &y, bool angleInDegrees = false); /** @brief Calculates a square root of array elements. @@ -907,7 +907,7 @@ std::sqrt . @param src input floating-point array. @return output array of the same size and type as src. */ -GAPI_EXPORTS GMat sqrt(const GMat &src); +GAPI_EXPORTS_W GMat sqrt(const GMat &src); //! @} gapi_math //! @@ -934,11 +934,11 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_16UC1, @ref CV_16SC1 @param src2 second input matrix/scalar of the same depth as first input matrix. @sa min, max, threshold, cmpLE, cmpGE, cmpLT */ -GAPI_EXPORTS GMat cmpGT(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat cmpGT(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.compare.cmpGTScalar" */ -GAPI_EXPORTS GMat cmpGT(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat cmpGT(const GMat& src1, const GScalar& src2); /** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are less than elements in second. @@ -960,11 +960,11 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @param src2 second input matrix/scalar of the same depth as first input matrix. @sa min, max, threshold, cmpLE, cmpGE, cmpGT */ -GAPI_EXPORTS GMat cmpLT(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat cmpLT(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLTScalar" */ -GAPI_EXPORTS GMat cmpLT(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat cmpLT(const GMat& src1, const GScalar& src2); /** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are greater or equal compare to elements in second. @@ -986,11 +986,11 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @param src2 second input matrix/scalar of the same depth as first input matrix. @sa min, max, threshold, cmpLE, cmpGT, cmpLT */ -GAPI_EXPORTS GMat cmpGE(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat cmpGE(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLGEcalar" */ -GAPI_EXPORTS GMat cmpGE(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat cmpGE(const GMat& src1, const GScalar& src2); /** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are less or equal compare to elements in second. @@ -1012,11 +1012,11 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @param src2 second input matrix/scalar of the same depth as first input matrix. @sa min, max, threshold, cmpGT, cmpGE, cmpLT */ -GAPI_EXPORTS GMat cmpLE(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat cmpLE(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.compare.cmpLEScalar" */ -GAPI_EXPORTS GMat cmpLE(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat cmpLE(const GMat& src1, const GScalar& src2); /** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are equal to elements in second. @@ -1038,11 +1038,11 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @param src2 second input matrix/scalar of the same depth as first input matrix. @sa min, max, threshold, cmpNE */ -GAPI_EXPORTS GMat cmpEQ(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat cmpEQ(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.compare.cmpEQScalar" */ -GAPI_EXPORTS GMat cmpEQ(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat cmpEQ(const GMat& src1, const GScalar& src2); /** @brief Performs the per-element comparison of two matrices checking if elements from first matrix are not equal to elements in second. @@ -1064,11 +1064,11 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @param src2 second input matrix/scalar of the same depth as first input matrix. @sa min, max, threshold, cmpEQ */ -GAPI_EXPORTS GMat cmpNE(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat cmpNE(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.compare.cmpNEScalar" */ -GAPI_EXPORTS GMat cmpNE(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat cmpNE(const GMat& src1, const GScalar& src2); /** @brief computes bitwise conjunction of the two matrixes (src1 & src2) Calculates the per-element bit-wise logical conjunction of two matrices of the same size. @@ -1086,13 +1086,13 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src1 first input matrix. @param src2 second input matrix. */ -GAPI_EXPORTS GMat bitwise_and(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat bitwise_and(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.bitwise_andS" @param src1 first input matrix. @param src2 scalar, which will be per-lemenetly conjuncted with elements of src1. */ -GAPI_EXPORTS GMat bitwise_and(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat bitwise_and(const GMat& src1, const GScalar& src2); /** @brief computes bitwise disjunction of the two matrixes (src1 | src2) Calculates the per-element bit-wise logical disjunction of two matrices of the same size. @@ -1110,13 +1110,13 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src1 first input matrix. @param src2 second input matrix. */ -GAPI_EXPORTS GMat bitwise_or(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat bitwise_or(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.bitwise_orS" @param src1 first input matrix. @param src2 scalar, which will be per-lemenetly disjuncted with elements of src1. */ -GAPI_EXPORTS GMat bitwise_or(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat bitwise_or(const GMat& src1, const GScalar& src2); /** @brief computes bitwise logical "exclusive or" of the two matrixes (src1 ^ src2) @@ -1135,13 +1135,13 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src1 first input matrix. @param src2 second input matrix. */ -GAPI_EXPORTS GMat bitwise_xor(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat bitwise_xor(const GMat& src1, const GMat& src2); /** @overload @note Function textual ID is "org.opencv.core.pixelwise.bitwise_xorS" @param src1 first input matrix. @param src2 scalar, for which per-lemenet "logical or" operation on elements of src1 will be performed. */ -GAPI_EXPORTS GMat bitwise_xor(const GMat& src1, const GScalar& src2); +GAPI_EXPORTS_W GMat bitwise_xor(const GMat& src1, const GScalar& src2); /** @brief Inverts every bit of an array. @@ -1162,7 +1162,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src input matrix. */ -GAPI_EXPORTS GMat bitwise_not(const GMat& src); +GAPI_EXPORTS_W GMat bitwise_not(const GMat& src); /** @brief Select values from either first or second of input matrices by given mask. The function set to the output matrix either the value from the first input matrix if corresponding value of mask matrix is 255, @@ -1178,7 +1178,7 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @param src2 second input matrix. @param mask mask input matrix. */ -GAPI_EXPORTS GMat select(const GMat& src1, const GMat& src2, const GMat& mask); +GAPI_EXPORTS_W GMat select(const GMat& src1, const GMat& src2, const GMat& mask); //! @} gapi_pixelwise @@ -1200,7 +1200,7 @@ Supported input matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @param src2 second input matrix of the same size and depth as src1. @sa max, cmpEQ, cmpLT, cmpLE */ -GAPI_EXPORTS GMat min(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat min(const GMat& src1, const GMat& src2); /** @brief Calculates per-element maximum of two matrices. @@ -1217,7 +1217,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src2 second input matrix of the same size and depth as src1. @sa min, compare, cmpEQ, cmpGT, cmpGE */ -GAPI_EXPORTS GMat max(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat max(const GMat& src1, const GMat& src2); /** @brief Calculates the per-element absolute difference between two matrices. @@ -1234,7 +1234,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src2 second input matrix. @sa abs */ -GAPI_EXPORTS GMat absDiff(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat absDiff(const GMat& src1, const GMat& src2); /** @brief Calculates absolute value of matrix elements. @@ -1251,7 +1251,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param c scalar to be subtracted. @sa min, max */ -GAPI_EXPORTS GMat absDiffC(const GMat& src, const GScalar& c); +GAPI_EXPORTS_W GMat absDiffC(const GMat& src, const GScalar& c); /** @brief Calculates sum of all matrix elements. @@ -1263,7 +1263,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src input matrix. @sa countNonZero, mean, min, max */ -GAPI_EXPORTS GScalar sum(const GMat& src); +GAPI_EXPORTS_W GScalar sum(const GMat& src); /** @brief Counts non-zero array elements. @@ -1276,7 +1276,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_16UC1, @ref CV_16SC1, @ref @param src input single-channel matrix. @sa mean, min, max */ -GAPI_EXPORTS GOpaque countNonZero(const GMat& src); +GAPI_EXPORTS_W GOpaque countNonZero(const GMat& src); /** @brief Calculates the weighted sum of two matrices. @@ -1299,7 +1299,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param ddepth optional depth of the output matrix. @sa add, sub */ -GAPI_EXPORTS GMat addWeighted(const GMat& src1, double alpha, const GMat& src2, double beta, double gamma, int ddepth = -1); +GAPI_EXPORTS_W GMat addWeighted(const GMat& src1, double alpha, const GMat& src2, double beta, double gamma, int ddepth = -1); /** @brief Calculates the absolute L1 norm of a matrix. @@ -1322,7 +1322,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src input matrix. @sa normL2, normInf */ -GAPI_EXPORTS GScalar normL1(const GMat& src); +GAPI_EXPORTS_W GScalar normL1(const GMat& src); /** @brief Calculates the absolute L2 norm of a matrix. @@ -1344,7 +1344,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src input matrix. @sa normL1, normInf */ -GAPI_EXPORTS GScalar normL2(const GMat& src); +GAPI_EXPORTS_W GScalar normL2(const GMat& src); /** @brief Calculates the absolute infinite norm of a matrix. @@ -1367,7 +1367,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src input matrix. @sa normL1, normL2 */ -GAPI_EXPORTS GScalar normInf(const GMat& src); +GAPI_EXPORTS_W GScalar normInf(const GMat& src); /** @brief Calculates the integral of an image. @@ -1387,7 +1387,7 @@ The function return integral image as \f$(W+1)\times (H+1)\f$ , 32-bit integer o CV_64F. @param sqdepth desired depth of the integral image of squared pixel values, CV_32F or CV_64F. */ -GAPI_EXPORTS std::tuple integral(const GMat& src, int sdepth = -1, int sqdepth = -1); +GAPI_EXPORTS_W std::tuple integral(const GMat& src, int sdepth = -1, int sqdepth = -1); /** @brief Applies a fixed-level threshold to each matrix element. @@ -1416,7 +1416,7 @@ types. @sa min, max, cmpGT, cmpLE, cmpGE, cmpLT */ -GAPI_EXPORTS GMat threshold(const GMat& src, const GScalar& thresh, const GScalar& maxval, int type); +GAPI_EXPORTS_W GMat threshold(const GMat& src, const GScalar& thresh, const GScalar& maxval, int type); /** @overload This function applicable for all threshold types except cv::THRESH_OTSU and cv::THRESH_TRIANGLE @note Function textual ID is "org.opencv.core.matrixop.thresholdOT" @@ -1438,7 +1438,7 @@ Input and output matrices must be CV_8UC1. @sa threshold */ -GAPI_EXPORTS GMat inRange(const GMat& src, const GScalar& threshLow, const GScalar& threshUp); +GAPI_EXPORTS_W GMat inRange(const GMat& src, const GScalar& threshLow, const GScalar& threshUp); //! @} gapi_matrixop @@ -1462,7 +1462,7 @@ The function split4 does the reverse operation. @param src4 fourth input @ref CV_8UC1 matrix to be merged. @sa merge3, split4, split3 */ -GAPI_EXPORTS GMat merge4(const GMat& src1, const GMat& src2, const GMat& src3, const GMat& src4); +GAPI_EXPORTS_W GMat merge4(const GMat& src1, const GMat& src2, const GMat& src3, const GMat& src4); /** @brief Creates one 3-channel matrix out of 3 single-channel ones. @@ -1481,7 +1481,7 @@ The function split3 does the reverse operation. @param src3 third input @ref CV_8UC1 matrix to be merged. @sa merge4, split4, split3 */ -GAPI_EXPORTS GMat merge3(const GMat& src1, const GMat& src2, const GMat& src3); +GAPI_EXPORTS_W GMat merge3(const GMat& src1, const GMat& src2, const GMat& src3); /** @brief Divides a 4-channel matrix into 4 single-channel matrices. @@ -1498,7 +1498,7 @@ The function merge4 does the reverse operation. @param src input @ref CV_8UC4 matrix. @sa split3, merge3, merge4 */ -GAPI_EXPORTS std::tuple split4(const GMat& src); +GAPI_EXPORTS_W std::tuple split4(const GMat& src); /** @brief Divides a 3-channel matrix into 3 single-channel matrices. @@ -1548,9 +1548,9 @@ borderMode=BORDER_TRANSPARENT, it means that the pixels in the destination image corresponds to the "outliers" in the source image are not modified by the function. @param borderValue Value used in case of a constant border. By default, it is 0. */ -GAPI_EXPORTS GMat remap(const GMat& src, const Mat& map1, const Mat& map2, - int interpolation, int borderMode = BORDER_CONSTANT, - const Scalar& borderValue = Scalar()); +GAPI_EXPORTS_W GMat remap(const GMat& src, const Mat& map1, const Mat& map2, + int interpolation, int borderMode = BORDER_CONSTANT, + const Scalar& borderValue = Scalar()); /** @brief Flips a 2D matrix around vertical, horizontal, or both axes. @@ -1587,7 +1587,7 @@ flipping around y-axis. Negative value (for example, -1) means flipping around both axes. @sa remap */ -GAPI_EXPORTS GMat flip(const GMat& src, int flipCode); +GAPI_EXPORTS_W GMat flip(const GMat& src, int flipCode); /** @brief Crops a 2D matrix. @@ -1601,7 +1601,7 @@ Output matrix must be of the same depth as input one, size is specified by given @param rect a rect to crop a matrix to @sa resize */ -GAPI_EXPORTS GMat crop(const GMat& src, const Rect& rect); +GAPI_EXPORTS_W GMat crop(const GMat& src, const Rect& rect); /** @brief Applies horizontal concatenation to given matrices. @@ -1629,7 +1629,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src2 second input matrix to be considered for horizontal concatenation. @sa concatVert */ -GAPI_EXPORTS GMat concatHor(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat concatHor(const GMat& src1, const GMat& src2); /** @overload The function horizontally concatenates given number of GMat matrices (with the same number of columns). @@ -1637,7 +1637,7 @@ Output matrix must the same number of columns and depth as the input matrices, a @param v vector of input matrices to be concatenated horizontally. */ -GAPI_EXPORTS GMat concatHor(const std::vector &v); +GAPI_EXPORTS_W GMat concatHor(const std::vector &v); /** @brief Applies vertical concatenation to given matrices. @@ -1669,7 +1669,7 @@ Supported matrix data types are @ref CV_8UC1, @ref CV_8UC3, @ref CV_16UC1, @ref @param src2 second input matrix to be considered for vertical concatenation. @sa concatHor */ -GAPI_EXPORTS GMat concatVert(const GMat& src1, const GMat& src2); +GAPI_EXPORTS_W GMat concatVert(const GMat& src1, const GMat& src2); /** @overload The function vertically concatenates given number of GMat matrices (with the same number of columns). @@ -1677,7 +1677,7 @@ Output matrix must the same number of columns and depth as the input matrices, a @param v vector of input matrices to be concatenated vertically. */ -GAPI_EXPORTS GMat concatVert(const std::vector &v); +GAPI_EXPORTS_W GMat concatVert(const std::vector &v); /** @brief Performs a look-up table transform of a matrix. @@ -1696,7 +1696,7 @@ Output is a matrix of the same size and number of channels as src, and the same either have a single channel (in this case the same table is used for all channels) or the same number of channels as in the input matrix. */ -GAPI_EXPORTS GMat LUT(const GMat& src, const Mat& lut); +GAPI_EXPORTS_W GMat LUT(const GMat& src, const Mat& lut); /** @brief Converts a matrix to another data depth with optional scaling. @@ -1713,7 +1713,7 @@ same as the input has; if rdepth is negative, the output matrix will have the sa @param alpha optional scale factor. @param beta optional delta added to the scaled values. */ -GAPI_EXPORTS GMat convertTo(const GMat& src, int rdepth, double alpha=1, double beta=0); +GAPI_EXPORTS_W GMat convertTo(const GMat& src, int rdepth, double alpha=1, double beta=0); /** @brief Normalizes the norm or value range of an array. @@ -1735,8 +1735,8 @@ normalization. number of channels as src and the depth =ddepth. @sa norm, Mat::convertTo */ -GAPI_EXPORTS GMat normalize(const GMat& src, double alpha, double beta, - int norm_type, int ddepth = -1); +GAPI_EXPORTS_W GMat normalize(const GMat& src, double alpha, double beta, + int norm_type, int ddepth = -1); /** @brief Applies a perspective transformation to an image. @@ -1759,8 +1759,8 @@ optional flag #WARP_INVERSE_MAP, that sets M as the inverse transformation ( @sa warpAffine, resize, remap, getRectSubPix, perspectiveTransform */ -GAPI_EXPORTS GMat warpPerspective(const GMat& src, const Mat& M, const Size& dsize, int flags = cv::INTER_LINEAR, - int borderMode = cv::BORDER_CONSTANT, const Scalar& borderValue = Scalar()); +GAPI_EXPORTS_W GMat warpPerspective(const GMat& src, const Mat& M, const Size& dsize, int flags = cv::INTER_LINEAR, + int borderMode = cv::BORDER_CONSTANT, const Scalar& borderValue = Scalar()); /** @brief Applies an affine transformation to an image. @@ -1784,8 +1784,8 @@ borderMode=#BORDER_TRANSPARENT isn't supported @sa warpPerspective, resize, remap, getRectSubPix, transform */ -GAPI_EXPORTS GMat warpAffine(const GMat& src, const Mat& M, const Size& dsize, int flags = cv::INTER_LINEAR, - int borderMode = cv::BORDER_CONSTANT, const Scalar& borderValue = Scalar()); +GAPI_EXPORTS_W GMat warpAffine(const GMat& src, const Mat& M, const Size& dsize, int flags = cv::INTER_LINEAR, + int borderMode = cv::BORDER_CONSTANT, const Scalar& borderValue = Scalar()); //! @} gapi_transform /** @brief Finds centers of clusters and groups input samples around the clusters. @@ -1834,7 +1834,7 @@ compactness value are returned by the function. - Integer array that stores the cluster indices for every sample. - Array of the cluster centers. */ -GAPI_EXPORTS std::tuple,GMat,GMat> +GAPI_EXPORTS_W std::tuple,GMat,GMat> kmeans(const GMat& data, const int K, const GMat& bestLabels, const TermCriteria& criteria, const int attempts, const KmeansFlags flags); @@ -1857,7 +1857,7 @@ kmeans(const GArray& data, const int K, const GArray& bestLabels, /** @overload @note Function textual ID is "org.opencv.core.kmeans3D" */ -GAPI_EXPORTS std::tuple,GArray,GArray> +GAPI_EXPORTS_W std::tuple,GArray,GArray> kmeans(const GArray& data, const int K, const GArray& bestLabels, const TermCriteria& criteria, const int attempts, const KmeansFlags flags); @@ -1873,7 +1873,7 @@ The function transposes the matrix: @param src input array. */ -GAPI_EXPORTS GMat transpose(const GMat& src); +GAPI_EXPORTS_W GMat transpose(const GMat& src); namespace streaming { @@ -1903,7 +1903,7 @@ GAPI_EXPORTS_W GOpaque size(const GOpaque& r); @param src Input frame @return Size (frame dimensions). */ -GAPI_EXPORTS GOpaque size(const GFrame& src); +GAPI_EXPORTS_W GOpaque size(const GFrame& src); } //namespace streaming } //namespace gapi } //namespace cv diff --git a/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp b/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp index ff3ee45ed3..eb5f784747 100644 --- a/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp +++ b/modules/gapi/include/opencv2/gapi/cpu/gcpukernel.hpp @@ -8,9 +8,9 @@ #ifndef OPENCV_GAPI_GCPUKERNEL_HPP #define OPENCV_GAPI_GCPUKERNEL_HPP -#ifdef _MSC_VER -#pragma warning(disable: 4702) // "Unreachable code" -// on postprocess(...) call inside OCVCallHelper +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4702) // "Unreachable code" on postprocess(...) call inside OCVCallHelper #endif #include @@ -535,4 +535,8 @@ gapi::cpu::GOCVFunctor gapi::cpu::ocv_kernel(const Callable& c) } // namespace cv +#if defined _MSC_VER +#pragma warning(pop) +#endif + #endif // OPENCV_GAPI_GCPUKERNEL_HPP diff --git a/modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp b/modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp index 92f1ccc87f..c3ae9dfdd6 100644 --- a/modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp +++ b/modules/gapi/include/opencv2/gapi/fluid/gfluidkernel.hpp @@ -248,11 +248,11 @@ struct scratch_helper const cv::GArgs &, gapi::fluid::Buffer &) { - GAPI_Assert(false); + GAPI_Error("InternalError"); } static void help_reset(gapi::fluid::Buffer &) { - GAPI_Assert(false); + GAPI_Error("InternalError"); } }; diff --git a/modules/gapi/include/opencv2/gapi/garray.hpp b/modules/gapi/include/opencv2/gapi/garray.hpp index 55f4d11b12..b6aa715518 100644 --- a/modules/gapi/include/opencv2/gapi/garray.hpp +++ b/modules/gapi/include/opencv2/gapi/garray.hpp @@ -177,7 +177,7 @@ namespace detail { util::get(m_ref).clear(); } - else GAPI_Assert(false); // shouldn't be called in *EXT modes + else GAPI_Error("InternalError"); // shouldn't be called in *EXT modes } // Obtain a WRITE reference to underlying object diff --git a/modules/gapi/include/opencv2/gapi/gcommon.hpp b/modules/gapi/include/opencv2/gapi/gcommon.hpp index dfbd9e3a2a..b08baaa365 100644 --- a/modules/gapi/include/opencv2/gapi/gcommon.hpp +++ b/modules/gapi/include/opencv2/gapi/gcommon.hpp @@ -51,6 +51,7 @@ namespace detail CV_STRING, // std::string user G-API data CV_POINT, // cv::Point user G-API data CV_POINT2F, // cv::Point2f user G-API data + CV_POINT3F, // cv::Point3f user G-API data CV_SIZE, // cv::Size user G-API data CV_RECT, // cv::Rect user G-API data CV_SCALAR, // cv::Scalar user G-API data @@ -72,16 +73,17 @@ namespace detail template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_SCALAR; }; template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_POINT; }; template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_POINT2F; }; + template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_POINT3F; }; template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_MAT; }; template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_RECT; }; template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_MAT; }; template<> struct GOpaqueTraits { static constexpr const OpaqueKind kind = OpaqueKind::CV_DRAW_PRIM; }; using GOpaqueTraitsArrayTypes = std::tuple; + cv::Point3f, cv::Mat, cv::Rect, cv::gapi::wip::draw::Prim>; // GOpaque is not supporting cv::Mat and cv::Scalar since there are GScalar and GMat types - using GOpaqueTraitsOpaqueTypes = std::tuple; + using GOpaqueTraitsOpaqueTypes = std::tuple; } // namespace detail // This definition is here because it is reused by both public(?) and internal diff --git a/modules/gapi/include/opencv2/gapi/gopaque.hpp b/modules/gapi/include/opencv2/gapi/gopaque.hpp index f77795c506..1d12f127da 100644 --- a/modules/gapi/include/opencv2/gapi/gopaque.hpp +++ b/modules/gapi/include/opencv2/gapi/gopaque.hpp @@ -171,7 +171,7 @@ namespace detail { util::get(m_ref) = {}; } - else GAPI_Assert(false); // shouldn't be called in *EXT modes + else GAPI_Error("InternalError"); // shouldn't be called in *EXT modes } // Obtain a WRITE reference to underlying object diff --git a/modules/gapi/include/opencv2/gapi/gscalar.hpp b/modules/gapi/include/opencv2/gapi/gscalar.hpp index d8a47c8ea8..43d12c782a 100644 --- a/modules/gapi/include/opencv2/gapi/gscalar.hpp +++ b/modules/gapi/include/opencv2/gapi/gscalar.hpp @@ -67,6 +67,7 @@ public: * * @param s a cv::Scalar value to associate with this GScalar object. */ + GAPI_WRAP explicit GScalar(const cv::Scalar& s); /** diff --git a/modules/gapi/include/opencv2/gapi/imgproc.hpp b/modules/gapi/include/opencv2/gapi/imgproc.hpp index 44f0528153..96aaa5a447 100644 --- a/modules/gapi/include/opencv2/gapi/imgproc.hpp +++ b/modules/gapi/include/opencv2/gapi/imgproc.hpp @@ -556,9 +556,9 @@ is at the kernel center. @param borderValue border value in case of constant border type @sa boxFilter, gaussianBlur, medianBlur */ -GAPI_EXPORTS GMat sepFilter(const GMat& src, int ddepth, const Mat& kernelX, const Mat& kernelY, const Point& anchor /*FIXME: = Point(-1,-1)*/, - const Scalar& delta /*FIXME = GScalar(0)*/, int borderType = BORDER_DEFAULT, - const Scalar& borderValue = Scalar(0)); +GAPI_EXPORTS_W GMat sepFilter(const GMat& src, int ddepth, const Mat& kernelX, const Mat& kernelY, const Point& anchor /*FIXME: = Point(-1,-1)*/, + const Scalar& delta /*FIXME = GScalar(0)*/, int borderType = BORDER_DEFAULT, + const Scalar& borderValue = Scalar(0)); /** @brief Convolves an image with the kernel. @@ -593,8 +593,8 @@ is at the kernel center. @param borderValue border value in case of constant border type @sa sepFilter */ -GAPI_EXPORTS GMat filter2D(const GMat& src, int ddepth, const Mat& kernel, const Point& anchor = Point(-1,-1), const Scalar& delta = Scalar(0), - int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); +GAPI_EXPORTS_W GMat filter2D(const GMat& src, int ddepth, const Mat& kernel, const Point& anchor = Point(-1,-1), const Scalar& delta = Scalar(0), + int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); /** @brief Blurs an image using the box filter. @@ -627,9 +627,9 @@ is at the kernel center. @param borderValue border value in case of constant border type @sa sepFilter, gaussianBlur, medianBlur, integral */ -GAPI_EXPORTS GMat boxFilter(const GMat& src, int dtype, const Size& ksize, const Point& anchor = Point(-1,-1), - bool normalize = true, int borderType = BORDER_DEFAULT, - const Scalar& borderValue = Scalar(0)); +GAPI_EXPORTS_W GMat boxFilter(const GMat& src, int dtype, const Size& ksize, const Point& anchor = Point(-1,-1), + bool normalize = true, int borderType = BORDER_DEFAULT, + const Scalar& borderValue = Scalar(0)); /** @brief Blurs an image using the normalized box filter. @@ -654,8 +654,8 @@ center. @param borderValue border value in case of constant border type @sa boxFilter, bilateralFilter, GaussianBlur, medianBlur */ -GAPI_EXPORTS GMat blur(const GMat& src, const Size& ksize, const Point& anchor = Point(-1,-1), - int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); +GAPI_EXPORTS_W GMat blur(const GMat& src, const Size& ksize, const Point& anchor = Point(-1,-1), + int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); //GAPI_EXPORTS_W void blur( InputArray src, OutputArray dst, @@ -687,8 +687,8 @@ sigmaX, and sigmaY. @param borderValue border value in case of constant border type @sa sepFilter, boxFilter, medianBlur */ -GAPI_EXPORTS GMat gaussianBlur(const GMat& src, const Size& ksize, double sigmaX, double sigmaY = 0, - int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); +GAPI_EXPORTS_W GMat gaussianBlur(const GMat& src, const Size& ksize, double sigmaX, double sigmaY = 0, + int borderType = BORDER_DEFAULT, const Scalar& borderValue = Scalar(0)); /** @brief Blurs an image using the median filter. @@ -730,9 +730,9 @@ anchor is at the element center. @param borderValue border value in case of a constant border @sa dilate, morphologyEx */ -GAPI_EXPORTS GMat erode(const GMat& src, const Mat& kernel, const Point& anchor = Point(-1,-1), int iterations = 1, - int borderType = BORDER_CONSTANT, - const Scalar& borderValue = morphologyDefaultBorderValue()); +GAPI_EXPORTS_W GMat erode(const GMat& src, const Mat& kernel, const Point& anchor = Point(-1,-1), int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); /** @brief Erodes an image by using 3 by 3 rectangular structuring element. @@ -750,9 +750,9 @@ Output image must have the same type, size, and number of channels as the input @param borderValue border value in case of a constant border @sa erode, dilate3x3 */ -GAPI_EXPORTS GMat erode3x3(const GMat& src, int iterations = 1, - int borderType = BORDER_CONSTANT, - const Scalar& borderValue = morphologyDefaultBorderValue()); +GAPI_EXPORTS_W GMat erode3x3(const GMat& src, int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); /** @brief Dilates an image by using a specific structuring element. @@ -777,9 +777,9 @@ anchor is at the element center. @param borderValue border value in case of a constant border @sa erode, morphologyEx, getStructuringElement */ -GAPI_EXPORTS GMat dilate(const GMat& src, const Mat& kernel, const Point& anchor = Point(-1,-1), int iterations = 1, - int borderType = BORDER_CONSTANT, - const Scalar& borderValue = morphologyDefaultBorderValue()); +GAPI_EXPORTS_W GMat dilate(const GMat& src, const Mat& kernel, const Point& anchor = Point(-1,-1), int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); /** @brief Dilates an image by using 3 by 3 rectangular structuring element. @@ -801,9 +801,9 @@ Output image must have the same type, size, and number of channels as the input @sa dilate, erode3x3 */ -GAPI_EXPORTS GMat dilate3x3(const GMat& src, int iterations = 1, - int borderType = BORDER_CONSTANT, - const Scalar& borderValue = morphologyDefaultBorderValue()); +GAPI_EXPORTS_W GMat dilate3x3(const GMat& src, int iterations = 1, + int borderType = BORDER_CONSTANT, + const Scalar& borderValue = morphologyDefaultBorderValue()); /** @brief Performs advanced morphological transformations. @@ -831,11 +831,11 @@ the kernel center. meaning. @sa dilate, erode, getStructuringElement */ -GAPI_EXPORTS GMat morphologyEx(const GMat &src, const MorphTypes op, const Mat &kernel, - const Point &anchor = Point(-1,-1), - const int iterations = 1, - const BorderTypes borderType = BORDER_CONSTANT, - const Scalar &borderValue = morphologyDefaultBorderValue()); +GAPI_EXPORTS_W GMat morphologyEx(const GMat &src, const MorphTypes op, const Mat &kernel, + const Point &anchor = Point(-1,-1), + const int iterations = 1, + const BorderTypes borderType = BORDER_CONSTANT, + const Scalar &borderValue = morphologyDefaultBorderValue()); /** @brief Calculates the first, second, third, or mixed image derivatives using an extended Sobel operator. @@ -883,10 +883,10 @@ applied (see cv::getDerivKernels for details). @param borderValue border value in case of constant border type @sa filter2D, gaussianBlur, cartToPolar */ -GAPI_EXPORTS GMat Sobel(const GMat& src, int ddepth, int dx, int dy, int ksize = 3, - double scale = 1, double delta = 0, - int borderType = BORDER_DEFAULT, - const Scalar& borderValue = Scalar(0)); +GAPI_EXPORTS_W GMat Sobel(const GMat& src, int ddepth, int dx, int dy, int ksize = 3, + double scale = 1, double delta = 0, + int borderType = BORDER_DEFAULT, + const Scalar& borderValue = Scalar(0)); /** @brief Calculates the first, second, third, or mixed image derivatives using an extended Sobel operator. @@ -934,10 +934,10 @@ applied (see cv::getDerivKernels for details). @param borderValue border value in case of constant border type @sa filter2D, gaussianBlur, cartToPolar */ -GAPI_EXPORTS std::tuple SobelXY(const GMat& src, int ddepth, int order, int ksize = 3, - double scale = 1, double delta = 0, - int borderType = BORDER_DEFAULT, - const Scalar& borderValue = Scalar(0)); +GAPI_EXPORTS_W std::tuple SobelXY(const GMat& src, int ddepth, int order, int ksize = 3, + double scale = 1, double delta = 0, + int borderType = BORDER_DEFAULT, + const Scalar& borderValue = Scalar(0)); /** @brief Calculates the Laplacian of an image. @@ -964,8 +964,8 @@ applied. See #getDerivKernels for details. @return Destination image of the same size and the same number of channels as src. @sa Sobel, Scharr */ -GAPI_EXPORTS GMat Laplacian(const GMat& src, int ddepth, int ksize = 1, - double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT); +GAPI_EXPORTS_W GMat Laplacian(const GMat& src, int ddepth, int ksize = 1, + double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT); /** @brief Applies the bilateral filter to an image. @@ -998,8 +998,8 @@ proportional to sigmaSpace. @param borderType border mode used to extrapolate pixels outside of the image, see #BorderTypes @return Destination image of the same size and type as src. */ -GAPI_EXPORTS GMat bilateralFilter(const GMat& src, int d, double sigmaColor, double sigmaSpace, - int borderType = BORDER_DEFAULT); +GAPI_EXPORTS_W GMat bilateralFilter(const GMat& src, int d, double sigmaColor, double sigmaSpace, + int borderType = BORDER_DEFAULT); //! @} gapi_filters @@ -1023,8 +1023,8 @@ largest value is used to find initial segments of strong edges. See L2gradient=true ), or whether the default \f$L_1\f$ norm \f$=|dI/dx|+|dI/dy|\f$ is enough ( L2gradient=false ). */ -GAPI_EXPORTS GMat Canny(const GMat& image, double threshold1, double threshold2, - int apertureSize = 3, bool L2gradient = false); +GAPI_EXPORTS_W GMat Canny(const GMat& image, double threshold1, double threshold2, + int apertureSize = 3, bool L2gradient = false); /** @brief Determines strong corners on an image. @@ -1070,14 +1070,14 @@ or #cornerMinEigenVal. @return vector of detected corners. */ -GAPI_EXPORTS_W GArray goodFeaturesToTrack(const GMat &image, - int maxCorners, - double qualityLevel, - double minDistance, - const Mat &mask = Mat(), - int blockSize = 3, - bool useHarrisDetector = false, - double k = 0.04); +GAPI_EXPORTS_W GArray goodFeaturesToTrack(const GMat &image, + int maxCorners, + double qualityLevel, + double minDistance, + const Mat &mask = Mat(), + int blockSize = 3, + bool useHarrisDetector = false, + double k = 0.04); /** @brief Equalizes the histogram of a grayscale image. @@ -1098,7 +1098,7 @@ The algorithm normalizes the brightness and increases the contrast of the image. @param src Source 8-bit single channel image. */ -GAPI_EXPORTS GMat equalizeHist(const GMat& src); +GAPI_EXPORTS_W GMat equalizeHist(const GMat& src); //! @addtogroup gapi_shape //! @{ @@ -1209,7 +1209,7 @@ Calculates the up-right bounding rectangle of a point set. @param src Input 2D point set, stored in std::vector. */ -GAPI_EXPORTS GOpaque boundingRect(const GArray& src); +GAPI_EXPORTS_W GOpaque boundingRect(const GArray& src); /** @brief Fits a line to a 2D point set. @@ -1399,7 +1399,7 @@ Resulting gray color value computed as @param bY float multiplier for B channel. @sa RGB2YUV */ -GAPI_EXPORTS GMat RGB2Gray(const GMat& src, float rY, float gY, float bY); +GAPI_EXPORTS_W GMat RGB2Gray(const GMat& src, float rY, float gY, float bY); /** @brief Converts an image from BGR color space to gray-scaled. @@ -1412,7 +1412,7 @@ Resulting gray color value computed as @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC1. @sa BGR2LUV */ -GAPI_EXPORTS GMat BGR2Gray(const GMat& src); +GAPI_EXPORTS_W GMat BGR2Gray(const GMat& src); /** @brief Converts an image from RGB color space to YUV color space. @@ -1429,7 +1429,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. @sa YUV2RGB, RGB2Lab */ -GAPI_EXPORTS GMat RGB2YUV(const GMat& src); +GAPI_EXPORTS_W GMat RGB2YUV(const GMat& src); /** @brief Converts an image from BGR color space to I420 color space. @@ -1445,7 +1445,7 @@ Height of I420 output image must be equal 3/2 from height of input image. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. @sa I4202BGR */ -GAPI_EXPORTS GMat BGR2I420(const GMat& src); +GAPI_EXPORTS_W GMat BGR2I420(const GMat& src); /** @brief Converts an image from RGB color space to I420 color space. @@ -1461,7 +1461,7 @@ Height of I420 output image must be equal 3/2 from height of input image. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. @sa I4202RGB */ -GAPI_EXPORTS GMat RGB2I420(const GMat& src); +GAPI_EXPORTS_W GMat RGB2I420(const GMat& src); /** @brief Converts an image from I420 color space to BGR color space. @@ -1477,7 +1477,7 @@ Height of BGR output image must be equal 2/3 from height of input image. @param src input image: 8-bit unsigned 1-channel image @ref CV_8UC1. @sa BGR2I420 */ -GAPI_EXPORTS GMat I4202BGR(const GMat& src); +GAPI_EXPORTS_W GMat I4202BGR(const GMat& src); /** @brief Converts an image from I420 color space to BGR color space. @@ -1493,7 +1493,7 @@ Height of RGB output image must be equal 2/3 from height of input image. @param src input image: 8-bit unsigned 1-channel image @ref CV_8UC1. @sa RGB2I420 */ -GAPI_EXPORTS GMat I4202RGB(const GMat& src); +GAPI_EXPORTS_W GMat I4202RGB(const GMat& src); /** @brief Converts an image from BGR color space to LUV color space. @@ -1507,7 +1507,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. @sa RGB2Lab, RGB2LUV */ -GAPI_EXPORTS GMat BGR2LUV(const GMat& src); +GAPI_EXPORTS_W GMat BGR2LUV(const GMat& src); /** @brief Converts an image from LUV color space to BGR color space. @@ -1521,7 +1521,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. @sa BGR2LUV */ -GAPI_EXPORTS GMat LUV2BGR(const GMat& src); +GAPI_EXPORTS_W GMat LUV2BGR(const GMat& src); /** @brief Converts an image from YUV color space to BGR color space. @@ -1535,7 +1535,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. @sa BGR2YUV */ -GAPI_EXPORTS GMat YUV2BGR(const GMat& src); +GAPI_EXPORTS_W GMat YUV2BGR(const GMat& src); /** @brief Converts an image from BGR color space to YUV color space. @@ -1549,7 +1549,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC3. @sa YUV2BGR */ -GAPI_EXPORTS GMat BGR2YUV(const GMat& src); +GAPI_EXPORTS_W GMat BGR2YUV(const GMat& src); /** @brief Converts an image from RGB color space to Lab color space. @@ -1563,7 +1563,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC1. @param src input image: 8-bit unsigned 3-channel image @ref CV_8UC1. @sa RGB2YUV, RGB2LUV */ -GAPI_EXPORTS GMat RGB2Lab(const GMat& src); +GAPI_EXPORTS_W GMat RGB2Lab(const GMat& src); /** @brief Converts an image from YUV color space to RGB. The function converts an input image from YUV color space to RGB. @@ -1577,7 +1577,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @sa RGB2Lab, RGB2YUV */ -GAPI_EXPORTS GMat YUV2RGB(const GMat& src); +GAPI_EXPORTS_W GMat YUV2RGB(const GMat& src); /** @brief Converts an image from NV12 (YUV420p) color space to RGB. The function converts an input image from NV12 color space to RGB. @@ -1592,7 +1592,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @sa YUV2RGB, NV12toBGR */ -GAPI_EXPORTS GMat NV12toRGB(const GMat& src_y, const GMat& src_uv); +GAPI_EXPORTS_W GMat NV12toRGB(const GMat& src_y, const GMat& src_uv); /** @brief Converts an image from NV12 (YUV420p) color space to gray-scaled. The function converts an input image from NV12 color space to gray-scaled. @@ -1607,7 +1607,7 @@ Output image must be 8-bit unsigned 1-channel image @ref CV_8UC1. @sa YUV2RGB, NV12toBGR */ -GAPI_EXPORTS GMat NV12toGray(const GMat& src_y, const GMat& src_uv); +GAPI_EXPORTS_W GMat NV12toGray(const GMat& src_y, const GMat& src_uv); /** @brief Converts an image from NV12 (YUV420p) color space to BGR. The function converts an input image from NV12 color space to RGB. @@ -1622,7 +1622,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @sa YUV2BGR, NV12toRGB */ -GAPI_EXPORTS GMat NV12toBGR(const GMat& src_y, const GMat& src_uv); +GAPI_EXPORTS_W GMat NV12toBGR(const GMat& src_y, const GMat& src_uv); /** @brief Converts an image from BayerGR color space to RGB. The function converts an input image from BayerGR color space to RGB. @@ -1636,7 +1636,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @sa YUV2BGR, NV12toRGB */ -GAPI_EXPORTS GMat BayerGR2RGB(const GMat& src_gr); +GAPI_EXPORTS_W GMat BayerGR2RGB(const GMat& src_gr); /** @brief Converts an image from RGB color space to HSV. The function converts an input image from RGB color space to HSV. @@ -1650,7 +1650,7 @@ Output image must be 8-bit unsigned 3-channel image @ref CV_8UC3. @sa YUV2BGR, NV12toRGB */ -GAPI_EXPORTS GMat RGB2HSV(const GMat& src); +GAPI_EXPORTS_W GMat RGB2HSV(const GMat& src); /** @brief Converts an image from RGB color space to YUV422. The function converts an input image from RGB color space to YUV422. @@ -1664,7 +1664,7 @@ Output image must be 8-bit unsigned 2-channel image @ref CV_8UC2. @sa YUV2BGR, NV12toRGB */ -GAPI_EXPORTS GMat RGB2YUV422(const GMat& src); +GAPI_EXPORTS_W GMat RGB2YUV422(const GMat& src); /** @brief Converts an image from NV12 (YUV420p) color space to RGB. The function converts an input image from NV12 color space to RGB. diff --git a/modules/gapi/include/opencv2/gapi/infer.hpp b/modules/gapi/include/opencv2/gapi/infer.hpp index 807c82d31f..c1f9501873 100644 --- a/modules/gapi/include/opencv2/gapi/infer.hpp +++ b/modules/gapi/include/opencv2/gapi/infer.hpp @@ -397,7 +397,7 @@ void inline unpackBlobs(const cv::GInferInputs::Map& blobs, kinds.emplace_back(cv::detail::OpaqueKind::CV_UNKNOWN); break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); } } } @@ -629,7 +629,7 @@ infer2(const std::string& tag, kinds.emplace_back(cv::detail::OpaqueKind::CV_RECT); break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); } } diff --git a/modules/gapi/include/opencv2/gapi/infer/bindings_onnx.hpp b/modules/gapi/include/opencv2/gapi/infer/bindings_onnx.hpp new file mode 100644 index 0000000000..d539f6007d --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/infer/bindings_onnx.hpp @@ -0,0 +1,43 @@ +// 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_GAPI_INFER_BINDINGS_ONNX_HPP +#define OPENCV_GAPI_INFER_BINDINGS_ONNX_HPP + +#include // GKernelPackage +#include // Params +#include "opencv2/gapi/own/exports.hpp" // GAPI_EXPORTS +#include + +#include + +namespace cv { +namespace gapi { +namespace onnx { + +// NB: Used by python wrapper +// This class can be marked as SIMPLE, because it's implemented as pimpl +class GAPI_EXPORTS_W_SIMPLE PyParams { +public: + GAPI_WRAP + PyParams() = default; + + GAPI_WRAP + PyParams(const std::string& tag, const std::string& model_path); + + GBackend backend() const; + std::string tag() const; + cv::util::any params() const; + +private: + std::shared_ptr> m_priv; +}; + +GAPI_EXPORTS_W PyParams params(const std::string& tag, const std::string& model_path); + +} // namespace onnx +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_INFER_BINDINGS_ONNX_HPP diff --git a/modules/gapi/include/opencv2/gapi/infer/ie.hpp b/modules/gapi/include/opencv2/gapi/infer/ie.hpp index 204bd8f266..0f7a59d8ae 100644 --- a/modules/gapi/include/opencv2/gapi/infer/ie.hpp +++ b/modules/gapi/include/opencv2/gapi/infer/ie.hpp @@ -52,6 +52,8 @@ enum class TraitAs: int using IEConfig = std::map; +enum InferMode {Sync, Async}; + namespace detail { struct ParamDesc { std::string model_path; @@ -88,6 +90,20 @@ struct ParamDesc { cv::optional vpl_preproc_device; cv::optional vpl_preproc_ctx; + + InferMode mode; + + using PrecisionT = int; + using PrecisionMapT = std::unordered_map; + // NB: This parameter can contain: + // 1. cv::util::monostate - Don't specify precision, but use default from IR/Blob. + // 2. PrecisionT (CV_8U, CV_32F, ...) - Specifies precision for all output layers. + // 3. PrecisionMapT ({{"layer0", CV_32F}, {"layer1", CV_16F}} - Specifies precision for certain output layer. + // cv::util::monostate is default value that means precision wasn't specified. + using PrecisionVariantT = cv::util::variant; + PrecisionVariantT output_precision; }; } // namespace detail @@ -132,7 +148,9 @@ public: , {} , {} , {} - , {}} { + , {} + , InferMode::Async + , {} } { }; /** @overload @@ -156,7 +174,9 @@ public: , {} , {} , {} - , {}} { + , {} + , InferMode::Async + , {} } { }; /** @brief Specifies sequence of network input layers names for inference. @@ -351,6 +371,47 @@ public: return *this; } + /** @brief Specifies which api will be used to run inference. + + The function is used to specify mode for OpenVINO inference. + OpenVINO has two options to run inference: + 1. Asynchronous (using StartAsync: https://docs.openvino.ai/latest/classInferenceEngine_1_1InferRequest.html#doxid-class-inference-engine-1-1-infer-request-1a405293e8423d82a5b45f642a3bef0d24) + 2. Synchronous (using Infer: https://docs.openvino.ai/latest/classInferenceEngine_1_1InferRequest.html#doxid-class-inference-engine-1-1-infer-request-1a3391ce30894abde730523e9ca9371ce8) + By default asynchronous mode is used. + + @param mode Inference mode which will be used. + @return reference to this parameter structure. + */ + Params& cfgInferMode(InferMode mode) { + desc.mode = mode; + return *this; + } + + /** @brief Specifies the output precision for model. + + The function is used to set an output precision for model. + + @param precision Precision in OpenCV format (CV_8U, CV_32F, ...) + will be applied to all output layers. + @return reference to this parameter structure. + */ + Params& cfgOutputPrecision(detail::ParamDesc::PrecisionT precision) { + desc.output_precision = precision; + return *this; + } + + /** @overload + + @param precision_map Map of pairs: name of corresponding output layer + and its precision in OpenCV format (CV_8U, CV_32F, ...) + @return reference to this parameter structure. + */ + Params& + cfgOutputPrecision(detail::ParamDesc::PrecisionMapT precision_map) { + desc.output_precision = precision_map; + return *this; + } + // BEGIN(G-API's network parametrization API) GBackend backend() const { return cv::gapi::ie::backend(); } std::string tag() const { return Net::tag(); } @@ -385,7 +446,7 @@ public: const std::string &device) : desc{ model, weights, device, {}, {}, {}, 0u, 0u, detail::ParamDesc::Kind::Load, true, {}, {}, {}, 1u, - {}, {}, {}, {}}, + {}, {}, {}, {}, InferMode::Async, {} }, m_tag(tag) { }; @@ -403,7 +464,7 @@ public: const std::string &device) : desc{ model, {}, device, {}, {}, {}, 0u, 0u, detail::ParamDesc::Kind::Import, true, {}, {}, {}, 1u, - {}, {}, {}, {}}, + {}, {}, {}, {}, InferMode::Async, {} }, m_tag(tag) { }; @@ -476,6 +537,25 @@ public: return *this; } + /** @see ie::Params::cfgInferAPI */ + Params& cfgInferMode(InferMode mode) { + desc.mode = mode; + return *this; + } + + /** @see ie::Params::cfgOutputPrecision */ + Params& cfgOutputPrecision(detail::ParamDesc::PrecisionT precision) { + desc.output_precision = precision; + return *this; + } + + /** @overload */ + Params& + cfgOutputPrecision(detail::ParamDesc::PrecisionMapT precision_map) { + desc.output_precision = precision_map; + return *this; + } + // BEGIN(G-API's network parametrization API) GBackend backend() const { return cv::gapi::ie::backend(); } std::string tag() const { return m_tag; } diff --git a/modules/gapi/include/opencv2/gapi/infer/onnx.hpp b/modules/gapi/include/opencv2/gapi/infer/onnx.hpp index 16fc42eb63..90e04a2477 100644 --- a/modules/gapi/include/opencv2/gapi/infer/onnx.hpp +++ b/modules/gapi/include/opencv2/gapi/infer/onnx.hpp @@ -17,6 +17,7 @@ #include // GAPI_EXPORTS #include // GKernelPackage +#include // Generic namespace cv { namespace gapi { @@ -67,6 +68,8 @@ struct ParamDesc { std::vector normalize; //!< Vector of bool values that enabled or disabled normalize of input data. std::vector names_to_remap; //!< Names of output layers that will be processed in PostProc function. + + bool is_generic; }; } // namespace detail @@ -103,6 +106,7 @@ public: desc.model_path = model; desc.num_in = std::tuple_size::value; desc.num_out = std::tuple_size::value; + desc.is_generic = false; }; /** @brief Specifies sequence of network input layers names for inference. @@ -277,6 +281,35 @@ protected: detail::ParamDesc desc; }; +/* +* @brief This structure provides functions for generic network type that +* fill inference parameters. +* @see struct Generic +*/ +template<> +class Params { +public: + /** @brief Class constructor. + + Constructs Params based on input information and sets default values for other + inference description parameters. + + @param tag string tag of the network for which these parameters are intended. + @param model_path path to model file (.onnx file). + */ + Params(const std::string& tag, const std::string& model_path) + : desc{model_path, 0u, 0u, {}, {}, {}, {}, {}, {}, {}, {}, {}, true}, m_tag(tag) {} + + // BEGIN(G-API's network parametrization API) + GBackend backend() const { return cv::gapi::onnx::backend(); } + std::string tag() const { return m_tag; } + cv::util::any params() const { return { desc }; } + // END(G-API's network parametrization API) +protected: + detail::ParamDesc desc; + std::string m_tag; +}; + } // namespace onnx } // namespace gapi } // namespace cv diff --git a/modules/gapi/include/opencv2/gapi/media.hpp b/modules/gapi/include/opencv2/gapi/media.hpp index 19aaef3fd1..5da8eeab48 100644 --- a/modules/gapi/include/opencv2/gapi/media.hpp +++ b/modules/gapi/include/opencv2/gapi/media.hpp @@ -242,11 +242,11 @@ public: // The default implementation does nothing virtual cv::util::any blobParams() const; virtual void serialize(cv::gapi::s11n::IOStream&) { - GAPI_Assert(false && "Generic serialize method of MediaFrame::IAdapter does nothing by default. " + GAPI_Error("Generic serialize method of MediaFrame::IAdapter does nothing by default. " "Please, implement it in derived class to properly serialize the object."); } virtual void deserialize(cv::gapi::s11n::IIStream&) { - GAPI_Assert(false && "Generic deserialize method of MediaFrame::IAdapter does nothing by default. " + GAPI_Error("Generic deserialize method of MediaFrame::IAdapter does nothing by default. " "Please, implement it in derived class to properly deserialize the object."); } }; diff --git a/modules/gapi/include/opencv2/gapi/ocl/goclkernel.hpp b/modules/gapi/include/opencv2/gapi/ocl/goclkernel.hpp index 6a2f1df769..b09082282f 100644 --- a/modules/gapi/include/opencv2/gapi/ocl/goclkernel.hpp +++ b/modules/gapi/include/opencv2/gapi/ocl/goclkernel.hpp @@ -119,6 +119,10 @@ template struct ocl_get_in > { static const std::vector& get(GOCLContext &ctx, int idx) { return ctx.inArg(idx).rref(); } }; +template<> struct ocl_get_in +{ + static cv::MediaFrame get(GOCLContext &ctx, int idx) { return ctx.inArg(idx); } +}; template struct ocl_get_in > { static const U& get(GOCLContext &ctx, int idx) { return ctx.inArg(idx).rref(); } @@ -149,6 +153,10 @@ struct tracked_cv_umat{ } }; +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4702) // unreachable code +#endif template void postprocess_ocl(Outputs&... outs) { @@ -162,6 +170,9 @@ void postprocess_ocl(Outputs&... outs) int dummy[] = { 0, (validate(&outs), 0)... }; cv::util::suppress_unused_warning(dummy); } +#ifdef _MSC_VER +#pragma warning(pop) +#endif template struct ocl_get_out; template<> struct ocl_get_out diff --git a/modules/gapi/include/opencv2/gapi/own/assert.hpp b/modules/gapi/include/opencv2/gapi/own/assert.hpp index 4bd3eaaf50..ab2fb896f1 100644 --- a/modules/gapi/include/opencv2/gapi/own/assert.hpp +++ b/modules/gapi/include/opencv2/gapi/own/assert.hpp @@ -25,6 +25,8 @@ # define GAPI_DbgAssert(expr) GAPI_DbgAssertNoOp(expr) #endif +#define GAPI_Error(msg) CV_Error(cv::Error::StsError, msg) + #else #include #include @@ -49,6 +51,10 @@ namespace detail # define GAPI_DbgAssert(expr) GAPI_Assert(expr) #endif +#define GAPI_Error(msg) { \ + ::detail::assert_abort(msg, __LINE__, __FILE__, __func__); \ +} + #endif // GAPI_STANDALONE #endif // OPENCV_GAPI_OWN_ASSERT_HPP diff --git a/modules/gapi/include/opencv2/gapi/python/python.hpp b/modules/gapi/include/opencv2/gapi/python/python.hpp index 0e20bbbb59..1857a938d5 100644 --- a/modules/gapi/include/opencv2/gapi/python/python.hpp +++ b/modules/gapi/include/opencv2/gapi/python/python.hpp @@ -31,19 +31,22 @@ struct GPythonContext const cv::GArgs &ins; const cv::GMetaArgs &in_metas; const cv::GTypesInfo &out_info; + + cv::optional m_state; }; using Impl = std::function; +using Setup = std::function; class GAPI_EXPORTS GPythonKernel { public: GPythonKernel() = default; - GPythonKernel(Impl run); + GPythonKernel(Impl run, Setup setup); - cv::GRunArgs operator()(const GPythonContext& ctx); -private: - Impl m_run; + Impl run; + Setup setup = nullptr; + bool is_stateful = false; }; class GAPI_EXPORTS GPythonFunctor : public cv::gapi::GFunctor @@ -51,7 +54,8 @@ class GAPI_EXPORTS GPythonFunctor : public cv::gapi::GFunctor public: using Meta = cv::GKernel::M; - GPythonFunctor(const char* id, const Meta &meta, const Impl& impl); + GPythonFunctor(const char* id, const Meta& meta, const Impl& impl, + const Setup& setup = nullptr); GKernelImpl impl() const override; gapi::GBackend backend() const override; diff --git a/modules/gapi/include/opencv2/gapi/rmat.hpp b/modules/gapi/include/opencv2/gapi/rmat.hpp index 38668d67a5..46989191b3 100644 --- a/modules/gapi/include/opencv2/gapi/rmat.hpp +++ b/modules/gapi/include/opencv2/gapi/rmat.hpp @@ -112,11 +112,11 @@ public: // is transferred to the device when the view is destroyed virtual View access(Access) = 0; virtual void serialize(cv::gapi::s11n::IOStream&) { - GAPI_Assert(false && "Generic serialize method of RMat::IAdapter does nothing by default. " + GAPI_Error("Generic serialize method of RMat::IAdapter does nothing by default. " "Please, implement it in derived class to properly serialize the object."); } virtual void deserialize(cv::gapi::s11n::IIStream&) { - GAPI_Assert(false && "Generic deserialize method of RMat::IAdapter does nothing by default. " + GAPI_Error("Generic deserialize method of RMat::IAdapter does nothing by default. " "Please, implement it in derived class to properly deserialize the object."); } }; diff --git a/modules/gapi/include/opencv2/gapi/s11n.hpp b/modules/gapi/include/opencv2/gapi/s11n.hpp index adbcfdbdeb..0bf368a856 100644 --- a/modules/gapi/include/opencv2/gapi/s11n.hpp +++ b/modules/gapi/include/opencv2/gapi/s11n.hpp @@ -17,7 +17,8 @@ #include // FIXME: caused by deserialize_runarg -#if (defined _WIN32 || defined _WIN64) && defined _MSC_VER +#if defined _MSC_VER +#pragma warning(push) #pragma warning(disable: 4702) #endif @@ -229,6 +230,9 @@ GAPI_EXPORTS IIStream& operator>> (IIStream& is, cv::Point &pt); GAPI_EXPORTS IOStream& operator<< (IOStream& os, const cv::Point2f &pt); GAPI_EXPORTS IIStream& operator>> (IIStream& is, cv::Point2f &pt); +GAPI_EXPORTS IOStream& operator<< (IOStream& os, const cv::Point3f &pt); +GAPI_EXPORTS IIStream& operator>> (IIStream& is, cv::Point3f &pt); + GAPI_EXPORTS IOStream& operator<< (IOStream& os, const cv::Size &sz); GAPI_EXPORTS IIStream& operator>> (IIStream& is, cv::Size &sz); @@ -332,7 +336,7 @@ IIStream& operator>> (IIStream& is, std::vector &ts) { namespace detail { template IOStream& put_v(IOStream&, const V&, std::size_t) { - GAPI_Assert(false && "variant>>: requested index is invalid"); + GAPI_Error("variant>>: requested index is invalid"); }; template @@ -344,7 +348,7 @@ IOStream& put_v(IOStream& os, const V& v, std::size_t x) { template IIStream& get_v(IIStream&, V&, std::size_t, std::size_t) { - GAPI_Assert(false && "variant<<: requested index is invalid"); + GAPI_Error("variant<<: requested index is invalid"); } template @@ -420,7 +424,7 @@ static GRunArg exec(cv::gapi::s11n::IIStream& is) { template struct deserialize_arg_with_adapter { static GRunArg exec(cv::gapi::s11n::IIStream&) { - GAPI_Assert(false && "No suitable adapter class found during RMat/MediaFrame deserialization. " + GAPI_Error("No suitable adapter class found during RMat/MediaFrame deserialization. " "Please, make sure you've passed them in cv::gapi::deserialize() template"); return GRunArg{}; } @@ -502,4 +506,8 @@ cv::GRunArgs getRunArgsWithAdapters(const std::vector &bytes) { } // namespace gapi } // namespace cv +#if defined _MSC_VER +#pragma warning(pop) +#endif + #endif // OPENCV_GAPI_S11N_HPP diff --git a/modules/gapi/include/opencv2/gapi/s11n/base.hpp b/modules/gapi/include/opencv2/gapi/s11n/base.hpp index 6cea94156e..760e8515f6 100644 --- a/modules/gapi/include/opencv2/gapi/s11n/base.hpp +++ b/modules/gapi/include/opencv2/gapi/s11n/base.hpp @@ -52,7 +52,7 @@ struct S11N: public NotImplemented { * properly overload the function to use it. */ static void serialize(IOStream &, const T &) { - GAPI_Assert(false && "No serialization routine is provided!"); + GAPI_Error("No serialization routine is provided!"); } /** * @brief This function allows user to deserialize their custom type. @@ -61,7 +61,7 @@ struct S11N: public NotImplemented { * properly overload the function to use it. */ static T deserialize(IIStream &) { - GAPI_Assert(false && "No deserialization routine is provided!"); + GAPI_Error("No deserialization routine is provided!"); } }; diff --git a/modules/gapi/include/opencv2/gapi/stereo.hpp b/modules/gapi/include/opencv2/gapi/stereo.hpp index dcf8f4d260..9b00267082 100644 --- a/modules/gapi/include/opencv2/gapi/stereo.hpp +++ b/modules/gapi/include/opencv2/gapi/stereo.hpp @@ -62,7 +62,7 @@ G_TYPED_KERNEL(GStereo, , "org.openc case StereoOutputFormat::DISPARITY_FIXED16_12_4: return left.withDepth(CV_16SC1); default: - GAPI_Assert(false && "Unknown output format!"); + GAPI_Error("Unknown output format!"); } } }; diff --git a/modules/gapi/include/opencv2/gapi/streaming/cap.hpp b/modules/gapi/include/opencv2/gapi/streaming/cap.hpp index 73d5bfcbeb..adf1133c3f 100644 --- a/modules/gapi/include/opencv2/gapi/streaming/cap.hpp +++ b/modules/gapi/include/opencv2/gapi/streaming/cap.hpp @@ -67,7 +67,7 @@ protected: cv::Mat tmp; if (!cap.read(tmp)) { - GAPI_Assert(false && "Couldn't grab the very first frame"); + GAPI_Error("Couldn't grab the very first frame"); } // NOTE: Some decode/media VideoCapture backends continue // owning the video buffer under cv::Mat so in order to diff --git a/modules/gapi/include/opencv2/gapi/streaming/onevpl/default.hpp b/modules/gapi/include/opencv2/gapi/streaming/onevpl/default.hpp new file mode 100644 index 0000000000..8b547e1aba --- /dev/null +++ b/modules/gapi/include/opencv2/gapi/streaming/onevpl/default.hpp @@ -0,0 +1,29 @@ +// 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) 2022 Intel Corporation + +#ifndef OPENCV_GAPI_STREAMING_ONEVPL_UTILS_HPP +#define OPENCV_GAPI_STREAMING_ONEVPL_UTILS_HPP + +#include // GAPI_EXPORTS +#include +#include + +namespace cv { +namespace gapi { +namespace wip { +namespace onevpl { + +/** + * @brief Provides default device selector based on config. + */ +GAPI_EXPORTS std::shared_ptr getDefaultDeviceSelector(const std::vector& cfg_params); + +} // namespace onevpl +} // namespace wip +} // namespace gapi +} // namespace cv + +#endif // OPENCV_GAPI_STREAMING_ONEVPL_UTILS_HPP diff --git a/modules/gapi/misc/python/package/gapi/__init__.py b/modules/gapi/misc/python/package/gapi/__init__.py index 87ad9e2086..992fa2aee2 100644 --- a/modules/gapi/misc/python/package/gapi/__init__.py +++ b/modules/gapi/misc/python/package/gapi/__init__.py @@ -76,6 +76,10 @@ class GOpaque(): def __new__(self): return cv.GOpaqueT(cv.gapi.CV_POINT2F) + class Point3f(): + def __new__(self): + return cv.GOpaqueT(cv.gapi.CV_POINT3F) + class Size(): def __new__(self): return cv.GOpaqueT(cv.gapi.CV_SIZE) @@ -127,6 +131,10 @@ class GArray(): def __new__(self): return cv.GArrayT(cv.gapi.CV_POINT2F) + class Point3f(): + def __new__(self): + return cv.GArrayT(cv.gapi.CV_POINT3F) + class Size(): def __new__(self): return cv.GArrayT(cv.gapi.CV_SIZE) @@ -167,6 +175,7 @@ def op(op_id, in_types, out_types): cv.GArray.String: cv.gapi.CV_STRING, cv.GArray.Point: cv.gapi.CV_POINT, cv.GArray.Point2f: cv.gapi.CV_POINT2F, + cv.GArray.Point3f: cv.gapi.CV_POINT3F, cv.GArray.Size: cv.gapi.CV_SIZE, cv.GArray.Rect: cv.gapi.CV_RECT, cv.GArray.Scalar: cv.gapi.CV_SCALAR, @@ -186,6 +195,7 @@ def op(op_id, in_types, out_types): cv.GOpaque.String: cv.gapi.CV_STRING, cv.GOpaque.Point: cv.gapi.CV_POINT, cv.GOpaque.Point2f: cv.gapi.CV_POINT2F, + cv.GOpaque.Point3f: cv.gapi.CV_POINT3F, cv.GOpaque.Size: cv.gapi.CV_SIZE, cv.GOpaque.Rect: cv.gapi.CV_RECT, cv.GOpaque.Prim: cv.gapi.CV_DRAW_PRIM, @@ -200,6 +210,7 @@ def op(op_id, in_types, out_types): cv.gapi.CV_STRING: 'cv.gapi.CV_STRING' , cv.gapi.CV_POINT: 'cv.gapi.CV_POINT' , cv.gapi.CV_POINT2F: 'cv.gapi.CV_POINT2F' , + cv.gapi.CV_POINT3F: 'cv.gapi.CV_POINT3F' , cv.gapi.CV_SIZE: 'cv.gapi.CV_SIZE', cv.gapi.CV_RECT: 'cv.gapi.CV_RECT', cv.gapi.CV_SCALAR: 'cv.gapi.CV_SCALAR', diff --git a/modules/gapi/misc/python/pyopencv_gapi.hpp b/modules/gapi/misc/python/pyopencv_gapi.hpp index 7b760920e7..49b2ddd1eb 100644 --- a/modules/gapi/misc/python/pyopencv_gapi.hpp +++ b/modules/gapi/misc/python/pyopencv_gapi.hpp @@ -14,10 +14,12 @@ using gapi_GKernelPackage = cv::GKernelPackage; using gapi_GNetPackage = cv::gapi::GNetPackage; using gapi_ie_PyParams = cv::gapi::ie::PyParams; +using gapi_onnx_PyParams = cv::gapi::onnx::PyParams; using gapi_wip_IStreamSource_Ptr = cv::Ptr; using detail_ExtractArgsCallback = cv::detail::ExtractArgsCallback; using detail_ExtractMetaCallback = cv::detail::ExtractMetaCallback; using vector_GNetParam = std::vector; +using vector_GMat = std::vector; using gapi_streaming_queue_capacity = cv::gapi::streaming::queue_capacity; using GStreamerSource_OutputType = cv::gapi::wip::GStreamerSource::OutputType; @@ -40,6 +42,7 @@ using GArray_float = cv::GArray; using GArray_string = cv::GArray; using GArray_Point2i = cv::GArray; using GArray_Point2f = cv::GArray; +using GArray_Point3f = cv::GArray; using GArray_Size = cv::GArray; using GArray_Rect = cv::GArray; using GArray_Scalar = cv::GArray; @@ -237,6 +240,7 @@ PyObject* pyopencv_from(const cv::GArg& value) HANDLE_CASE(STRING, std::string); HANDLE_CASE(POINT, cv::Point); HANDLE_CASE(POINT2F, cv::Point2f); + HANDLE_CASE(POINT3F, cv::Point3f); HANDLE_CASE(SIZE, cv::Size); HANDLE_CASE(RECT, cv::Rect); HANDLE_CASE(SCALAR, cv::Scalar); @@ -294,6 +298,7 @@ PyObject* pyopencv_from(const cv::detail::OpaqueRef& o) case cv::detail::OpaqueKind::CV_STRING : return pyopencv_from(o.rref()); case cv::detail::OpaqueKind::CV_POINT : return pyopencv_from(o.rref()); case cv::detail::OpaqueKind::CV_POINT2F : return pyopencv_from(o.rref()); + case cv::detail::OpaqueKind::CV_POINT3F : return pyopencv_from(o.rref()); case cv::detail::OpaqueKind::CV_SIZE : return pyopencv_from(o.rref()); case cv::detail::OpaqueKind::CV_RECT : return pyopencv_from(o.rref()); case cv::detail::OpaqueKind::CV_UNKNOWN : return pyopencv_from(o.rref()); @@ -320,6 +325,7 @@ PyObject* pyopencv_from(const cv::detail::VectorRef& v) case cv::detail::OpaqueKind::CV_STRING : return pyopencv_from_generic_vec(v.rref()); case cv::detail::OpaqueKind::CV_POINT : return pyopencv_from_generic_vec(v.rref()); case cv::detail::OpaqueKind::CV_POINT2F : return pyopencv_from_generic_vec(v.rref()); + case cv::detail::OpaqueKind::CV_POINT3F : return pyopencv_from_generic_vec(v.rref()); case cv::detail::OpaqueKind::CV_SIZE : return pyopencv_from_generic_vec(v.rref()); case cv::detail::OpaqueKind::CV_RECT : return pyopencv_from_generic_vec(v.rref()); case cv::detail::OpaqueKind::CV_SCALAR : return pyopencv_from_generic_vec(v.rref()); @@ -490,6 +496,7 @@ static cv::detail::OpaqueRef extract_opaque_ref(PyObject* from, cv::detail::Opaq HANDLE_CASE(STRING, std::string); HANDLE_CASE(POINT, cv::Point); HANDLE_CASE(POINT2F, cv::Point2f); + HANDLE_CASE(POINT3F, cv::Point3f); HANDLE_CASE(SIZE, cv::Size); HANDLE_CASE(RECT, cv::Rect); HANDLE_CASE(UNKNOWN, cv::GArg); @@ -522,6 +529,7 @@ static cv::detail::VectorRef extract_vector_ref(PyObject* from, cv::detail::Opaq HANDLE_CASE(STRING, std::string); HANDLE_CASE(POINT, cv::Point); HANDLE_CASE(POINT2F, cv::Point2f); + HANDLE_CASE(POINT3F, cv::Point3f); HANDLE_CASE(SIZE, cv::Size); HANDLE_CASE(RECT, cv::Rect); HANDLE_CASE(SCALAR, cv::Scalar); @@ -660,7 +668,8 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel, // NB: Doesn't increase reference counter (false), // because PyObject already have ownership. // In case exception decrement reference counter. - cv::detail::PyObjectHolder args(PyTuple_New(ins.size()), false); + cv::detail::PyObjectHolder args( + PyTuple_New(ctx.m_state.has_value() ? ins.size() + 1 : ins.size()), false); for (size_t i = 0; i < ins.size(); ++i) { // NB: If meta is monostate then object isn't associated with G-TYPE. @@ -690,6 +699,12 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel, } ++in_idx; } + + if (ctx.m_state.has_value()) + { + PyTuple_SetItem(args.get(), ins.size(), pyopencv_from(ctx.m_state.value())); + } + // NB: Doesn't increase reference counter (false). // In case PyObject_CallObject return NULL, do nothing in destructor. cv::detail::PyObjectHolder result( @@ -723,7 +738,7 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel, else { // Seems to be impossible case. - GAPI_Assert(false); + GAPI_Error("InternalError"); } } catch (...) @@ -736,6 +751,86 @@ static cv::GRunArgs run_py_kernel(cv::detail::PyObjectHolder kernel, return outs; } +static void unpackMetasToTuple(const cv::GMetaArgs& meta, + const cv::GArgs& gargs, + cv::detail::PyObjectHolder& tuple) +{ + size_t idx = 0; + for (auto&& m : meta) + { + switch (m.index()) + { + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, + pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, + pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, + pyopencv_from(cv::util::get(m))); + break; + case cv::GMetaArg::index_of(): + PyTuple_SetItem(tuple.get(), idx, pyopencv_from(gargs[idx])); + break; + case cv::GMetaArg::index_of(): + util::throw_error( + std::logic_error("GFrame isn't supported for custom operation")); + break; + } + ++idx; + } +} + +static cv::GArg setup_py(cv::detail::PyObjectHolder setup, + const cv::GMetaArgs& meta, + const cv::GArgs& gargs) +{ + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + cv::GArg out; + + try + { + // NB: Doesn't increase reference counter (false), + // because PyObject already have ownership. + // In case exception decrement reference counter. + cv::detail::PyObjectHolder args(PyTuple_New(meta.size()), false); + unpackMetasToTuple(meta, gargs, args); + // NB: Take an onwership because this state is "Python" type so it will be wrapped as-is + // into cv::GArg and stored in GPythonBackend. Object without ownership can't + // be dealocated outside this function. + cv::detail::PyObjectHolder result(PyObject_CallObject(setup.get(), args.get()), true); + + if (PyErr_Occurred()) + { + PyErr_PrintEx(0); + PyErr_Clear(); + throw std::logic_error("Python kernel failed with error!"); + } + // NB: In fact it's impossible situation, because errors were handled above. + GAPI_Assert(result.get() && "Python kernel returned NULL!"); + + if (!pyopencv_to(result.get(), out, ArgInfo("arg", false))) + { + util::throw_error(std::logic_error("Unsupported output meta type")); + } + } + catch (...) + { + PyGILState_Release(gstate); + throw; + } + PyGILState_Release(gstate); + return out; +} + static GMetaArg get_meta_arg(PyObject* obj) { cv::GMetaArg arg; @@ -761,8 +856,8 @@ static cv::GMetaArgs get_meta_args(PyObject* tuple) } static GMetaArgs run_py_meta(cv::detail::PyObjectHolder out_meta, - const cv::GMetaArgs &meta, - const cv::GArgs &gargs) + const cv::GMetaArgs &meta, + const cv::GArgs &gargs) { PyGILState_STATE gstate; gstate = PyGILState_Ensure(); @@ -774,32 +869,7 @@ static GMetaArgs run_py_meta(cv::detail::PyObjectHolder out_meta, // because PyObject already have ownership. // In case exception decrement reference counter. cv::detail::PyObjectHolder args(PyTuple_New(meta.size()), false); - size_t idx = 0; - for (auto&& m : meta) - { - switch (m.index()) - { - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(cv::util::get(m))); - break; - case cv::GMetaArg::index_of(): - PyTuple_SetItem(args.get(), idx, pyopencv_from(gargs[idx])); - break; - case cv::GMetaArg::index_of(): - util::throw_error(std::logic_error("GFrame isn't supported for custom operation")); - break; - } - ++idx; - } + unpackMetasToTuple(meta, gargs, args); // NB: Doesn't increase reference counter (false). // In case PyObject_CallObject return NULL, do nothing in destructor. cv::detail::PyObjectHolder result( @@ -860,6 +930,10 @@ static PyObject* pyopencv_cv_gapi_kernels(PyObject* , PyObject* py_args, PyObjec "Python kernel should contain run, please use cv.gapi.kernel to define kernel"); return NULL; } + PyObject* setup = nullptr; + if (PyObject_HasAttrString(user_kernel, "setup")) { + setup = PyObject_GetAttrString(user_kernel, "setup"); + } std::string id; if (!pyopencv_to(id_obj, id, ArgInfo("id", false))) @@ -869,10 +943,22 @@ static PyObject* pyopencv_cv_gapi_kernels(PyObject* , PyObject* py_args, PyObjec } using namespace std::placeholders; - gapi::python::GPythonFunctor f(id.c_str(), - std::bind(run_py_meta , cv::detail::PyObjectHolder{out_meta}, _1, _2), - std::bind(run_py_kernel, cv::detail::PyObjectHolder{run} , _1)); - pkg.include(f); + + if (setup) + { + gapi::python::GPythonFunctor f( + id.c_str(), std::bind(run_py_meta, cv::detail::PyObjectHolder{out_meta}, _1, _2), + std::bind(run_py_kernel, cv::detail::PyObjectHolder{run}, _1), + std::bind(setup_py, cv::detail::PyObjectHolder{setup}, _1, _2)); + pkg.include(f); + } + else + { + gapi::python::GPythonFunctor f( + id.c_str(), std::bind(run_py_meta, cv::detail::PyObjectHolder{out_meta}, _1, _2), + std::bind(run_py_kernel, cv::detail::PyObjectHolder{run}, _1)); + pkg.include(f); + } } return pyopencv_from(pkg); } diff --git a/modules/gapi/misc/python/python_bridge.hpp b/modules/gapi/misc/python/python_bridge.hpp index 11d1728730..07d056abb7 100644 --- a/modules/gapi/misc/python/python_bridge.hpp +++ b/modules/gapi/misc/python/python_bridge.hpp @@ -22,7 +22,7 @@ switch(type) { \ LIST_G(HC, HC) \ default: \ - GAPI_Assert(false && "Unsupported type"); \ + GAPI_Error("Unsupported type"); \ } using cv::gapi::wip::draw::Prim; @@ -36,6 +36,7 @@ WRAP_ARGS(float , cv::gapi::ArgType::CV_FLOAT, G) \ WRAP_ARGS(std::string , cv::gapi::ArgType::CV_STRING, G) \ WRAP_ARGS(cv::Point , cv::gapi::ArgType::CV_POINT, G) \ WRAP_ARGS(cv::Point2f , cv::gapi::ArgType::CV_POINT2F, G) \ +WRAP_ARGS(cv::Point3f , cv::gapi::ArgType::CV_POINT3F, G) \ WRAP_ARGS(cv::Size , cv::gapi::ArgType::CV_SIZE, G) \ WRAP_ARGS(cv::Rect , cv::gapi::ArgType::CV_RECT, G) \ WRAP_ARGS(cv::Scalar , cv::gapi::ArgType::CV_SCALAR, G) \ @@ -53,6 +54,7 @@ WRAP_ARGS(float , cv::gapi::ArgType::CV_FLOAT, G) \ WRAP_ARGS(std::string , cv::gapi::ArgType::CV_STRING, G) \ WRAP_ARGS(cv::Point , cv::gapi::ArgType::CV_POINT, G) \ WRAP_ARGS(cv::Point2f , cv::gapi::ArgType::CV_POINT2F, G) \ +WRAP_ARGS(cv::Point3f , cv::gapi::ArgType::CV_POINT3F, G) \ WRAP_ARGS(cv::Size , cv::gapi::ArgType::CV_SIZE, G) \ WRAP_ARGS(cv::GArg , cv::gapi::ArgType::CV_ANY, G) \ WRAP_ARGS(cv::Rect , cv::gapi::ArgType::CV_RECT, G2) \ @@ -70,6 +72,7 @@ enum ArgType { CV_STRING, CV_POINT, CV_POINT2F, + CV_POINT3F, CV_SIZE, CV_RECT, CV_SCALAR, @@ -154,7 +157,7 @@ public: SWITCH(m_arg.index(), GOPAQUE_TYPE_LIST_G, HC) #undef HC - GAPI_Assert(false); + GAPI_Error("InternalError"); } GAPI_WRAP gapi::ArgType type() { return m_type; } @@ -192,7 +195,7 @@ public: SWITCH(m_arg.index(), GARRAY_TYPE_LIST_G, HC) #undef HC - GAPI_Assert(false); + GAPI_Error("InternalError"); } GAPI_WRAP gapi::ArgType type() { return m_type; } diff --git a/modules/gapi/misc/python/shadow_gapi.hpp b/modules/gapi/misc/python/shadow_gapi.hpp index 802f4397a0..cf81335e0b 100644 --- a/modules/gapi/misc/python/shadow_gapi.hpp +++ b/modules/gapi/misc/python/shadow_gapi.hpp @@ -79,5 +79,6 @@ namespace streaming namespace detail { gapi::GNetParam GAPI_EXPORTS_W strip(gapi::ie::PyParams params); + gapi::GNetParam GAPI_EXPORTS_W strip(gapi::onnx::PyParams params); } // namespace detail } // namespace cv diff --git a/modules/gapi/misc/python/test/test_gapi_core.py b/modules/gapi/misc/python/test/test_gapi_core.py index 780558d98b..19cc87d442 100644 --- a/modules/gapi/misc/python/test/test_gapi_core.py +++ b/modules/gapi/misc/python/test/test_gapi_core.py @@ -164,7 +164,7 @@ try: def generate_random_points(self, sz): arr = np.random.random(sz).astype(np.float32).T - return list(zip(arr[0], arr[1])) + return list(zip(*[arr[i] for i in range(sz[1])])) def test_kmeans_2d(self): @@ -194,6 +194,33 @@ try: self.assertEqual(K, len(centers)) + def test_kmeans_3d(self): + # K-means 3D params + count = 100 + sz = (count, 3) + amount = sz[0] + K = 5 + flags = cv.KMEANS_RANDOM_CENTERS + attempts = 1 + criteria = (cv.TERM_CRITERIA_MAX_ITER + cv.TERM_CRITERIA_EPS, 30, 0) + in_vector = self.generate_random_points(sz) + in_labels = [] + + # G-API + data = cv.GArrayT(cv.gapi.CV_POINT3F) + best_labels = cv.GArrayT(cv.gapi.CV_INT) + + compactness, out_labels, centers = cv.gapi.kmeans(data, K, best_labels, criteria, attempts, flags) + comp = cv.GComputation(cv.GIn(data, best_labels), cv.GOut(compactness, out_labels, centers)) + + compact, labels, centers = comp.apply(cv.gin(in_vector, in_labels)) + + # Assert + self.assertTrue(compact >= 0) + self.assertEqual(amount, len(labels)) + self.assertEqual(K, len(centers)) + + except unittest.SkipTest as e: message = str(e) diff --git a/modules/gapi/misc/python/test/test_gapi_infer_onnx.py b/modules/gapi/misc/python/test/test_gapi_infer_onnx.py new file mode 100644 index 0000000000..443c6d4396 --- /dev/null +++ b/modules/gapi/misc/python/test/test_gapi_infer_onnx.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +import numpy as np +import cv2 as cv +import os +import sys +import unittest + +from tests_common import NewOpenCVTests + + +try: + + if sys.version_info[:2] < (3, 0): + raise unittest.SkipTest('Python 2.x is not supported') + + CLASSIFICATION_MODEL_PATH = "onnx_models/vision/classification/squeezenet/model/squeezenet1.0-9.onnx" + + testdata_required = bool(os.environ.get('OPENCV_DNN_TEST_REQUIRE_TESTDATA', False)) + + class test_gapi_infer(NewOpenCVTests): + def find_dnn_file(self, filename, required=None): + if not required: + required = testdata_required + return self.find_file(filename, [os.environ.get('OPENCV_DNN_TEST_DATA_PATH', os.getcwd()), + os.environ['OPENCV_TEST_DATA_PATH']], + required=required) + + def test_onnx_classification(self): + model_path = self.find_dnn_file(CLASSIFICATION_MODEL_PATH) + + if model_path is None: + raise unittest.SkipTest("Missing DNN test file") + + in_mat = cv.imread( + self.find_file("cv/dpm/cat.png", + [os.environ.get('OPENCV_TEST_DATA_PATH')])) + + g_in = cv.GMat() + g_infer_inputs = cv.GInferInputs() + g_infer_inputs.setInput("data_0", g_in) + g_infer_out = cv.gapi.infer("squeeze-net", g_infer_inputs) + g_out = g_infer_out.at("softmaxout_1") + + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out)) + + net = cv.gapi.onnx.params("squeeze-net", model_path) + try: + out_gapi = comp.apply(cv.gin(in_mat), cv.gapi.compile_args(cv.gapi.networks(net))) + except cv.error as err: + if err.args[0] == "G-API has been compiled without ONNX support": + raise unittest.SkipTest("G-API has been compiled without ONNX support") + else: + raise + + self.assertEqual((1, 1000, 1, 1), out_gapi.shape) + + +except unittest.SkipTest as e: + + message = str(e) + + class TestSkip(unittest.TestCase): + def setUp(self): + self.skipTest('Skip tests: ' + message) + + def test_skip(): + pass + + pass + + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py b/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py index 7763579ebf..8e15d457d9 100644 --- a/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py +++ b/modules/gapi/misc/python/test/test_gapi_sample_pipelines.py @@ -432,7 +432,7 @@ try: with self.assertRaises(Exception): create_op([cv.GMat, int], [cv.GMat]).on(cv.GMat()) - def test_stateful_kernel(self): + def test_state_in_class(self): @cv.gapi.op('custom.sum', in_types=[cv.GArray.Int], out_types=[cv.GOpaque.Int]) class GSum: @staticmethod diff --git a/modules/gapi/misc/python/test/test_gapi_stateful_kernel.py b/modules/gapi/misc/python/test/test_gapi_stateful_kernel.py new file mode 100644 index 0000000000..9b3b614523 --- /dev/null +++ b/modules/gapi/misc/python/test/test_gapi_stateful_kernel.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python + +import numpy as np +import cv2 as cv +import os +import sys +import unittest + +from tests_common import NewOpenCVTests + + +try: + + if sys.version_info[:2] < (3, 0): + raise unittest.SkipTest('Python 2.x is not supported') + + + class CounterState: + def __init__(self): + self.counter = 0 + + + @cv.gapi.op('stateful_counter', + in_types=[cv.GOpaque.Int], + out_types=[cv.GOpaque.Int]) + class GStatefulCounter: + """Accumulate state counter on every call""" + + @staticmethod + def outMeta(desc): + return cv.empty_gopaque_desc() + + + @cv.gapi.kernel(GStatefulCounter) + class GStatefulCounterImpl: + """Implementation for GStatefulCounter operation.""" + + @staticmethod + def setup(desc): + return CounterState() + + @staticmethod + def run(value, state): + state.counter += value + return state.counter + + + class gapi_sample_pipelines(NewOpenCVTests): + def test_stateful_kernel_single_instance(self): + g_in = cv.GOpaque.Int() + g_out = GStatefulCounter.on(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out)) + pkg = cv.gapi.kernels(GStatefulCounterImpl) + + nums = [i for i in range(10)] + acc = 0 + for v in nums: + acc = comp.apply(cv.gin(v), args=cv.gapi.compile_args(pkg)) + + self.assertEqual(sum(nums), acc) + + + def test_stateful_kernel_multiple_instances(self): + # NB: Every counter has his own independent state. + g_in = cv.GOpaque.Int() + g_out0 = GStatefulCounter.on(g_in) + g_out1 = GStatefulCounter.on(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out0, g_out1)) + pkg = cv.gapi.kernels(GStatefulCounterImpl) + + nums = [i for i in range(10)] + acc0 = acc1 = 0 + for v in nums: + acc0, acc1 = comp.apply(cv.gin(v), args=cv.gapi.compile_args(pkg)) + + ref = sum(nums) + self.assertEqual(ref, acc0) + self.assertEqual(ref, acc1) + + + def test_stateful_throw_setup(self): + @cv.gapi.kernel(GStatefulCounter) + class GThrowStatefulCounterImpl: + """Implementation for GStatefulCounter operation + that throw exception in setup method""" + + @staticmethod + def setup(desc): + raise Exception('Throw from setup method') + + @staticmethod + def run(value, state): + raise Exception('Unreachable') + + g_in = cv.GOpaque.Int() + g_out = GStatefulCounter.on(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out)) + pkg = cv.gapi.kernels(GThrowStatefulCounterImpl) + + with self.assertRaises(Exception): comp.apply(cv.gin(42), + args=cv.gapi.compile_args(pkg)) + + + def test_stateful_reset(self): + g_in = cv.GOpaque.Int() + g_out = GStatefulCounter.on(g_in) + comp = cv.GComputation(cv.GIn(g_in), cv.GOut(g_out)) + pkg = cv.gapi.kernels(GStatefulCounterImpl) + + cc = comp.compileStreaming(args=cv.gapi.compile_args(pkg)) + + cc.setSource(cv.gin(1)) + cc.start() + for i in range(1, 10): + _, actual = cc.pull() + self.assertEqual(i, actual) + cc.stop() + + cc.setSource(cv.gin(2)) + cc.start() + for i in range(2, 10, 2): + _, actual = cc.pull() + self.assertEqual(i, actual) + cc.stop() + + +except unittest.SkipTest as e: + + message = str(e) + + class TestSkip(unittest.TestCase): + def setUp(self): + self.skipTest('Skip tests: ' + message) + + def test_skip(): + pass + + pass + + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/gapi/misc/python/test/test_gapi_types.py b/modules/gapi/misc/python/test/test_gapi_types.py index dde554f5e1..41bfbabd61 100644 --- a/modules/gapi/misc/python/test/test_gapi_types.py +++ b/modules/gapi/misc/python/test/test_gapi_types.py @@ -18,8 +18,9 @@ try: def test_garray_type(self): types = [cv.gapi.CV_BOOL , cv.gapi.CV_INT , cv.gapi.CV_DOUBLE , cv.gapi.CV_FLOAT, - cv.gapi.CV_STRING, cv.gapi.CV_POINT , cv.gapi.CV_POINT2F, cv.gapi.CV_SIZE , - cv.gapi.CV_RECT , cv.gapi.CV_SCALAR, cv.gapi.CV_MAT , cv.gapi.CV_GMAT] + cv.gapi.CV_STRING, cv.gapi.CV_POINT , cv.gapi.CV_POINT2F, cv.gapi.CV_POINT3F , + cv.gapi.CV_SIZE , cv.gapi.CV_RECT , cv.gapi.CV_SCALAR , cv.gapi.CV_MAT , + cv.gapi.CV_GMAT] for t in types: g_array = cv.GArrayT(t) @@ -27,9 +28,9 @@ try: def test_gopaque_type(self): - types = [cv.gapi.CV_BOOL , cv.gapi.CV_INT , cv.gapi.CV_DOUBLE , cv.gapi.CV_FLOAT, - cv.gapi.CV_STRING, cv.gapi.CV_POINT , cv.gapi.CV_POINT2F, cv.gapi.CV_SIZE , - cv.gapi.CV_RECT] + types = [cv.gapi.CV_BOOL , cv.gapi.CV_INT , cv.gapi.CV_DOUBLE , cv.gapi.CV_FLOAT , + cv.gapi.CV_STRING, cv.gapi.CV_POINT, cv.gapi.CV_POINT2F, cv.gapi.CV_POINT3F, + cv.gapi.CV_SIZE , cv.gapi.CV_RECT] for t in types: g_opaque = cv.GOpaqueT(t) diff --git a/modules/gapi/samples/onevpl_infer_single_roi.cpp b/modules/gapi/samples/onevpl_infer_with_advanced_device_selection.cpp similarity index 99% rename from modules/gapi/samples/onevpl_infer_single_roi.cpp rename to modules/gapi/samples/onevpl_infer_with_advanced_device_selection.cpp index 4734b12f3f..de1d233ae5 100644 --- a/modules/gapi/samples/onevpl_infer_single_roi.cpp +++ b/modules/gapi/samples/onevpl_infer_with_advanced_device_selection.cpp @@ -20,13 +20,11 @@ #ifdef HAVE_DIRECTX #ifdef HAVE_D3D11 -#pragma comment(lib,"d3d11.lib") // get rid of generate macro max/min/etc from DX side #define D3D11_NO_HELPERS #define NOMINMAX #include -#pragma comment(lib, "dxgi") #undef NOMINMAX #undef D3D11_NO_HELPERS #endif // HAVE_D3D11 @@ -499,11 +497,11 @@ int main(int argc, char *argv[]) { std::tie(dx11_dev, dx11_ctx) = create_device_with_ctx(intel_adapter.get()); gpu_accel_device = cv::util::make_optional( cv::gapi::wip::onevpl::create_dx11_device( - reinterpret_cast(dx11_dev.get()), + reinterpret_cast(dx11_dev.release()), "GPU")); gpu_accel_ctx = cv::util::make_optional( cv::gapi::wip::onevpl::create_dx11_context( - reinterpret_cast(dx11_ctx.get()))); + reinterpret_cast(dx11_ctx.release()))); #endif // HAVE_D3D11 #endif // HAVE_DIRECTX #ifdef __linux__ diff --git a/modules/gapi/samples/onevpl_source_to_bgr_conv.cpp b/modules/gapi/samples/onevpl_source_to_bgr_conv.cpp new file mode 100644 index 0000000000..660d7ed9a8 --- /dev/null +++ b/modules/gapi/samples/onevpl_source_to_bgr_conv.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include // CommandLineParser +#include + +const std::string about = + "This is an example presents decoding on GPU using VPL Source and passing it to OpenCL backend"; +const std::string keys = + "{ h help | | Print this help message }" + "{ input | | Path to the input video file. Use .avi extension }" + "{ accel_mode | mfxImplDescription.AccelerationMode:MFX_ACCEL_MODE_VIA_D3D11 | Acceleration mode for VPL }"; + +namespace { +namespace cfg { +// FIXME: Move OneVPL arguments parser to a single place +typename cv::gapi::wip::onevpl::CfgParam create_from_string(const std::string &line); +} // namespace cfg +} // anonymous namespace + +int main(int argc, char *argv[]) { + cv::CommandLineParser cmd(argc, argv, keys); + cmd.about(about); + if (cmd.has("help")) { + cmd.printMessage(); + return 0; + } + + // Get file name + const auto input = cmd.get("input"); + const auto accel_mode = cmd.get("accel_mode"); + + // Create VPL config + std::vector source_cfgs; + source_cfgs.push_back(cfg::create_from_string(accel_mode)); + + // Create VPL-based source + std::shared_ptr default_device_selector = + cv::gapi::wip::onevpl::getDefaultDeviceSelector(source_cfgs); + + cv::gapi::wip::IStreamSource::Ptr source = cv::gapi::wip::make_onevpl_src(input, source_cfgs, + default_device_selector); + + // Build the graph + cv::GFrame in; // input frame from VPL source + auto bgr_gmat = cv::gapi::streaming::BGR(in); // conversion from VPL source frame to BGR UMat + auto out = cv::gapi::blur(bgr_gmat, cv::Size(4,4)); // ocl kernel of blur operation + + cv::GStreamingCompiled pipeline = cv::GComputation(cv::GIn(in), cv::GOut(out)) + .compileStreaming(cv::compile_args(cv::gapi::core::ocl::kernels())); + pipeline.setSource(std::move(source)); + + // The execution part + size_t frames = 0u; + cv::TickMeter tm; + cv::Mat outMat; + + pipeline.start(); + tm.start(); + + while (pipeline.pull(cv::gout(outMat))) { + cv::imshow("OutVideo", outMat); + cv::waitKey(1); + ++frames; + } + tm.stop(); + std::cout << "Processed " << frames << " frames" << " (" << frames / tm.getTimeSec() << " FPS)" << std::endl; + + return 0; +} + +namespace { +namespace cfg { +typename cv::gapi::wip::onevpl::CfgParam create_from_string(const std::string &line) { + using namespace cv::gapi::wip; + + if (line.empty()) { + throw std::runtime_error("Cannot parse CfgParam from emply line"); + } + + std::string::size_type name_endline_pos = line.find(':'); + if (name_endline_pos == std::string::npos) { + throw std::runtime_error("Cannot parse CfgParam from: " + line + + "\nExpected separator \":\""); + } + + std::string name = line.substr(0, name_endline_pos); + std::string value = line.substr(name_endline_pos + 1); + + return cv::gapi::wip::onevpl::CfgParam::create(name, value, + /* vpp params strongly optional */ + name.find("vpp.") == std::string::npos); +} +} // namespace cfg +} // anonymous namespace diff --git a/modules/gapi/samples/pipeline_modeling_tool.cpp b/modules/gapi/samples/pipeline_modeling_tool.cpp index 7a0f94655c..7c202642a9 100644 --- a/modules/gapi/samples/pipeline_modeling_tool.cpp +++ b/modules/gapi/samples/pipeline_modeling_tool.cpp @@ -175,6 +175,17 @@ static PLMode strToPLMode(const std::string& mode_str) { } } +static cv::gapi::ie::InferMode strToInferMode(const std::string& infer_mode) { + if (infer_mode == "async") { + return cv::gapi::ie::InferMode::Async; + } else if (infer_mode == "sync") { + return cv::gapi::ie::InferMode::Sync; + } else { + throw std::logic_error("Unsupported Infer mode: " + infer_mode + + "\nPlease chose between: async and sync"); + } +} + template <> CallParams read(const cv::FileNode& fn) { auto name = @@ -190,6 +201,15 @@ CallParams read(const cv::FileNode& fn) { return CallParams{std::move(name), static_cast(call_every_nth)}; } +template +std::map readMap(const cv::FileNode& fn) { + std::map map; + for (auto item : fn) { + map.emplace(item.name(), read(item)); + } + return map; +} + template <> InferParams read(const cv::FileNode& fn) { auto name = @@ -200,7 +220,13 @@ InferParams read(const cv::FileNode& fn) { params.device = check_and_read(fn, "device", name); params.input_layers = readList(fn, "input_layers", name); params.output_layers = readList(fn, "output_layers", name); + params.config = readMap(fn["config"]); + auto out_prec_str = readOpt(fn["output_precision"]); + if (out_prec_str.has_value()) { + params.out_precision = + cv::optional(strToPrecision(out_prec_str.value())); + } return params; } @@ -273,7 +299,8 @@ int main(int argc, char* argv[]) { "{ drop_frames | false | Drop frames if they come earlier than pipeline is completed. }" "{ exec_list | | A comma-separated list of pipelines that" " will be executed. Spaces around commas" - " are prohibited. }"; + " are prohibited. }" + "{ infer_mode | async | OpenVINO inference mode (async/sync). }"; cv::CommandLineParser cmd(argc, argv, keys); if (cmd.has("help")) { @@ -289,6 +316,7 @@ int main(int argc, char* argv[]) { const auto qc = cmd.get("qc"); const auto app_mode = strToAppMode(cmd.get("app_mode")); const auto exec_str = cmd.get("exec_list"); + const auto infer_mode = strToInferMode(cmd.get("infer_mode")); const auto drop_frames = cmd.get("drop_frames"); cv::FileStorage fs; @@ -309,20 +337,24 @@ int main(int argc, char* argv[]) { cv::FileStorage::MEMORY); } - std::map config; + std::map gconfig; if (!load_config.empty()) { - loadConfig(load_config, config); + loadConfig(load_config, gconfig); } // NB: Takes priority over config from file if (!cached_dir.empty()) { - config = + gconfig = std::map{{"CACHE_DIR", cached_dir}}; } - const double work_time_ms = - check_and_read(fs, "work_time", "Config"); - if (work_time_ms < 0) { - throw std::logic_error("work_time must be positive"); + auto opt_work_time_ms = readOpt(fs["work_time"]); + cv::optional opt_work_time_mcs; + if (opt_work_time_ms) { + const double work_time_ms = opt_work_time_ms.value(); + if (work_time_ms < 0) { + throw std::logic_error("work_time must be positive"); + } + opt_work_time_mcs = cv::optional(utils::ms_to_mcs(work_time_ms)); } auto pipelines_fn = check_and_get_fn(fs, "Pipelines", "Config"); @@ -341,6 +373,21 @@ int main(int argc, char* argv[]) { for (const auto& name : exec_list) { const auto& pl_fn = check_and_get_fn(pipelines_fn, name, "Pipelines"); builder.setName(name); + StopCriterion::Ptr stop_criterion; + auto opt_num_iters = readOpt(pl_fn["num_iters"]); + // NB: num_iters for specific pipeline takes priority over global work_time. + if (opt_num_iters) { + stop_criterion.reset(new NumItersCriterion(opt_num_iters.value())); + } else if (opt_work_time_mcs) { + stop_criterion.reset(new ElapsedTimeCriterion(opt_work_time_mcs.value())); + } else { + throw std::logic_error( + "Failed: Pipeline " + name + " doesn't have stop criterion!\n" + "Please specify either work_time: in the config root" + " or num_iters: for specific pipeline."); + } + builder.setStopCriterion(std::move(stop_criterion)); + // NB: Set source { const auto& src_fn = check_and_get_fn(pl_fn, "source", name); @@ -371,7 +418,15 @@ int main(int argc, char* argv[]) { builder.addDummy(call_params, read(node_fn)); } else if (node_type == "Infer") { auto infer_params = read(node_fn); - infer_params.config = config; + try { + utils::mergeMapWith(infer_params.config, gconfig); + } catch (std::exception& e) { + std::stringstream ss; + ss << "Failed to merge global and local config for Infer node: " + << call_params.name << std::endl << e.what(); + throw std::logic_error(ss.str()); + } + infer_params.mode = infer_mode; builder.addInfer(call_params, infer_params); } else { throw std::logic_error("Unsupported node type: " + node_type); @@ -428,7 +483,7 @@ int main(int argc, char* argv[]) { for (size_t i = 0; i < pipelines.size(); ++i) { threads[i] = std::thread([&, i]() { try { - pipelines[i]->run(work_time_ms); + pipelines[i]->run(); } catch (...) { eptrs[i] = std::current_exception(); } diff --git a/modules/gapi/samples/pipeline_modeling_tool/dummy_source.hpp b/modules/gapi/samples/pipeline_modeling_tool/dummy_source.hpp index d77e120081..4c2ea1638c 100644 --- a/modules/gapi/samples/pipeline_modeling_tool/dummy_source.hpp +++ b/modules/gapi/samples/pipeline_modeling_tool/dummy_source.hpp @@ -18,6 +18,7 @@ public: const bool drop_frames); bool pull(cv::gapi::wip::Data& data) override; cv::GMetaArg descr_of() const override; + double latency() const { return m_latency; }; private: double m_latency; diff --git a/modules/gapi/samples/pipeline_modeling_tool/pipeline.hpp b/modules/gapi/samples/pipeline_modeling_tool/pipeline.hpp index 2951d45610..ac192cba52 100644 --- a/modules/gapi/samples/pipeline_modeling_tool/pipeline.hpp +++ b/modules/gapi/samples/pipeline_modeling_tool/pipeline.hpp @@ -1,13 +1,18 @@ #ifndef OPENCV_GAPI_PIPELINE_MODELING_TOOL_PIPELINE_HPP #define OPENCV_GAPI_PIPELINE_MODELING_TOOL_PIPELINE_HPP +#include + struct PerfReport { - std::string name; - double avg_latency = 0.0; - double throughput = 0.0; - int64_t first_run_latency = 0; - int64_t elapsed = 0; - int64_t compilation_time = 0; + std::string name; + double avg_latency = 0.0; + int64_t min_latency = 0; + int64_t max_latency = 0; + int64_t first_latency = 0; + double throughput = 0.0; + int64_t elapsed = 0; + int64_t warmup_time = 0; + int64_t num_late_frames = 0; std::vector latencies; std::string toStr(bool expanded = false) const; @@ -15,17 +20,19 @@ struct PerfReport { std::string PerfReport::toStr(bool expand) const { std::stringstream ss; - ss << name << ": Compilation time: " << compilation_time << " ms; " - << "Average latency: " << avg_latency << " ms; Throughput: " - << throughput << " FPS; First latency: " - << first_run_latency << " ms"; - + ss << name << ": \n" + << " Warm up time: " << warmup_time << " ms\n" + << " Execution time: " << elapsed << " ms\n" + << " Frames: " << num_late_frames << "/" << latencies.size() << " (late/all)\n" + << " Latency:\n" + << " first: " << first_latency << " ms\n" + << " min: " << min_latency << " ms\n" + << " max: " << max_latency << " ms\n" + << " avg: " << std::fixed << std::setprecision(3) << avg_latency << " ms\n" + << " Throughput: " << std::fixed << std::setprecision(3) << throughput << " FPS"; if (expand) { - ss << "\nTotal processed frames: " << latencies.size() - << "\nTotal elapsed time: " << elapsed << " ms" << std::endl; for (size_t i = 0; i < latencies.size(); ++i) { - ss << std::endl; - ss << "Frame:" << i << "\nLatency: " + ss << "\nFrame:" << i << "\nLatency: " << latencies[i] << " ms"; } } @@ -33,74 +40,103 @@ std::string PerfReport::toStr(bool expand) const { return ss.str(); } +class StopCriterion { +public: + using Ptr = std::unique_ptr; + + virtual void start() = 0; + virtual void iter() = 0; + virtual bool done() = 0; + virtual ~StopCriterion() = default; +}; + class Pipeline { public: using Ptr = std::shared_ptr; - Pipeline(std::string&& name, - cv::GComputation&& comp, - cv::gapi::wip::IStreamSource::Ptr&& src, - cv::GCompileArgs&& args, - const size_t num_outputs); + Pipeline(std::string&& name, + cv::GComputation&& comp, + std::shared_ptr&& src, + StopCriterion::Ptr stop_criterion, + cv::GCompileArgs&& args, + const size_t num_outputs); void compile(); - void run(double work_time_ms); + void run(); + const PerfReport& report() const; const std::string& name() const { return m_name;} virtual ~Pipeline() = default; protected: - struct RunPerf { - int64_t elapsed = 0; - std::vector latencies; - }; + virtual void _compile() = 0; + virtual int64_t run_iter() = 0; + virtual void init() {}; + virtual void deinit() {}; - virtual void _compile() = 0; - virtual RunPerf _run(double work_time_ms) = 0; - - std::string m_name; - cv::GComputation m_comp; - cv::gapi::wip::IStreamSource::Ptr m_src; - cv::GCompileArgs m_args; - size_t m_num_outputs; - PerfReport m_perf; + std::string m_name; + cv::GComputation m_comp; + std::shared_ptr m_src; + StopCriterion::Ptr m_stop_criterion; + cv::GCompileArgs m_args; + size_t m_num_outputs; + PerfReport m_perf; }; -Pipeline::Pipeline(std::string&& name, - cv::GComputation&& comp, - cv::gapi::wip::IStreamSource::Ptr&& src, - cv::GCompileArgs&& args, - const size_t num_outputs) +Pipeline::Pipeline(std::string&& name, + cv::GComputation&& comp, + std::shared_ptr&& src, + StopCriterion::Ptr stop_criterion, + cv::GCompileArgs&& args, + const size_t num_outputs) : m_name(std::move(name)), m_comp(std::move(comp)), m_src(std::move(src)), + m_stop_criterion(std::move(stop_criterion)), m_args(std::move(args)), m_num_outputs(num_outputs) { m_perf.name = m_name; } void Pipeline::compile() { - m_perf.compilation_time = + m_perf.warmup_time = utils::measure([this]() { _compile(); }); } -void Pipeline::run(double work_time_ms) { - auto run_perf = _run(work_time_ms); +void Pipeline::run() { + using namespace std::chrono; - m_perf.elapsed = run_perf.elapsed; - m_perf.latencies = std::move(run_perf.latencies); + init(); + auto start = high_resolution_clock::now(); + m_stop_criterion->start(); + while (true) { + m_perf.latencies.push_back(run_iter()); + m_perf.elapsed = duration_cast(high_resolution_clock::now() - start).count(); + m_stop_criterion->iter(); + + if (m_stop_criterion->done()) { + deinit(); + break; + } + } + + m_perf.avg_latency = utils::avg(m_perf.latencies); + m_perf.min_latency = utils::min(m_perf.latencies); + m_perf.max_latency = utils::max(m_perf.latencies); + m_perf.first_latency = m_perf.latencies[0]; + + // NB: Count how many executions don't fit into camera latency interval. + m_perf.num_late_frames = + std::count_if(m_perf.latencies.begin(), m_perf.latencies.end(), + [this](int64_t latency) { + return static_cast(latency) > m_src->latency(); + }); - m_perf.avg_latency = - std::accumulate(m_perf.latencies.begin(), - m_perf.latencies.end(), - 0.0) / static_cast(m_perf.latencies.size()); m_perf.throughput = (m_perf.latencies.size() / static_cast(m_perf.elapsed)) * 1000; - - m_perf.first_run_latency = m_perf.latencies[0]; } const PerfReport& Pipeline::report() const { @@ -118,39 +154,31 @@ private: cv::GCompileArgs(m_args)); } - Pipeline::RunPerf _run(double work_time_ms) override { - // NB: Setup. + virtual void init() override { using namespace std::chrono; // NB: N-1 buffers + timestamp. - std::vector out_mats(m_num_outputs - 1); - int64_t start_ts = -1; - cv::GRunArgsP pipeline_outputs; - for (auto& m : out_mats) { - pipeline_outputs += cv::gout(m); + m_out_mats.resize(m_num_outputs - 1); + for (auto& m : m_out_mats) { + m_pipeline_outputs += cv::gout(m); } - pipeline_outputs += cv::gout(start_ts); + m_pipeline_outputs += cv::gout(m_start_ts); m_compiled.setSource(m_src); - - // NB: Start execution & measure performance statistics. - Pipeline::RunPerf perf; - auto start = high_resolution_clock::now(); m_compiled.start(); - while (m_compiled.pull(cv::GRunArgsP{pipeline_outputs})) { - int64_t latency = utils::timestamp() - start_ts; + } - perf.latencies.push_back(latency); - perf.elapsed = duration_cast( - high_resolution_clock::now() - start).count(); + virtual void deinit() override { + m_compiled.stop(); + } - if (perf.elapsed >= work_time_ms) { - m_compiled.stop(); - break; - } - }; - return perf; + virtual int64_t run_iter() override { + m_compiled.pull(cv::GRunArgsP{m_pipeline_outputs}); + return utils::timestamp() - m_start_ts; } cv::GStreamingCompiled m_compiled; + cv::GRunArgsP m_pipeline_outputs; + std::vector m_out_mats; + int64_t m_start_ts; }; class RegularPipeline : public Pipeline { @@ -164,37 +192,26 @@ private: cv::GCompileArgs(m_args)); } - Pipeline::RunPerf _run(double work_time_ms) override { - // NB: Setup - using namespace std::chrono; - cv::gapi::wip::Data d; - std::vector out_mats(m_num_outputs); - cv::GRunArgsP pipeline_outputs; - for (auto& m : out_mats) { - pipeline_outputs += cv::gout(m); + virtual void init() override { + m_out_mats.resize(m_num_outputs); + for (auto& m : m_out_mats) { + m_pipeline_outputs += cv::gout(m); } - - // NB: Start execution & measure performance statistics. - Pipeline::RunPerf perf; - auto start = high_resolution_clock::now(); - while (m_src->pull(d)) { - auto in_mat = cv::util::get(d); - int64_t latency = utils::measure([&]{ - m_compiled(cv::gin(in_mat), cv::GRunArgsP{pipeline_outputs}); - }); - - perf.latencies.push_back(latency); - perf.elapsed = duration_cast( - high_resolution_clock::now() - start).count(); - - if (perf.elapsed >= work_time_ms) { - break; - } - }; - return perf; } - cv::GCompiled m_compiled; + virtual int64_t run_iter() override { + using namespace std::chrono; + cv::gapi::wip::Data d; + m_src->pull(d); + auto in_mat = cv::util::get(d); + return utils::measure([&]{ + m_compiled(cv::gin(in_mat), cv::GRunArgsP{m_pipeline_outputs}); + }); + } + + cv::GCompiled m_compiled; + cv::GRunArgsP m_pipeline_outputs; + std::vector m_out_mats; }; enum class PLMode { diff --git a/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp b/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp index a3f187249d..6ac6374f07 100644 --- a/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp +++ b/modules/gapi/samples/pipeline_modeling_tool/pipeline_builder.hpp @@ -258,8 +258,69 @@ struct InferParams { std::vector input_layers; std::vector output_layers; std::map config; + cv::gapi::ie::InferMode mode; + cv::util::optional out_precision; }; +class ElapsedTimeCriterion : public StopCriterion { +public: + ElapsedTimeCriterion(int64_t work_time_mcs); + + void start() override; + void iter() override; + bool done() override; + +private: + int64_t m_work_time_mcs; + int64_t m_start_ts = -1; + int64_t m_curr_ts = -1; +}; + +ElapsedTimeCriterion::ElapsedTimeCriterion(int64_t work_time_mcs) + : m_work_time_mcs(work_time_mcs) { +}; + +void ElapsedTimeCriterion::start() { + m_start_ts = m_curr_ts = utils::timestamp(); +} + +void ElapsedTimeCriterion::iter() { + m_curr_ts = utils::timestamp(); +} + +bool ElapsedTimeCriterion::done() { + return (m_curr_ts - m_start_ts) >= m_work_time_mcs; +} + +class NumItersCriterion : public StopCriterion { +public: + NumItersCriterion(int64_t num_iters); + + void start() override; + void iter() override; + bool done() override; + +private: + int64_t m_num_iters; + int64_t m_curr_iters = 0; +}; + +NumItersCriterion::NumItersCriterion(int64_t num_iters) + : m_num_iters(num_iters) { +} + +void NumItersCriterion::start() { + m_curr_iters = 0; +} + +void NumItersCriterion::iter() { + ++m_curr_iters; +} + +bool NumItersCriterion::done() { + return m_curr_iters == m_num_iters; +} + class PipelineBuilder { public: PipelineBuilder(); @@ -277,6 +338,7 @@ public: void setDumpFilePath(const std::string& dump); void setQueueCapacity(const size_t qc); void setName(const std::string& name); + void setStopCriterion(StopCriterion::Ptr stop_criterion); Pipeline::Ptr build(); @@ -295,15 +357,16 @@ private: std::vector output_edges; }; - M calls_map; - std::vector all_calls; + M calls_map; + std::vector all_calls; - cv::gapi::GNetPackage networks; - cv::gapi::GKernelPackage kernels; - cv::GCompileArgs compile_args; - cv::gapi::wip::IStreamSource::Ptr src; - PLMode mode = PLMode::STREAMING; - std::string name; + cv::gapi::GNetPackage networks; + cv::gapi::GKernelPackage kernels; + cv::GCompileArgs compile_args; + std::shared_ptr src; + PLMode mode = PLMode::STREAMING; + std::string name; + StopCriterion::Ptr stop_criterion; }; std::unique_ptr m_state; @@ -362,6 +425,10 @@ void PipelineBuilder::addInfer(const CallParams& call_params, } pp->pluginConfig(infer_params.config); + pp->cfgInferMode(infer_params.mode); + if (infer_params.out_precision) { + pp->cfgOutputPrecision(infer_params.out_precision.value()); + } m_state->networks += cv::gapi::networks(*pp); addCall(call_params, @@ -426,6 +493,10 @@ void PipelineBuilder::setName(const std::string& name) { m_state->name = name; } +void PipelineBuilder::setStopCriterion(StopCriterion::Ptr stop_criterion) { + m_state->stop_criterion = std::move(stop_criterion); +} + static bool visit(Node::Ptr node, std::vector& sorted, std::unordered_map& visited) { @@ -584,6 +655,7 @@ Pipeline::Ptr PipelineBuilder::construct() { } } + GAPI_Assert(m_state->stop_criterion); if (m_state->mode == PLMode::STREAMING) { GAPI_Assert(graph_inputs.size() == 1); GAPI_Assert(cv::util::holds_alternative(graph_inputs[0])); @@ -599,6 +671,7 @@ Pipeline::Ptr PipelineBuilder::construct() { cv::GProtoInputArgs{graph_inputs}, cv::GProtoOutputArgs{graph_outputs}), std::move(m_state->src), + std::move(m_state->stop_criterion), std::move(m_state->compile_args), graph_outputs.size()); } @@ -608,6 +681,7 @@ Pipeline::Ptr PipelineBuilder::construct() { cv::GProtoInputArgs{graph_inputs}, cv::GProtoOutputArgs{graph_outputs}), std::move(m_state->src), + std::move(m_state->stop_criterion), std::move(m_state->compile_args), graph_outputs.size()); } diff --git a/modules/gapi/samples/pipeline_modeling_tool/test_pipeline_modeling_tool.py b/modules/gapi/samples/pipeline_modeling_tool/test_pipeline_modeling_tool.py index d56a0399e9..d1701d9ad2 100644 --- a/modules/gapi/samples/pipeline_modeling_tool/test_pipeline_modeling_tool.py +++ b/modules/gapi/samples/pipeline_modeling_tool/test_pipeline_modeling_tool.py @@ -26,14 +26,6 @@ def test_error_no_config_exists(): assert 'Failed to open config file: not_existing_cfg.yml' in out -def test_error_no_work_time(): - cfg_file = """\"%YAML:1.0\" """ - - exec_str = '{} --cfg={}'.format(pipeline_modeling_tool, cfg_file) - out = get_output(exec_str) - assert out.startswith('Config must contain field: work_time') - - def test_error_work_time_not_positive(): cfg_file = """\"%YAML:1.0 work_time: -1\" """ @@ -77,7 +69,8 @@ def test_error_no_source(): cfg_file = """\"%YAML:1.0 work_time: 1000 Pipelines: - PL1:\" """ + PL1: + queue_capacity: 1\" """ exec_str = '{} --cfg={}'.format(pipeline_modeling_tool, cfg_file) out = get_output(exec_str) @@ -982,3 +975,29 @@ Pipelines: check(cfg_file, -3) check(cfg_file, 0) + + +def test_error_no_worktime_and_num_iters(): + cfg_file = """\"%YAML:1.0 +Pipelines: + PL1: + source: + name: 'Src' + latency: 20 + output: + dims: [1,1] + precision: 'U8' + nodes: + - name: 'Node0' + type: 'Dummy' + time: 0.2 + output: + dims: [1,2,3,4] + precision: 'U8' + edges: + - from: 'Src' + to: 'Node0'\" """ + + exec_str = '{} --cfg={}'.format(pipeline_modeling_tool, cfg_file) + out = get_output(exec_str) + assert out.startswith('Failed: Pipeline PL1 doesn\'t have stop criterion!') diff --git a/modules/gapi/samples/pipeline_modeling_tool/utils.hpp b/modules/gapi/samples/pipeline_modeling_tool/utils.hpp index c110bf3b47..c0f0897c35 100644 --- a/modules/gapi/samples/pipeline_modeling_tool/utils.hpp +++ b/modules/gapi/samples/pipeline_modeling_tool/utils.hpp @@ -1,6 +1,8 @@ #ifndef OPENCV_GAPI_PIPELINE_MODELING_TOOL_UTILS_HPP #define OPENCV_GAPI_PIPELINE_MODELING_TOOL_UTILS_HPP +#include + #include #if defined(_WIN32) @@ -91,6 +93,38 @@ typename duration_t::rep timestamp() { return duration_cast(now.time_since_epoch()).count(); } +template +void mergeMapWith(std::map& target, const std::map& second) { + for (auto&& item : second) { + auto it = target.find(item.first); + if (it != target.end()) { + throw std::logic_error("Error: key: " + it->first + " is already in target map"); + } + target.insert(item); + } +} + +template +double avg(const std::vector& vec) { + return std::accumulate(vec.begin(), vec.end(), 0.0) / vec.size(); +} + +template +T max(const std::vector& vec) { + return *std::max_element(vec.begin(), vec.end()); +} + +template +T min(const std::vector& vec) { + return *std::min_element(vec.begin(), vec.end()); +} + +template +int64_t ms_to_mcs(T ms) { + using namespace std::chrono; + return duration_cast(duration(ms)).count(); +} + } // namespace utils #endif // OPENCV_GAPI_PIPELINE_MODELING_TOOL_UTILS_HPP diff --git a/modules/gapi/src/api/gbackend.cpp b/modules/gapi/src/api/gbackend.cpp index e3b1e7123d..efbe17a305 100644 --- a/modules/gapi/src/api/gbackend.cpp +++ b/modules/gapi/src/api/gbackend.cpp @@ -35,7 +35,7 @@ cv::gapi::GBackend::Priv::compile(const ade::Graph&, const std::vector &) const { // ...and this method is here for the same reason! - GAPI_Assert(false); + GAPI_Error("InternalError"); return {}; } @@ -391,7 +391,7 @@ void unbind(Mag& mag, const RcDesc &rc) break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); } } diff --git a/modules/gapi/src/api/gframe.cpp b/modules/gapi/src/api/gframe.cpp index b0830b7a63..6e9347c32d 100644 --- a/modules/gapi/src/api/gframe.cpp +++ b/modules/gapi/src/api/gframe.cpp @@ -45,7 +45,7 @@ std::ostream& operator<<(std::ostream& os, const cv::GFrameDesc &d) { case MediaFormat::BGR: os << "BGR"; break; case MediaFormat::NV12: os << "NV12"; break; case MediaFormat::GRAY: os << "GRAY"; break; - default: GAPI_Assert(false && "Invalid media format"); + default: GAPI_Error("Invalid media format"); } os << ' ' << d.size << ']'; return os; diff --git a/modules/gapi/src/api/gproto.cpp b/modules/gapi/src/api/gproto.cpp index 9b012770ca..43bcfb9c14 100644 --- a/modules/gapi/src/api/gproto.cpp +++ b/modules/gapi/src/api/gproto.cpp @@ -313,7 +313,7 @@ std::ostream& operator<<(std::ostream& os, const cv::GMetaArg &arg) break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); } return os; diff --git a/modules/gapi/src/api/s11n.cpp b/modules/gapi/src/api/s11n.cpp index bd7f46c88a..989cc79118 100644 --- a/modules/gapi/src/api/s11n.cpp +++ b/modules/gapi/src/api/s11n.cpp @@ -98,7 +98,7 @@ cv::GRunArgsP cv::gapi::bind(cv::GRunArgs &out_args) outputs.emplace_back(&(cv::util::get(res_obj))); break; default: - GAPI_Assert(false && "This value type is not supported!"); // ...maybe because of STANDALONE mode. + GAPI_Error("This value type is not supported!"); // ...maybe because of STANDALONE mode. break; } } @@ -114,7 +114,7 @@ cv::GRunArg cv::gapi::bind(cv::GRunArgP &out) { #if !defined(GAPI_STANDALONE) case T::index_of() : - GAPI_Assert(false && "Please implement this!"); + GAPI_Error("Please implement this!"); break; #endif @@ -138,7 +138,7 @@ cv::GRunArg cv::gapi::bind(cv::GRunArgP &out) default: // ...maybe our types were extended - GAPI_Assert(false && "This value type is UNKNOWN!"); + GAPI_Error("This value type is UNKNOWN!"); break; } return cv::GRunArg(); diff --git a/modules/gapi/src/backends/common/gcompoundkernel.cpp b/modules/gapi/src/backends/common/gcompoundkernel.cpp index 20467117b2..d9410f98eb 100644 --- a/modules/gapi/src/backends/common/gcompoundkernel.cpp +++ b/modules/gapi/src/backends/common/gcompoundkernel.cpp @@ -37,7 +37,7 @@ cv::detail::GCompoundContext::GCompoundContext(const cv::GArgs& in_args) // do nothing - as handled in a special way, see gcompoundkernel.hpp for details // same applies to GMatP break; - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } } } diff --git a/modules/gapi/src/backends/common/serialization.cpp b/modules/gapi/src/backends/common/serialization.cpp index 638ce2f025..2a71a782b0 100644 --- a/modules/gapi/src/backends/common/serialization.cpp +++ b/modules/gapi/src/backends/common/serialization.cpp @@ -176,6 +176,13 @@ IIStream& operator>> (IIStream& is, cv::Point2f& pt) { return is >> pt.x >> pt.y; } +IOStream& operator<< (IOStream& os, const cv::Point3f &pt) { + return os << pt.x << pt.y << pt.z; +} +IIStream& operator>> (IIStream& is, cv::Point3f& pt) { + return is >> pt.x >> pt.y >> pt.z; +} + IOStream& operator<< (IOStream& os, const cv::Size &sz) { return os << sz.width << sz.height; } @@ -283,7 +290,7 @@ IOStream& operator<< (IOStream& os, const cv::Mat &m) { case CV_32S: write_mat_data< int32_t>(os, m); break; case CV_32F: write_mat_data< float>(os, m); break; case CV_64F: write_mat_data< double>(os, m); break; - default: GAPI_Assert(false && "Unsupported Mat depth"); + default: GAPI_Error("Unsupported Mat depth"); } return os; } @@ -299,7 +306,7 @@ IIStream& operator>> (IIStream& is, cv::Mat& m) { case CV_32S: read_mat_data< int32_t>(is, m); break; case CV_32F: read_mat_data< float>(is, m); break; case CV_64F: read_mat_data< double>(is, m); break; - default: GAPI_Assert(false && "Unsupported Mat depth"); + default: GAPI_Error("Unsupported Mat depth"); } return is; } @@ -312,10 +319,10 @@ IIStream& operator>> (IIStream& is, cv::gapi::wip::draw::Text &t) { } IOStream& operator<< (IOStream&, const cv::gapi::wip::draw::FText &) { - GAPI_Assert(false && "Serialization: Unsupported << for FText"); + GAPI_Error("Serialization: Unsupported << for FText"); } IIStream& operator>> (IIStream&, cv::gapi::wip::draw::FText &) { - GAPI_Assert(false && "Serialization: Unsupported >> for FText"); + GAPI_Error("Serialization: Unsupported >> for FText"); } IOStream& operator<< (IOStream& os, const cv::gapi::wip::draw::Circle &c) { @@ -391,19 +398,19 @@ IIStream& operator>> (IIStream& is, cv::GArrayDesc &) {return is;} #if !defined(GAPI_STANDALONE) IOStream& operator<< (IOStream& os, const cv::UMat &) { - GAPI_Assert(false && "Serialization: Unsupported << for UMat"); + GAPI_Error("Serialization: Unsupported << for UMat"); return os; } IIStream& operator >> (IIStream& is, cv::UMat &) { - GAPI_Assert(false && "Serialization: Unsupported >> for UMat"); + GAPI_Error("Serialization: Unsupported >> for UMat"); return is; } #endif // !defined(GAPI_STANDALONE) IOStream& operator<< (IOStream& os, const cv::gapi::wip::IStreamSource::Ptr &) { - GAPI_Assert(false && "Serialization: Unsupported << for IStreamSource::Ptr"); + GAPI_Error("Serialization: Unsupported << for IStreamSource::Ptr"); return os; } IIStream& operator >> (IIStream& is, cv::gapi::wip::IStreamSource::Ptr &) @@ -422,7 +429,7 @@ struct putToStream> { static void put(IOStream&, const Ref &) { - GAPI_Assert(false && "Unsupported type for GArray/GOpaque serialization"); + GAPI_Error("Unsupported type for GArray/GOpaque serialization"); } }; @@ -447,7 +454,7 @@ struct getFromStream> { static void get(IIStream&, Ref &, cv::detail::OpaqueKind) { - GAPI_Assert(false && "Unsupported type for GArray/GOpaque deserialization"); + GAPI_Error("Unsupported type for GArray/GOpaque deserialization"); } }; @@ -553,7 +560,7 @@ IOStream& operator<< (IOStream& os, const cv::GArg &arg) { case cv::detail::OpaqueKind::CV_RECT: os << arg.get(); break; case cv::detail::OpaqueKind::CV_SCALAR: os << arg.get(); break; case cv::detail::OpaqueKind::CV_MAT: os << arg.get(); break; - default: GAPI_Assert(false && "GArg: Unsupported (unknown?) opaque value type"); + default: GAPI_Error("GArg: Unsupported (unknown?) opaque value type"); } } return os; @@ -584,12 +591,13 @@ IIStream& operator>> (IIStream& is, cv::GArg &arg) { HANDLE_CASE(STRING , std::string); HANDLE_CASE(POINT , cv::Point); HANDLE_CASE(POINT2F , cv::Point2f); + HANDLE_CASE(POINT3F , cv::Point3f); HANDLE_CASE(SIZE , cv::Size); HANDLE_CASE(RECT , cv::Rect); HANDLE_CASE(SCALAR , cv::Scalar); HANDLE_CASE(MAT , cv::Mat); #undef HANDLE_CASE - default: GAPI_Assert(false && "GArg: Unsupported (unknown?) opaque value type"); + default: GAPI_Error("GArg: Unsupported (unknown?) opaque value type"); } } return is; @@ -657,7 +665,7 @@ struct initCtor> { static void init(cv::gimpl::Data&) { - GAPI_Assert(false && "Unsupported type for GArray/GOpaque deserialization"); + GAPI_Error("Unsupported type for GArray/GOpaque deserialization"); } }; diff --git a/modules/gapi/src/backends/common/serialization.hpp b/modules/gapi/src/backends/common/serialization.hpp index 3ba2e83581..a64805e25c 100644 --- a/modules/gapi/src/backends/common/serialization.hpp +++ b/modules/gapi/src/backends/common/serialization.hpp @@ -18,7 +18,8 @@ #include "opencv2/gapi/render/render_types.hpp" #include "opencv2/gapi/s11n.hpp" // basic interfaces -#if (defined _WIN32 || defined _WIN64) && defined _MSC_VER +#if defined _MSC_VER +#pragma warning(push) #pragma warning(disable: 4702) #endif @@ -232,4 +233,8 @@ GAPI_EXPORTS std::vector vector_of_strings_deserialize(IIStream& is } // namespace gapi } // namespace cv +#if defined _MSC_VER +#pragma warning(pop) +#endif + #endif // OPENCV_GAPI_COMMON_SERIALIZATION_HPP diff --git a/modules/gapi/src/backends/cpu/gcpustereo.cpp b/modules/gapi/src/backends/cpu/gcpustereo.cpp index 2ff50b9d22..eed491da5e 100644 --- a/modules/gapi/src/backends/cpu/gcpustereo.cpp +++ b/modules/gapi/src/backends/cpu/gcpustereo.cpp @@ -63,9 +63,9 @@ GAPI_OCV_KERNEL_ST(GCPUStereo, cv::gapi::calib3d::GStereo, StereoSetup) stereoSetup.stereoBM->compute(left, right, out_mat); break; case cv::gapi::StereoOutputFormat::DISPARITY_FIXED16_11_5: - GAPI_Assert(false && "This case may be supported in future."); + GAPI_Error("This case may be supported in future."); default: - GAPI_Assert(false && "Unknown output format!"); + GAPI_Error("Unknown output format!"); } } }; diff --git a/modules/gapi/src/backends/fluid/gfluidbackend.cpp b/modules/gapi/src/backends/fluid/gfluidbackend.cpp index ed4dda7d49..d24dcd599a 100644 --- a/modules/gapi/src/backends/fluid/gfluidbackend.cpp +++ b/modules/gapi/src/backends/fluid/gfluidbackend.cpp @@ -313,7 +313,7 @@ static int maxLineConsumption(const cv::GFluidKernel::Kind kind, int window, int } } break; case cv::GFluidKernel::Kind::YUV420toRGB: return inPort == 0 ? 2 : 1; break; - default: GAPI_Assert(false); return 0; + default: GAPI_Error("InternalError"); return 0; } } @@ -325,7 +325,7 @@ static int borderSize(const cv::GFluidKernel::Kind kind, int window) // Resize never reads from border pixels case cv::GFluidKernel::Kind::Resize: return 0; break; case cv::GFluidKernel::Kind::YUV420toRGB: return 0; break; - default: GAPI_Assert(false); return 0; + default: GAPI_Error("InternalError"); return 0; } } @@ -685,7 +685,7 @@ void cv::gimpl::GFluidExecutable::initBufferRois(std::vector& readStarts, case 0: roi = produced; break; case 1: case 2: roi = cv::Rect{ produced.x/2, produced.y/2, produced.width/2, produced.height/2 }; break; - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } return roi; }; @@ -699,7 +699,7 @@ void cv::gimpl::GFluidExecutable::initBufferRois(std::vector& readStarts, case GFluidKernel::Kind::Filter: resized = produced; break; case GFluidKernel::Kind::Resize: resized = adjResizeRoi(produced, in_meta.size, meta.size); break; case GFluidKernel::Kind::YUV420toRGB: resized = adj420Roi(produced, m_gm.metadata(in_edge).get().port); break; - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } // All below transformations affect roi of the writer, preserve read start position here @@ -814,7 +814,7 @@ cv::gimpl::FluidGraphInputData cv::gimpl::fluidExtractInputDataFromGraph(const a last_agent++; break; } - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } } @@ -844,7 +844,7 @@ cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph case GFluidKernel::Kind::Filter: agent_ptr.reset(new FluidFilterAgent(g, agent_data.nh)); break; case GFluidKernel::Kind::Resize: agent_ptr.reset(new FluidResizeAgent(g, agent_data.nh)); break; case GFluidKernel::Kind::YUV420toRGB: agent_ptr.reset(new Fluid420toRGBAgent(g, agent_data.nh)); break; - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } std::tie(agent_ptr->in_buffer_ids, agent_ptr->out_buffer_ids) = std::tie(agent_data.in_buffer_ids, agent_data.out_buffer_ids); return agent_ptr; @@ -1388,7 +1388,7 @@ cv::gimpl::GParallelFluidExecutable::GParallelFluidExecutable(const ade::Graph void cv::gimpl::GParallelFluidExecutable::reshape(ade::Graph&, const GCompileArgs& ) { //TODO: implement ? - GAPI_Assert(false && "Not Implemented;"); + GAPI_Error("Not Implemented;"); } void cv::gimpl::GParallelFluidExecutable::run(std::vector &&input_objs, @@ -1474,7 +1474,7 @@ void GFluidBackendImpl::addMetaSensitiveBackendPasses(ade::ExecutionEngineSetupC case NodeKind::EMIT: case NodeKind::SINK: break; // do nothing for Streaming nodes - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } // switch } // for (gim.nodes()) }); diff --git a/modules/gapi/src/backends/fluid/gfluidbuffer.cpp b/modules/gapi/src/backends/fluid/gfluidbuffer.cpp index 9c2ec000ba..2bdbbbecd6 100644 --- a/modules/gapi/src/backends/fluid/gfluidbuffer.cpp +++ b/modules/gapi/src/backends/fluid/gfluidbuffer.cpp @@ -90,7 +90,7 @@ void fillBorderConstant(int borderSize, cv::Scalar borderValue, cv::Mat& mat) case CV_16S: return &fillConstBorderRow< int16_t>; break; case CV_16U: return &fillConstBorderRow; break; case CV_32F: return &fillConstBorderRow< float >; break; - default: GAPI_Assert(false); return &fillConstBorderRow; + default: GAPI_Error("InternalError"); return &fillConstBorderRow; } }; @@ -231,7 +231,7 @@ void fluid::BufferStorageWithBorder::init(int dtype, int border_size, Border bor case cv::BORDER_REFLECT_101: m_borderHandler.reset(new BorderHandlerT(border_size, dtype)); break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); } } diff --git a/modules/gapi/src/backends/ie/giebackend.cpp b/modules/gapi/src/backends/ie/giebackend.cpp index eca07ce9df..ad9de097e8 100644 --- a/modules/gapi/src/backends/ie/giebackend.cpp +++ b/modules/gapi/src/backends/ie/giebackend.cpp @@ -114,7 +114,7 @@ inline IE::Precision toIE(int depth) { case CV_32S: return IE::Precision::I32; case CV_32F: return IE::Precision::FP32; case CV_16F: return IE::Precision::FP16; - default: GAPI_Assert(false && "IE. Unsupported data type"); + default: GAPI_Error("IE. Unsupported data type"); } return IE::Precision::UNSPECIFIED; } @@ -125,7 +125,7 @@ inline int toCV(IE::Precision prec) { case IE::Precision::I32: return CV_32S; case IE::Precision::I64: return CV_32S; case IE::Precision::FP16: return CV_16F; - default: GAPI_Assert(false && "IE. Unsupported data type"); + default: GAPI_Error("IE. Unsupported data type"); } return -1; } @@ -167,7 +167,7 @@ inline IE::Blob::Ptr wrapIE(const cv::Mat &mat, cv::gapi::ie::TraitAs hint) { HANDLE(32S, int); HANDLE(16F, int16_t); #undef HANDLE - default: GAPI_Assert(false && "IE. Unsupported data type"); + default: GAPI_Error("IE. Unsupported data type"); } return IE::Blob::Ptr{}; } @@ -190,13 +190,23 @@ inline IE::Blob::Ptr wrapIE(const cv::MediaFrame::View& view, return wrapIE(gray, cv::gapi::ie::TraitAs::IMAGE); } default: - GAPI_Assert(false && "Unsupported media format for IE backend"); + GAPI_Error("Unsupported media format for IE backend"); } - GAPI_Assert(false); + GAPI_Error("InternalError"); } template inline void copyFromIE(const IE::Blob::Ptr &blob, MatType &mat) { + const auto& desc = blob->getTensorDesc(); + const auto ie_type = toCV(desc.getPrecision()); + if (ie_type != mat.type()) { + std::stringstream ss; + ss << "Failed to copy blob from IE to OCV: " + << "Blobs have different data types " + << "(IE type: " << ie_type + << " vs OCV type: " << mat.type() << ")." << std::endl; + throw std::logic_error(ss.str()); + } switch (blob->getTensorDesc().getPrecision()) { #define HANDLE(E,T) \ case IE::Precision::E: std::copy_n(blob->buffer().as(), \ @@ -215,7 +225,7 @@ inline void copyFromIE(const IE::Blob::Ptr &blob, MatType &mat) { mat.total()); break; } - default: GAPI_Assert(false && "IE. Unsupported data type"); + default: GAPI_Error("IE. Unsupported data type"); } } @@ -365,6 +375,13 @@ struct IEUnit { cv::util::throw_error(std::logic_error("Unsupported ParamDesc::Kind")); } + if (params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import && + !cv::util::holds_alternative(params.output_precision)) { + cv::util::throw_error( + std::logic_error("Setting output precision isn't supported for imported network")); + } + + using namespace cv::gapi::wip::onevpl; if (params.vpl_preproc_device.has_value() && params.vpl_preproc_ctx.has_value()) { using namespace cv::gapi::wip; @@ -375,6 +392,12 @@ struct IEUnit { params.vpl_preproc_ctx.value()); GAPI_LOG_INFO(nullptr, "VPP preproc created successfuly"); } + + if (params.mode == cv::gapi::ie::InferMode::Sync && + params.nireq != 1u) { + throw std::logic_error( + "Failed: cv::gapi::ie::InferMode::Sync works only with nireq equal to 1."); + } } // This method is [supposed to be] called at Island compilation stage @@ -416,7 +439,7 @@ void IEUnit::InputFramesDesc::set_param(const input_name_type &input, if (layout != InferenceEngine::NHWC && layout != InferenceEngine::NCHW) { GAPI_LOG_WARNING(nullptr, "Unsupported layout for VPP preproc: " << layout << ", input name: " << input); - GAPI_Assert(false && "Unsupported layout for VPP preproc"); + GAPI_Error("Unsupported layout for VPP preproc"); } GAPI_Assert(inDims.size() == 4u); ret.size.width = static_cast(inDims[3]); @@ -731,7 +754,7 @@ inline IE::Blob::Ptr extractBlob(IECallContext& ctx, NV12ParamType* blob_params = cv::util::any_cast(&any_blob_params); if (blob_params == nullptr) { - GAPI_Assert(false && "Incorrect type of blobParams:" + GAPI_Error("Incorrect type of blobParams:" "expected std::pair," "with ParamType std::pair>"); @@ -759,7 +782,7 @@ inline IE::Blob::Ptr extractBlob(IECallContext& ctx, default: GAPI_Assert("Unsupported input shape for IE backend"); } - GAPI_Assert(false); + GAPI_Error("InternalError"); } @@ -826,40 +849,130 @@ std::vector cv::gimpl::ie::IECompiled::createInfe return requests; } -class cv::gimpl::ie::RequestPool { +class IInferExecutor { public: - using RunF = std::function; - using CallbackF = std::function; + using Ptr = std::shared_ptr; + using NotifyCallbackF = std::function; + using SetInputDataF = std::function; + using ReadOutputDataF = std::function; // NB: The task is represented by: - // RunF - function which is set blobs and run async inference. - // CallbackF - function which is obtain output blobs and post it to output. + // SetInputDataF - function which set input data. + // ReadOutputDataF - function which read output data. struct Task { - RunF run; - CallbackF callback; + SetInputDataF set_input_data; + ReadOutputDataF read_output_data; }; - explicit RequestPool(std::vector&& requests); + IInferExecutor(IE::InferRequest request, NotifyCallbackF notify) + : m_request(std::move(request)), + m_notify(std::move(notify)) { + }; - void execute(Task&& t); - void waitAll(); + virtual void execute(const Task& task) = 0; + virtual ~IInferExecutor() = default; + +protected: + IE::InferRequest m_request; + NotifyCallbackF m_notify; +}; + +class SyncInferExecutor : public IInferExecutor { + using IInferExecutor::IInferExecutor; + virtual void execute(const IInferExecutor::Task& task) override; +}; + +void SyncInferExecutor::execute(const IInferExecutor::Task& task) { + try { + task.set_input_data(m_request); + m_request.Infer(); + task.read_output_data(m_request, IE::StatusCode::OK); + } catch (...) { + m_notify(); + throw; + } + // NB: Notify pool that executor has finished. + m_notify(); +} + +class AsyncInferExecutor : public IInferExecutor { +public: + using IInferExecutor::IInferExecutor; + virtual void execute(const IInferExecutor::Task& task) override; private: void callback(Task task, - size_t id, IE::InferRequest request, IE::StatusCode code) noexcept; - void setup(); - - QueueClass m_idle_ids; - std::vector m_requests; }; -// RequestPool implementation ////////////////////////////////////////////// -cv::gimpl::ie::RequestPool::RequestPool(std::vector&& requests) - : m_requests(std::move(requests)) { - setup(); +void AsyncInferExecutor::execute(const IInferExecutor::Task& task) { + using namespace std::placeholders; + using callback_t = std::function; + m_request.SetCompletionCallback( + static_cast( + std::bind(&AsyncInferExecutor::callback, this, task, _1, _2))); + try { + task.set_input_data(m_request); + m_request.StartAsync(); + } catch (...) { + m_request.SetCompletionCallback([](){}); + m_notify(); + throw; } +} + +void AsyncInferExecutor::callback(IInferExecutor::Task task, + IE::InferRequest request, + IE::StatusCode code) noexcept { + task.read_output_data(request, code); + request.SetCompletionCallback([](){}); + // NB: Notify pool that executor has finished. + m_notify(); +} + +class cv::gimpl::ie::RequestPool { +public: + + explicit RequestPool(cv::gapi::ie::InferMode mode, + std::vector&& requests); + + IInferExecutor::Ptr getIdleRequest(); + void waitAll(); + +private: + void setup(); + void release(const size_t id); + + QueueClass m_idle_ids; + std::vector m_requests; +}; + +void cv::gimpl::ie::RequestPool::release(const size_t id) { + m_idle_ids.push(id); +} + +// RequestPool implementation ////////////////////////////////////////////// +cv::gimpl::ie::RequestPool::RequestPool(cv::gapi::ie::InferMode mode, + std::vector&& requests) { + for (size_t i = 0; i < requests.size(); ++i) { + IInferExecutor::Ptr iexec = nullptr; + switch (mode) { + case cv::gapi::ie::InferMode::Async: + iexec = std::make_shared(std::move(requests[i]), + std::bind(&RequestPool::release, this, i)); + break; + case cv::gapi::ie::InferMode::Sync: + iexec = std::make_shared(std::move(requests[i]), + std::bind(&RequestPool::release, this, i)); + break; + default: + GAPI_Error("Unsupported cv::gapi::ie::InferMode"); + } + m_requests.emplace_back(std::move(iexec)); + } + setup(); +} void cv::gimpl::ie::RequestPool::setup() { for (size_t i = 0; i < m_requests.size(); ++i) { @@ -867,40 +980,10 @@ void cv::gimpl::ie::RequestPool::setup() { } } -void cv::gimpl::ie::RequestPool::execute(cv::gimpl::ie::RequestPool::Task&& t) { +IInferExecutor::Ptr cv::gimpl::ie::RequestPool::getIdleRequest() { size_t id = 0u; m_idle_ids.pop(id); - - auto& request = m_requests[id]; - - using namespace std::placeholders; - using callback_t = std::function; - request.SetCompletionCallback( - static_cast( - std::bind(&cv::gimpl::ie::RequestPool::callback, this, - t, id, _1, _2))); - // NB: InferRequest is already marked as busy - // in case of exception need to return it back to the idle. - try { - t.run(request); - } catch (...) { - request.SetCompletionCallback([](){}); - m_idle_ids.push(id); - throw; - } -} - -void cv::gimpl::ie::RequestPool::callback(cv::gimpl::ie::RequestPool::Task task, - size_t id, - IE::InferRequest request, - IE::StatusCode code) noexcept { - // NB: Inference is over. - // 1. Run callback - // 2. Destroy callback to free resources. - // 3. Mark InferRequest as idle. - task.callback(request, code); - request.SetCompletionCallback([](){}); - m_idle_ids.push(id); + return m_requests[id]; } // NB: Not thread-safe. @@ -927,7 +1010,7 @@ cv::gimpl::ie::GIEExecutable::GIEExecutable(const ade::Graph &g, if (this_nh == nullptr) { this_nh = nh; this_iec = iem.metadata(this_nh).get().compile(); - m_reqPool.reset(new RequestPool(this_iec.createInferRequests())); + m_reqPool.reset(new RequestPool(this_iec.params.mode, this_iec.createInferRequests())); } else util::throw_error(std::logic_error("Multi-node inference is not supported!")); @@ -1061,7 +1144,7 @@ static void configureInputReshapeByImage(const IE::InputInfo::Ptr& ii, auto input_dims = ii->getTensorDesc().getDims(); const auto size = input_dims.size(); if (size <= 1) { - GAPI_Assert(false && "Unsupported number of dimensions for reshape by image"); + GAPI_Error("Unsupported number of dimensions for reshape by image"); } input_dims.at(size - 2) = static_cast(image_sz.height); input_dims.at(size - 1) = static_cast(image_sz.width); @@ -1090,7 +1173,7 @@ static void configureInputInfo(const IE::InputInfo::Ptr& ii, const cv::GMetaArg // NB: Do nothing break; default: - GAPI_Assert(false && "Unsupported media format for IE backend"); + GAPI_Error("Unsupported media format for IE backend"); } ii->setPrecision(toIE(CV_8U)); break; @@ -1122,6 +1205,28 @@ static IE::PreProcessInfo configurePreProcInfo(const IE::InputInfo::CPtr& ii, return info; } +using namespace cv::gapi::ie::detail; +static void configureOutputPrecision(const IE::OutputsDataMap &outputs_info, + const ParamDesc::PrecisionVariantT &output_precision) { + cv::util::visit(cv::util::overload_lambdas( + [&outputs_info](ParamDesc::PrecisionT cvdepth) { + auto precision = toIE(cvdepth); + for (auto it : outputs_info) { + it.second->setPrecision(precision); + } + }, + [&outputs_info](const ParamDesc::PrecisionMapT& precision_map) { + for (auto it : precision_map) { + outputs_info.at(it.first)->setPrecision(toIE(it.second)); + } + }, + [&outputs_info](cv::util::monostate) { + // Do nothing. + } + ), output_precision + ); +} + // NB: This is a callback used by async infer // to post outputs blobs (cv::GMat's). static void PostOutputs(InferenceEngine::InferRequest &request, @@ -1241,7 +1346,7 @@ struct Infer: public cv::detail::KernelTag { GAPI_Assert(uu.params.input_names.size() == in_metas.size() && "Known input layers count doesn't match input meta count"); - // NB: Configuring input precision and network reshape must be done + // NB: Configuring input/output precision and network reshape must be done // only in the loadNetwork case. using namespace cv::gapi::ie::detail; if (uu.params.kind == ParamDesc::Kind::Load) { @@ -1275,6 +1380,7 @@ struct Infer: public cv::detail::KernelTag { if (!input_reshape_table.empty()) { const_cast(&uu.net)->reshape(input_reshape_table); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == ParamDesc::Kind::Import); auto inputs = uu.this_network.GetInputsInfo(); @@ -1316,8 +1422,8 @@ struct Infer: public cv::detail::KernelTag { static void run(std::shared_ptr ctx, cv::gimpl::ie::RequestPool &reqPool) { using namespace std::placeholders; - reqPool.execute( - cv::gimpl::ie::RequestPool::Task { + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { [ctx](InferenceEngine::InferRequest &req) { // non-generic version for now: // - assumes all inputs/outputs are always Mats @@ -1335,9 +1441,6 @@ struct Infer: public cv::detail::KernelTag { cv::util::optional{}); setBlob(req, layer_name, this_blob, *ctx); } - // FIXME: Should it be done by kernel ? - // What about to do that in RequestPool ? - req.StartAsync(); }, std::bind(PostOutputs, _1, _2, ctx) } @@ -1393,6 +1496,7 @@ struct InferROI: public cv::detail::KernelTag { const_cast(uu.net_input_params) .set_param(input_name, ii->getTensorDesc()); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import); auto inputs = uu.this_network.GetInputsInfo(); @@ -1429,8 +1533,8 @@ struct InferROI: public cv::detail::KernelTag { static void run(std::shared_ptr ctx, cv::gimpl::ie::RequestPool &reqPool) { using namespace std::placeholders; - reqPool.execute( - cv::gimpl::ie::RequestPool::Task { + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { [ctx](InferenceEngine::InferRequest &req) { GAPI_Assert(ctx->uu.params.num_in == 1); auto&& this_roi = ctx->inArg(0).rref(); @@ -1455,9 +1559,6 @@ struct InferROI: public cv::detail::KernelTag { *(ctx->uu.params.input_names.begin()), this_blob, *ctx); } - // FIXME: Should it be done by kernel ? - // What about to do that in RequestPool ? - req.StartAsync(); }, std::bind(PostOutputs, _1, _2, ctx) } @@ -1513,6 +1614,7 @@ struct InferList: public cv::detail::KernelTag { if (!input_reshape_table.empty()) { const_cast(&uu.net)->reshape(input_reshape_table); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import); std::size_t idx = 1u; @@ -1571,11 +1673,10 @@ struct InferList: public cv::detail::KernelTag { for (auto&& it : ade::util::indexed(in_roi_vec)) { auto pos = ade::util::index(it); const auto& rc = ade::util::value(it); - reqPool.execute( - cv::gimpl::ie::RequestPool::Task { + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { [ctx, rc, this_blob](InferenceEngine::InferRequest &req) { setROIBlob(req, ctx->uu.params.input_names[0u], this_blob, rc, *ctx); - req.StartAsync(); }, std::bind(callback, std::placeholders::_1, std::placeholders::_2, pos) } @@ -1667,6 +1768,7 @@ struct InferList2: public cv::detail::KernelTag { if (!input_reshape_table.empty()) { const_cast(&uu.net)->reshape(input_reshape_table); } + configureOutputPrecision(uu.net.getOutputsInfo(), uu.params.output_precision); } else { GAPI_Assert(uu.params.kind == cv::gapi::ie::detail::ParamDesc::Kind::Import); auto inputs = uu.this_network.GetInputsInfo(); @@ -1727,8 +1829,8 @@ struct InferList2: public cv::detail::KernelTag { PostOutputsList callback(list_size, ctx, std::move(cached_dims)); for (const auto &list_idx : ade::util::iota(list_size)) { - reqPool.execute( - cv::gimpl::ie::RequestPool::Task { + reqPool.getIdleRequest()->execute( + IInferExecutor::Task { [ctx, list_idx, list_size, blob_0](InferenceEngine::InferRequest &req) { for (auto in_idx : ade::util::iota(ctx->uu.params.num_in)) { const auto &this_vec = ctx->inArg(in_idx+1u); @@ -1748,7 +1850,6 @@ struct InferList2: public cv::detail::KernelTag { "Only Rect and Mat types are supported for infer list 2!"); } } - req.StartAsync(); }, std::bind(callback, std::placeholders::_1, std::placeholders::_2, list_idx) } // task diff --git a/modules/gapi/src/backends/ie/giebackend.hpp b/modules/gapi/src/backends/ie/giebackend.hpp index fbfeeccd61..c7d938878d 100644 --- a/modules/gapi/src/backends/ie/giebackend.hpp +++ b/modules/gapi/src/backends/ie/giebackend.hpp @@ -62,12 +62,12 @@ public: virtual inline bool canReshape() const override { return false; } virtual inline void reshape(ade::Graph&, const GCompileArgs&) override { - GAPI_Assert(false); // Not implemented yet + GAPI_Error("InternalError"); // Not implemented yet } virtual void run(std::vector &&, std::vector &&) override { - GAPI_Assert(false && "Not implemented"); + GAPI_Error("Not implemented"); } virtual void run(GIslandExecutable::IInput &in, diff --git a/modules/gapi/src/backends/oak/goakbackend.cpp b/modules/gapi/src/backends/oak/goakbackend.cpp index e159160ba9..bb99c21fcf 100644 --- a/modules/gapi/src/backends/oak/goakbackend.cpp +++ b/modules/gapi/src/backends/oak/goakbackend.cpp @@ -39,7 +39,7 @@ class GOAKExecutable final: public GIslandExecutable { friend class OAKKernelParams; virtual void run(std::vector&&, std::vector&&) override { - GAPI_Assert(false && "Not implemented"); + GAPI_Error("Not implemented"); } virtual void run(GIslandExecutable::IInput &in, @@ -121,7 +121,7 @@ public: // FIXME: could it reshape? virtual bool canReshape() const override { return false; } virtual void reshape(ade::Graph&, const GCompileArgs&) override { - GAPI_Assert(false && "GOAKExecutable::reshape() is not supported"); + GAPI_Error("GOAKExecutable::reshape() is not supported"); } virtual void handleNewStream() override; @@ -391,7 +391,7 @@ void cv::gimpl::GOAKExecutable::linkCopy(ade::NodeHandle handle) { for (const auto& copy_next_op : copy_out.front().get()->outNodes()) { const auto& op = m_gm.metadata(copy_next_op).get(); if (op.k.name == "org.opencv.oak.copy") { - GAPI_Assert(false && "Back-to-back Copy operations are not supported in graph"); + GAPI_Error("Back-to-back Copy operations are not supported in graph"); } } @@ -701,14 +701,14 @@ cv::gimpl::GOAKExecutable::GOAKExecutable(const ade::Graph& g, [](ExtractTypeHelper::InputPtr ptr) { return ptr == nullptr; })) { - GAPI_Assert(false && "DAI input are not set"); + GAPI_Error("DAI input are not set"); } if (std::any_of(node_info.outputs.cbegin(), node_info.outputs.cend(), [](ExtractTypeHelper::OutputPtr ptr) { return ptr == nullptr; })) { - GAPI_Assert(false && "DAI outputs are not set"); + GAPI_Error("DAI outputs are not set"); } } } @@ -907,7 +907,7 @@ void cv::gimpl::GOAKExecutable::run(GIslandExecutable::IInput &in, } // FIXME: Add support for remaining types default: - GAPI_Assert(false && "Unsupported type in OAK backend"); + GAPI_Error("Unsupported type in OAK backend"); } out.meta(out_arg, meta); @@ -1080,7 +1080,7 @@ class GOAKBackendImpl final : public cv::gapi::GBackend::Priv { // NB: how could we have non-OAK source in streaming mode, then OAK backend in // streaming mode but without camera input? if (!gm.metadata().contains()) { - GAPI_Assert(false && "OAK backend only supports Streaming mode for now"); + GAPI_Error("OAK backend only supports Streaming mode for now"); } return EPtr{new cv::gimpl::GOAKExecutable(graph, args, nodes, ins_data, outs_data)}; } @@ -1118,14 +1118,11 @@ namespace gapi { namespace oak { cv::gapi::GKernelPackage kernels() { - GAPI_Assert(false && "Built without OAK support"); - return {}; + GAPI_Error("Built without OAK support"); } cv::gapi::GBackend backend() { - GAPI_Assert(false && "Built without OAK support"); - static cv::gapi::GBackend this_backend(nullptr); - return this_backend; + GAPI_Error("Built without OAK support"); } } // namespace oak diff --git a/modules/gapi/src/backends/ocl/goclbackend.cpp b/modules/gapi/src/backends/ocl/goclbackend.cpp index dba2b27b59..9c6d7154e4 100644 --- a/modules/gapi/src/backends/ocl/goclbackend.cpp +++ b/modules/gapi/src/backends/ocl/goclbackend.cpp @@ -114,7 +114,8 @@ cv::GArg cv::gimpl::GOCLExecutable::packArg(const GArg &arg) GAPI_Assert( arg.kind != cv::detail::ArgKind::GMAT && arg.kind != cv::detail::ArgKind::GSCALAR && arg.kind != cv::detail::ArgKind::GARRAY - && arg.kind != cv::detail::ArgKind::GOPAQUE); + && arg.kind != cv::detail::ArgKind::GOPAQUE + && arg.kind != cv::detail::ArgKind::GFRAME); if (arg.kind != cv::detail::ArgKind::GOBJREF) { @@ -136,6 +137,7 @@ cv::GArg cv::gimpl::GOCLExecutable::packArg(const GArg &arg) // Note: .at() is intentional for GOpaque as object MUST be already there // (and constructed by either bindIn/Out or resetInternal) case GShape::GOPAQUE: return GArg(m_res.slot().at(ref.id)); + case GShape::GFRAME: return GArg(m_res.slot().at(ref.id)); default: util::throw_error(std::logic_error("Unsupported GShape type")); break; diff --git a/modules/gapi/src/backends/ocl/goclcore.cpp b/modules/gapi/src/backends/ocl/goclcore.cpp index f3c5aa32bc..19fa54a40a 100644 --- a/modules/gapi/src/backends/ocl/goclcore.cpp +++ b/modules/gapi/src/backends/ocl/goclcore.cpp @@ -6,11 +6,32 @@ #include "precomp.hpp" +#include "logger.hpp" #include #include +#include + #include "backends/ocl/goclcore.hpp" +#ifdef HAVE_DIRECTX +#ifdef HAVE_D3D11 +#pragma comment(lib,"d3d11.lib") + +// get rid of generate macro max/min/etc from DX side +#define D3D11_NO_HELPERS +#define NOMINMAX +#include +#pragma comment(lib, "dxgi") +#undef NOMINMAX +#undef D3D11_NO_HELPERS +#include +#endif // HAVE_D3D11 +#endif // HAVE_DIRECTX + +#include +#include "streaming/onevpl/accelerators/surface/dx11_frame_adapter.hpp" + GAPI_OCL_KERNEL(GOCLAdd, cv::gapi::core::GAdd) { static void run(const cv::UMat& a, const cv::UMat& b, int dtype, cv::UMat& out) @@ -523,6 +544,79 @@ GAPI_OCL_KERNEL(GOCLTranspose, cv::gapi::core::GTranspose) } }; +GAPI_OCL_KERNEL(GOCLBGR, cv::gapi::streaming::GBGR) +{ + static void run(const cv::MediaFrame& in, cv::UMat& out) + { + cv::util::suppress_unused_warning(in); + cv::util::suppress_unused_warning(out); +#ifdef HAVE_DIRECTX +#ifdef HAVE_D3D11 +#ifdef HAVE_ONEVPL + auto d = in.desc(); + if (d.fmt != cv::MediaFormat::NV12) + { + GAPI_LOG_FATAL(nullptr, "Unsupported format provided: " << static_cast(d.fmt) << + ". Expected cv::MediaFormat::NV12."); + cv::util::throw_error(std::logic_error("Unsupported MediaFrame format provided")); + } + + // FIXME: consider a better solution. + // Current approach cannot be easily extended for other adapters (getHandle). + auto adapterPtr = in.get(); + if (adapterPtr == nullptr) + { + GAPI_LOG_FATAL(nullptr, "Unsupported adapter type. Only VPLMediaFrameDX11Adapter is supported"); + cv::util::throw_error(std::logic_error("Unsupported adapter type. Only VPLMediaFrameDX11Adapter is supported")); + } + + auto params = adapterPtr->getHandle(); + auto handle = cv::util::any_cast(params); + ID3D11Texture2D* texture = reinterpret_cast(handle.first); + if (texture == nullptr) + { + GAPI_LOG_FATAL(nullptr, "mfxHDLPair contains ID3D11Texture2D that is nullptr. Handle address" << + reinterpret_cast(handle.first)); + cv::util::throw_error(std::logic_error("mfxHDLPair contains ID3D11Texture2D that is nullptr")); + } + + // FIXME: Assuming here that we only have 1 device + // TODO: Textures are reusable, so to improve the peroformance here + // consider creating a hash map texture <-> device/ctx + static thread_local ID3D11Device* pD3D11Device = nullptr; + if (pD3D11Device == nullptr) + { + texture->GetDevice(&pD3D11Device); + } + if (pD3D11Device == nullptr) + { + GAPI_LOG_FATAL(nullptr, "D3D11Texture2D::GetDevice returns pD3D11Device that is nullptr"); + cv::util::throw_error(std::logic_error("D3D11Texture2D::GetDevice returns pD3D11Device that is nullptr")); + } + + // FIXME: assuming here that the context is always the same + // TODO: Textures are reusable, so to improve the peroformance here + // consider creating a hash map texture <-> device/ctx + static thread_local cv::ocl::Context ctx = cv::directx::ocl::initializeContextFromD3D11Device(pD3D11Device); + if (ctx.ptr() == nullptr) + { + GAPI_LOG_FATAL(nullptr, "initializeContextFromD3D11Device returned null context"); + cv::util::throw_error(std::logic_error("initializeContextFromD3D11Device returned null context")); + } + + cv::directx::convertFromD3D11Texture2D(texture, out); +#else + GAPI_LOG_FATAL(nullptr, "HAVE_ONEVPL is not set. Please, check your cmake flags"); + cv::util::throw_error(std::logic_error("HAVE_ONEVPL is not set. Please, check your cmake flags")); +#endif // HAVE_ONEVPL +#else + GAPI_LOG_FATAL(nullptr, "HAVE_D3D11 or HAVE_DIRECTX is not set. Please, check your cmake flags"); + cv::util::throw_error(std::logic_error("HAVE_D3D11 or HAVE_DIRECTX is not set. Please, check your cmake flags")); +#endif // HAVE_D3D11 +#endif // HAVE_DIRECTX + } +}; + cv::GKernelPackage cv::gapi::core::ocl::kernels() { static auto pkg = cv::gapi::kernels @@ -587,6 +681,7 @@ cv::GKernelPackage cv::gapi::core::ocl::kernels() , GOCLLUT , GOCLConvertTo , GOCLTranspose + , GOCLBGR >(); return pkg; } diff --git a/modules/gapi/src/backends/onnx/bindings_onnx.cpp b/modules/gapi/src/backends/onnx/bindings_onnx.cpp new file mode 100644 index 0000000000..c43b86e0c8 --- /dev/null +++ b/modules/gapi/src/backends/onnx/bindings_onnx.cpp @@ -0,0 +1,24 @@ +// 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 + +cv::gapi::onnx::PyParams::PyParams(const std::string& tag, + const std::string& model_path) + : m_priv(std::make_shared>(tag, model_path)) {} + +cv::gapi::GBackend cv::gapi::onnx::PyParams::backend() const { + return m_priv->backend(); +} + +std::string cv::gapi::onnx::PyParams::tag() const { return m_priv->tag(); } + +cv::util::any cv::gapi::onnx::PyParams::params() const { + return m_priv->params(); +} + +cv::gapi::onnx::PyParams cv::gapi::onnx::params( + const std::string& tag, const std::string& model_path) { + return {tag, model_path}; +} diff --git a/modules/gapi/src/backends/onnx/gonnxbackend.cpp b/modules/gapi/src/backends/onnx/gonnxbackend.cpp index af1f7f8948..b78ba6d05e 100644 --- a/modules/gapi/src/backends/onnx/gonnxbackend.cpp +++ b/modules/gapi/src/backends/onnx/gonnxbackend.cpp @@ -171,7 +171,7 @@ inline int toCV(ONNXTensorElementDataType prec) { case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: return CV_32F; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: return CV_32S; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: return CV_32S; - default: GAPI_Assert(false && "ONNX. Unsupported data type"); + default: GAPI_Error("ONNX. Unsupported data type"); } return -1; } @@ -207,7 +207,7 @@ inline void copyFromONNX(Ort::Value &v, cv::Mat& mat) { mat.total()); break; } - default: GAPI_Assert(false && "ONNX. Unsupported data type"); + default: GAPI_Error("ONNX. Unsupported data type"); } } @@ -233,7 +233,7 @@ inline void preprocess(const cv::Mat& src, "32F tensor dimensions should match with all non-dynamic NN input dimensions"); } } else { - GAPI_Assert(false && "32F tensor size should match with NN input"); + GAPI_Error("32F tensor size should match with NN input"); } dst = src; @@ -338,7 +338,7 @@ void preprocess(const cv::MediaFrame::View& view, break; } default: - GAPI_Assert(false && "Unsupported media format for ONNX backend"); + GAPI_Error("Unsupported media format for ONNX backend"); } } @@ -367,7 +367,7 @@ inline Ort::Value createTensor(const Ort::MemoryInfo& memory_info, case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: return createTensor(memory_info, tensor_params, data); default: - GAPI_Assert(false && "ONNX. Unsupported data type"); + GAPI_Error("ONNX. Unsupported data type"); } return Ort::Value{nullptr}; } @@ -735,7 +735,8 @@ void ONNXCompiled::extractMat(ONNXCallContext &ctx, const size_t in_idx, Views& } } -void ONNXCompiled::setOutput(int i, cv::Mat &m) { +void ONNXCompiled::setOutput(int i, cv::Mat &m) +{ // FIXME: No need in double-indexing? out_data[i] = m; } @@ -857,7 +858,7 @@ static void checkInputMeta(const cv::GMetaArg mm) { case cv::MediaFormat::NV12: break; case cv::MediaFormat::BGR: break; default: - GAPI_Assert(false && "Unsupported media format for ONNX backend"); + GAPI_Error("Unsupported media format for ONNX backend"); } break; } break; default: @@ -1100,7 +1101,7 @@ struct InferList2: public cv::detail::KernelTag { const auto &vec = this_vec.rref(); uu.oc->setInput(in_idx, vec[list_idx]); } else { - GAPI_Assert(false && "Only Rect and Mat types are supported for infer list 2!"); + GAPI_Error("Only Rect and Mat types are supported for infer list 2!"); } // }}} (Prepare input) } // }}} (For every input of the net) @@ -1133,9 +1134,34 @@ namespace { // FIXME: Introduce a DNNBackend interface which'd specify // the framework for this??? GONNXModel gm(gr); - const auto &np = gm.metadata(nh).get(); - const auto &pp = cv::util::any_cast(np.opaque); + auto &np = gm.metadata(nh).get(); + auto &pp = cv::util::any_cast(np.opaque); const auto &ki = cv::util::any_cast(ii.opaque); + + GModel::Graph model(gr); + auto& op = model.metadata(nh).get(); + if (pp.is_generic) { + auto& info = cv::util::any_cast(op.params); + + for (const auto& a : info.in_names) + { + pp.input_names.push_back(a); + } + // Adding const input is necessary because the definition of input_names + // includes const input. + for (const auto& a : pp.const_inputs) + { + pp.input_names.push_back(a.first); + } + pp.num_in = info.in_names.size(); + + for (const auto& a : info.out_names) + { + pp.output_names.push_back(a); + } + pp.num_out = info.out_names.size(); + } + gm.metadata(nh).set(ONNXUnit{pp}); gm.metadata(nh).set(ONNXCallable{ki.run}); gm.metadata(nh).set(CustomMetaFunction{ki.customMetaFunc}); diff --git a/modules/gapi/src/backends/onnx/gonnxbackend.hpp b/modules/gapi/src/backends/onnx/gonnxbackend.hpp index a3cc897030..8c67df1e1e 100644 --- a/modules/gapi/src/backends/onnx/gonnxbackend.hpp +++ b/modules/gapi/src/backends/onnx/gonnxbackend.hpp @@ -43,7 +43,7 @@ public: virtual inline bool canReshape() const override { return false; } virtual inline void reshape(ade::Graph&, const GCompileArgs&) override { - GAPI_Assert(false); // Not implemented yet + GAPI_Error("InternalError"); // Not implemented yet } virtual void run(std::vector &&input_objs, diff --git a/modules/gapi/src/backends/python/gpythonbackend.cpp b/modules/gapi/src/backends/python/gpythonbackend.cpp index 4361bab75d..54fbf7decd 100644 --- a/modules/gapi/src/backends/python/gpythonbackend.cpp +++ b/modules/gapi/src/backends/python/gpythonbackend.cpp @@ -6,26 +6,25 @@ #include // zip_range, indexed +#include "compiler/gmodel.hpp" +#include #include // throw_error #include #include "api/gbackend_priv.hpp" #include "backends/common/gbackend.hpp" -cv::gapi::python::GPythonKernel::GPythonKernel(cv::gapi::python::Impl run) - : m_run(run) +cv::gapi::python::GPythonKernel::GPythonKernel(cv::gapi::python::Impl runf, + cv::gapi::python::Setup setupf) + : run(runf), setup(setupf), is_stateful(setup != nullptr) { } -cv::GRunArgs cv::gapi::python::GPythonKernel::operator()(const cv::gapi::python::GPythonContext& ctx) -{ - return m_run(ctx); -} - cv::gapi::python::GPythonFunctor::GPythonFunctor(const char* id, - const cv::gapi::python::GPythonFunctor::Meta &meta, - const cv::gapi::python::Impl& impl) - : gapi::GFunctor(id), impl_{GPythonKernel{impl}, meta} + const cv::gapi::python::GPythonFunctor::Meta& meta, + const cv::gapi::python::Impl& impl, + const cv::gapi::python::Setup& setup) + : gapi::GFunctor(id), impl_{GPythonKernel{impl, setup}, meta} { } @@ -68,6 +67,7 @@ class GPythonExecutable final: public cv::gimpl::GIslandExecutable virtual cv::RMat allocate(const cv::GMatDesc&) const override { return {}; } virtual bool canReshape() const override { return true; } + virtual void handleNewStream() override; virtual void reshape(ade::Graph&, const cv::GCompileArgs&) override { // Do nothing here } @@ -80,6 +80,7 @@ public: cv::gimpl::GModel::ConstGraph m_gm; cv::gapi::python::GPythonKernel m_kernel; ade::NodeHandle m_op; + cv::GArg m_node_state; cv::GTypesInfo m_out_info; cv::GMetaArgs m_in_metas; @@ -149,10 +150,19 @@ static void writeBack(cv::GRunArg& arg, cv::GRunArgP& out) break; } default: - GAPI_Assert(false && "Unsupported output type"); + GAPI_Error("Unsupported output type"); } } +void GPythonExecutable::handleNewStream() +{ + if (!m_kernel.is_stateful) + return; + + m_node_state = m_kernel.setup(cv::gimpl::GModel::collectInputMeta(m_gm, m_op), + m_gm.metadata(m_op).get().args); +} + void GPythonExecutable::run(std::vector &&input_objs, std::vector &&output_objs) { @@ -165,9 +175,15 @@ void GPythonExecutable::run(std::vector &&input_objs, std::back_inserter(inputs), std::bind(&packArg, std::ref(m_res), _1)); + cv::gapi::python::GPythonContext ctx{inputs, m_in_metas, m_out_info, /*state*/{}}; - cv::gapi::python::GPythonContext ctx{inputs, m_in_metas, m_out_info}; - auto outs = m_kernel(ctx); + // NB: For stateful kernel add state to its execution context + if (m_kernel.is_stateful) + { + ctx.m_state = cv::optional(m_node_state); + } + + auto outs = m_kernel.run(ctx); for (auto&& it : ade::util::zip(outs, output_objs)) { @@ -225,6 +241,12 @@ GPythonExecutable::GPythonExecutable(const ade::Graph& g, m_op = *it; m_kernel = cag.metadata(m_op).get().kernel; + // If kernel is stateful then prepare storage for its state. + if (m_kernel.is_stateful) + { + m_node_state = cv::GArg{ }; + } + // Ensure this the only op in the graph if (std::any_of(it+1, nodes.end(), is_op)) { diff --git a/modules/gapi/src/backends/streaming/gstreamingbackend.cpp b/modules/gapi/src/backends/streaming/gstreamingbackend.cpp index 69b5f6c72b..ae7125f2e5 100644 --- a/modules/gapi/src/backends/streaming/gstreamingbackend.cpp +++ b/modules/gapi/src/backends/streaming/gstreamingbackend.cpp @@ -42,7 +42,7 @@ class GStreamingIntrinExecutable final: public cv::gimpl::GIslandExecutable { virtual void run(std::vector &&, std::vector &&) override { - GAPI_Assert(false && "Not implemented"); + GAPI_Error("Not implemented"); } virtual void run(GIslandExecutable::IInput &in, @@ -188,7 +188,7 @@ void Copy::Actor::run(cv::gimpl::GIslandExecutable::IInput &in, break; // FIXME: Add support for remaining types default: - GAPI_Assert(false && "Copy: unsupported data type"); + GAPI_Error("Copy: unsupported data type"); } out.meta(out_arg, in_arg.meta); out.post(std::move(out_arg)); diff --git a/modules/gapi/src/compiler/gcompiled.cpp b/modules/gapi/src/compiler/gcompiled.cpp index 263878ce0d..df4411e832 100644 --- a/modules/gapi/src/compiler/gcompiled.cpp +++ b/modules/gapi/src/compiler/gcompiled.cpp @@ -14,11 +14,12 @@ #include "compiler/gcompiled_priv.hpp" #include "backends/common/gbackend.hpp" +#include "executor/gexecutor.hpp" // GCompiled private implementation //////////////////////////////////////////// void cv::GCompiled::Priv::setup(const GMetaArgs &_metaArgs, const GMetaArgs &_outMetas, - std::unique_ptr &&_pE) + std::unique_ptr &&_pE) { m_metas = _metaArgs; m_outMetas = _outMetas; diff --git a/modules/gapi/src/compiler/gcompiled_priv.hpp b/modules/gapi/src/compiler/gcompiled_priv.hpp index f21bfc80bc..3f873aba23 100644 --- a/modules/gapi/src/compiler/gcompiled_priv.hpp +++ b/modules/gapi/src/compiler/gcompiled_priv.hpp @@ -12,7 +12,7 @@ #include "opencv2/gapi/util/optional.hpp" #include "compiler/gmodel.hpp" -#include "executor/gexecutor.hpp" +#include "executor/gabstractexecutor.hpp" // NB: BTW, GCompiled is the only "public API" class which // private part (implementation) is hosted in the "compiler/" module. @@ -36,14 +36,14 @@ class GAPI_EXPORTS GCompiled::Priv // If we want to go autonomous, we might to do something with this. GMetaArgs m_metas; // passed by user GMetaArgs m_outMetas; // inferred by compiler - std::unique_ptr m_exec; + std::unique_ptr m_exec; void checkArgs(const cv::gimpl::GRuntimeArgs &args) const; public: void setup(const GMetaArgs &metaArgs, const GMetaArgs &outMetas, - std::unique_ptr &&pE); + std::unique_ptr &&pE); bool isEmpty() const; bool canReshape() const; diff --git a/modules/gapi/src/compiler/gcompiler.cpp b/modules/gapi/src/compiler/gcompiler.cpp index bcf91f7dcd..526b2746dc 100644 --- a/modules/gapi/src/compiler/gcompiler.cpp +++ b/modules/gapi/src/compiler/gcompiler.cpp @@ -338,7 +338,7 @@ void cv::gimpl::GCompiler::validateInputMeta() return util::holds_alternative(meta); default: - GAPI_Assert(false); + GAPI_Error("InternalError"); } return false; // should never happen }; @@ -485,7 +485,7 @@ cv::GStreamingCompiled cv::gimpl::GCompiler::produceStreamingCompiled(GPtr &&pg) // Otherwise, set it up with executor object only compiled.priv().setup(std::move(pE)); } - else GAPI_Assert(false && "Impossible happened -- please report a bug"); + else GAPI_Error("Impossible happened -- please report a bug"); return compiled; } diff --git a/modules/gapi/src/compiler/gislandmodel.cpp b/modules/gapi/src/compiler/gislandmodel.cpp index 0567a90e3a..736e4f7170 100644 --- a/modules/gapi/src/compiler/gislandmodel.cpp +++ b/modules/gapi/src/compiler/gislandmodel.cpp @@ -120,7 +120,7 @@ ade::NodeHandle GIsland::producer(const ade::Graph &g, } // Consistency: A GIsland requested for producer() of slot_nh should // always had the appropriate GModel node handle in its m_out_ops vector. - GAPI_Assert(false && "Broken GIslandModel ?."); + GAPI_Error("Broken GIslandModel ?."); } std::string GIsland::name() const @@ -164,7 +164,7 @@ void GIslandModel::generateInitial(GIslandModel::Graph &g, { case NodeType::OP: all_operations.insert(src_nh); break; case NodeType::DATA: data_to_slot[src_nh] = mkSlotNode(g, src_nh); break; - default: GAPI_Assert(false); break; + default: GAPI_Error("InternalError"); break; } } // for (src_g.nodes) diff --git a/modules/gapi/src/compiler/gislandmodel.hpp b/modules/gapi/src/compiler/gislandmodel.hpp index 565b3c4f21..3a1a8d5ab9 100644 --- a/modules/gapi/src/compiler/gislandmodel.hpp +++ b/modules/gapi/src/compiler/gislandmodel.hpp @@ -122,7 +122,7 @@ public: virtual bool canReshape() const = 0; virtual void reshape(ade::Graph& g, const GCompileArgs& args) = 0; virtual bool allocatesOutputs() const { return false; } - virtual cv::RMat allocate(const cv::GMatDesc&) const { GAPI_Assert(false && "should never be called"); } + virtual cv::RMat allocate(const cv::GMatDesc&) const { GAPI_Error("should never be called"); } // This method is called when the GStreamingCompiled gets a new // input source to process. Normally this method is called once diff --git a/modules/gapi/src/compiler/gmodelbuilder.cpp b/modules/gapi/src/compiler/gmodelbuilder.cpp index f0e4917d7a..aed3428693 100644 --- a/modules/gapi/src/compiler/gmodelbuilder.cpp +++ b/modules/gapi/src/compiler/gmodelbuilder.cpp @@ -162,7 +162,7 @@ cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins, default: // Unsupported node shape - GAPI_Assert(false); + GAPI_Error("InternalError"); break; } } diff --git a/modules/gapi/src/compiler/gstreaming.cpp b/modules/gapi/src/compiler/gstreaming.cpp index e45e770427..c0bc0b1d1b 100644 --- a/modules/gapi/src/compiler/gstreaming.cpp +++ b/modules/gapi/src/compiler/gstreaming.cpp @@ -19,14 +19,14 @@ // GStreamingCompiled private implementation /////////////////////////////////// void cv::GStreamingCompiled::Priv::setup(const GMetaArgs &_metaArgs, const GMetaArgs &_outMetas, - std::unique_ptr &&_pE) + std::unique_ptr &&_pE) { m_metas = _metaArgs; m_outMetas = _outMetas; m_exec = std::move(_pE); } -void cv::GStreamingCompiled::Priv::setup(std::unique_ptr &&_pE) +void cv::GStreamingCompiled::Priv::setup(std::unique_ptr &&_pE) { m_exec = std::move(_pE); } diff --git a/modules/gapi/src/compiler/gstreaming_priv.hpp b/modules/gapi/src/compiler/gstreaming_priv.hpp index 1b559ba310..0fd5fc7b7f 100644 --- a/modules/gapi/src/compiler/gstreaming_priv.hpp +++ b/modules/gapi/src/compiler/gstreaming_priv.hpp @@ -9,7 +9,7 @@ #define OPENCV_GAPI_GSTREAMING_COMPILED_PRIV_HPP #include // unique_ptr -#include "executor/gstreamingexecutor.hpp" +#include "executor/gabstractstreamingexecutor.hpp" namespace cv { @@ -26,7 +26,7 @@ class GAPI_EXPORTS GStreamingCompiled::Priv { GMetaArgs m_metas; // passed by user GMetaArgs m_outMetas; // inferred by compiler - std::unique_ptr m_exec; + std::unique_ptr m_exec; // NB: Used by python wrapper to clarify input/output types GTypesInfo m_out_info; @@ -35,8 +35,8 @@ class GAPI_EXPORTS GStreamingCompiled::Priv public: void setup(const GMetaArgs &metaArgs, const GMetaArgs &outMetas, - std::unique_ptr &&pE); - void setup(std::unique_ptr &&pE); + std::unique_ptr &&pE); + void setup(std::unique_ptr &&pE); bool isEmpty() const; const GMetaArgs& metas() const; diff --git a/modules/gapi/src/compiler/passes/dump_dot.cpp b/modules/gapi/src/compiler/passes/dump_dot.cpp index b7f5ea96d3..f7284fb362 100644 --- a/modules/gapi/src/compiler/passes/dump_dot.cpp +++ b/modules/gapi/src/compiler/passes/dump_dot.cpp @@ -149,7 +149,7 @@ void dumpDot(const ade::Graph &g, std::ostream& os) } } break; - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } } @@ -209,7 +209,7 @@ void dumpDot(const ade::Graph &g, std::ostream& os) } break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); break; } } diff --git a/modules/gapi/src/compiler/passes/intrin.cpp b/modules/gapi/src/compiler/passes/intrin.cpp index 8920be6d4e..a6c1dd289c 100644 --- a/modules/gapi/src/compiler/passes/intrin.cpp +++ b/modules/gapi/src/compiler/passes/intrin.cpp @@ -128,7 +128,7 @@ void traceUp(cv::gimpl::GModel::Graph &g, // this recursive process (e.g. via some other output or branch in the // subgraph) if (g.metadata(nh).get().index != desync_id) { - GAPI_Assert(false && "Desynchronization can't be nested!"); + GAPI_Error("Desynchronization can't be nested!"); } return; // This object belongs to the desync path - exit early. } diff --git a/modules/gapi/src/compiler/passes/pattern_matching.cpp b/modules/gapi/src/compiler/passes/pattern_matching.cpp index 7be2da73ae..d52b48a631 100644 --- a/modules/gapi/src/compiler/passes/pattern_matching.cpp +++ b/modules/gapi/src/compiler/passes/pattern_matching.cpp @@ -371,10 +371,9 @@ cv::gimpl::findMatches(const cv::gimpl::GModel::Graph& patternGraph, testNodeMeta, isAlreadyVisited); default: - GAPI_Assert(false && "Unsupported Node type!"); + break; } - - return false; + GAPI_Error("Unsupported Node type!"); }); if (testIt == testOutputNodesLabeled.end()) { diff --git a/modules/gapi/src/compiler/transactions.hpp b/modules/gapi/src/compiler/transactions.hpp index 9092c66291..200cfcd1b1 100644 --- a/modules/gapi/src/compiler/transactions.hpp +++ b/modules/gapi/src/compiler/transactions.hpp @@ -117,7 +117,7 @@ struct ChangeT { case Direction::In: eh = g.link(m_sibling, m_node); break; case Direction::Out: eh = g.link(m_node, m_sibling); break; - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } GAPI_Assert(eh != nullptr); m_meta.copyTo(g, eh); diff --git a/modules/gapi/src/executor/gabstractexecutor.cpp b/modules/gapi/src/executor/gabstractexecutor.cpp new file mode 100644 index 0000000000..e22b2eeb7c --- /dev/null +++ b/modules/gapi/src/executor/gabstractexecutor.cpp @@ -0,0 +1,25 @@ +// 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) 2022 Intel Corporation + +#include "precomp.hpp" + +#include + +#include "executor/gabstractexecutor.hpp" + +cv::gimpl::GAbstractExecutor::GAbstractExecutor(std::unique_ptr &&g_model) + : m_orig_graph(std::move(g_model)) + , m_island_graph(GModel::Graph(*m_orig_graph).metadata() + .get().model) + , m_gm(*m_orig_graph) + , m_gim(*m_island_graph) +{ +} + +const cv::gimpl::GModel::Graph& cv::gimpl::GAbstractExecutor::model() const +{ + return m_gm; +} diff --git a/modules/gapi/src/executor/gabstractexecutor.hpp b/modules/gapi/src/executor/gabstractexecutor.hpp new file mode 100644 index 0000000000..1657142021 --- /dev/null +++ b/modules/gapi/src/executor/gabstractexecutor.hpp @@ -0,0 +1,80 @@ +// 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) 2022 Intel Corporation + + +#ifndef OPENCV_GAPI_GABSTRACT_EXECUTOR_HPP +#define OPENCV_GAPI_GABSTRACT_EXECUTOR_HPP + +#include // unique_ptr, shared_ptr + +#include // tuple, required by magazine +#include // required by magazine + +#include + +#include "backends/common/gbackend.hpp" + +namespace cv { +namespace gimpl { + +// Graph-level executor interface. +// +// This class specifies API for a "super-executor" which orchestrates +// the overall Island graph execution. +// +// Every Island (subgraph) execution is delegated to a particular +// backend and is done opaquely to the GExecutor. +// +// Inputs to a GExecutor instance are: +// - GIslandModel - a high-level graph model which may be seen as a +// "procedure" to execute. +// - GModel - a low-level graph of operations (from which a GIslandModel +// is projected) +// - GComputation runtime arguments - vectors of input/output objects +// +// Every GExecutor is responsible for +// a. Maintaining non-island (intermediate) data objects within graph +// b. Providing GIslandExecutables with input/output data according to +// their protocols +// c. Triggering execution of GIslandExecutables when task/data dependencies +// are met. +// +// By default G-API stores all data on host, and cross-Island +// exchange happens via host buffers (and CV data objects). +// +// Today's exchange data objects are: +// - cv::Mat, cv::RMat - for image buffers +// - cv::Scalar - for single values (with up to four components inside) +// - cv::detail::VectorRef - an untyped wrapper over std::vector +// - cv::detail::OpaqueRef - an untyped wrapper over T +// - cv::MediaFrame - for image textures and surfaces (e.g. in planar format) + +class GAbstractExecutor +{ +protected: + std::unique_ptr m_orig_graph; + std::shared_ptr m_island_graph; + + cv::gimpl::GModel::Graph m_gm; // FIXME: make const? + cv::gimpl::GIslandModel::Graph m_gim; // FIXME: make const? + +public: + explicit GAbstractExecutor(std::unique_ptr &&g_model); + virtual ~GAbstractExecutor() = default; + virtual void run(cv::gimpl::GRuntimeArgs &&args) = 0; + + virtual bool canReshape() const = 0; + virtual void reshape(const GMetaArgs& inMetas, const GCompileArgs& args) = 0; + + virtual void prepareForNewStream() = 0; + + const GModel::Graph& model() const; // FIXME: make it ConstGraph? +}; + +} // namespace gimpl +} // namespace cv + +#endif // OPENCV_GAPI_GABSTRACT_EXECUTOR_HPP diff --git a/modules/gapi/src/executor/gabstractstreamingexecutor.cpp b/modules/gapi/src/executor/gabstractstreamingexecutor.cpp new file mode 100644 index 0000000000..07579a9a6f --- /dev/null +++ b/modules/gapi/src/executor/gabstractstreamingexecutor.cpp @@ -0,0 +1,23 @@ +// 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) 2022 Intel Corporation + +#include "precomp.hpp" + +#include + +#include "executor/gabstractstreamingexecutor.hpp" + +cv::gimpl::GAbstractStreamingExecutor::GAbstractStreamingExecutor(std::unique_ptr &&g_model, + const GCompileArgs &comp_args) + : m_orig_graph(std::move(g_model)) + , m_island_graph(GModel::Graph(*m_orig_graph).metadata() + .get().model) + , m_comp_args(comp_args) + , m_gim(*m_island_graph) + , m_desync(GModel::Graph(*m_orig_graph).metadata() + .contains()) +{ +} diff --git a/modules/gapi/src/executor/gabstractstreamingexecutor.hpp b/modules/gapi/src/executor/gabstractstreamingexecutor.hpp new file mode 100644 index 0000000000..952bbd01db --- /dev/null +++ b/modules/gapi/src/executor/gabstractstreamingexecutor.hpp @@ -0,0 +1,49 @@ +// 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) 2022 Intel Corporation + +#ifndef OPENCV_GAPI_GABSTRACT_STREAMING_EXECUTOR_HPP +#define OPENCV_GAPI_GABSTRACT_STREAMING_EXECUTOR_HPP + +#include // unique_ptr, shared_ptr + +#include + +#include "backends/common/gbackend.hpp" + +namespace cv { +namespace gimpl { + +class GAbstractStreamingExecutor +{ +protected: + std::unique_ptr m_orig_graph; + std::shared_ptr m_island_graph; + cv::GCompileArgs m_comp_args; + + cv::gimpl::GIslandModel::Graph m_gim; // FIXME: make const? + const bool m_desync; + +public: + explicit GAbstractStreamingExecutor(std::unique_ptr &&g_model, + const cv::GCompileArgs &comp_args); + virtual ~GAbstractStreamingExecutor() = default; + virtual void setSource(GRunArgs &&args) = 0; + virtual void start() = 0; + virtual bool pull(cv::GRunArgsP &&outs) = 0; + virtual bool pull(cv::GOptRunArgsP &&outs) = 0; + + using PyPullResult = std::tuple>; + virtual PyPullResult pull() = 0; + + virtual bool try_pull(cv::GRunArgsP &&outs) = 0; + virtual void stop() = 0; + virtual bool running() const = 0; +}; + +} // namespace gimpl +} // namespace cv + +#endif // OPENCV_GAPI_GABSTRACT_STREAMING_EXECUTOR_HPP diff --git a/modules/gapi/src/executor/gexecutor.cpp b/modules/gapi/src/executor/gexecutor.cpp index 472abaaa14..bf25302b75 100644 --- a/modules/gapi/src/executor/gexecutor.cpp +++ b/modules/gapi/src/executor/gexecutor.cpp @@ -16,11 +16,7 @@ #include "compiler/passes/passes.hpp" cv::gimpl::GExecutor::GExecutor(std::unique_ptr &&g_model) - : m_orig_graph(std::move(g_model)) - , m_island_graph(GModel::Graph(*m_orig_graph).metadata() - .get().model) - , m_gm(*m_orig_graph) - , m_gim(*m_island_graph) + : GAbstractExecutor(std::move(g_model)) { // NB: Right now GIslandModel is acyclic, so for a naive execution, // simple unrolling to a list of triggers is enough @@ -79,7 +75,7 @@ cv::gimpl::GExecutor::GExecutor(std::unique_ptr &&g_model) break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); break; } // switch(kind) } // for(gim nodes) @@ -252,7 +248,7 @@ void cv::gimpl::GExecutor::initResource(const ade::NodeHandle & nh, const ade::N break; } default: - GAPI_Assert(false); + GAPI_Error("InternalError"); } } @@ -424,11 +420,6 @@ void cv::gimpl::GExecutor::run(cv::gimpl::GRuntimeArgs &&args) } } -const cv::gimpl::GModel::Graph& cv::gimpl::GExecutor::model() const -{ - return m_gm; -} - bool cv::gimpl::GExecutor::canReshape() const { // FIXME: Introduce proper reshaping support on GExecutor level diff --git a/modules/gapi/src/executor/gexecutor.hpp b/modules/gapi/src/executor/gexecutor.hpp index 5d797ce604..67182fc8cc 100644 --- a/modules/gapi/src/executor/gexecutor.hpp +++ b/modules/gapi/src/executor/gexecutor.hpp @@ -8,58 +8,18 @@ #ifndef OPENCV_GAPI_GEXECUTOR_HPP #define OPENCV_GAPI_GEXECUTOR_HPP -#include // unique_ptr, shared_ptr - #include // tuple, required by magazine #include // required by magazine -#include - -#include "backends/common/gbackend.hpp" +#include "executor/gabstractexecutor.hpp" namespace cv { namespace gimpl { -// Graph-level executor interface. -// -// This class specifies API for a "super-executor" which orchestrates -// the overall Island graph execution. -// -// Every Island (subgraph) execution is delegated to a particular -// backend and is done opaquely to the GExecutor. -// -// Inputs to a GExecutor instance are: -// - GIslandModel - a high-level graph model which may be seen as a -// "procedure" to execute. -// - GModel - a low-level graph of operations (from which a GIslandModel -// is projected) -// - GComputation runtime arguments - vectors of input/output objects -// -// Every GExecutor is responsible for -// a. Maintaining non-island (intermediate) data objects within graph -// b. Providing GIslandExecutables with input/output data according to -// their protocols -// c. Triggering execution of GIslandExecutables when task/data dependencies -// are met. -// -// By default G-API stores all data on host, and cross-Island -// exchange happens via host buffers (and CV data objects). -// -// Today's exchange data objects are: -// - cv::Mat - for image buffers -// - cv::Scalar - for single values (with up to four components inside) -// - cv::detail::VectorRef - an untyped wrapper over std::vector -// - -class GExecutor +class GExecutor final: public GAbstractExecutor { protected: Mag m_res; - std::unique_ptr m_orig_graph; - std::shared_ptr m_island_graph; - - cv::gimpl::GModel::Graph m_gm; // FIXME: make const? - cv::gimpl::GIslandModel::Graph m_gim; // FIXME: make const? // FIXME: Naive executor details are here for now // but then it should be moved to another place @@ -85,14 +45,12 @@ protected: public: explicit GExecutor(std::unique_ptr &&g_model); - void run(cv::gimpl::GRuntimeArgs &&args); + void run(cv::gimpl::GRuntimeArgs &&args) override; - bool canReshape() const; - void reshape(const GMetaArgs& inMetas, const GCompileArgs& args); + bool canReshape() const override; + void reshape(const GMetaArgs& inMetas, const GCompileArgs& args) override; - void prepareForNewStream(); - - const GModel::Graph& model() const; // FIXME: make it ConstGraph? + void prepareForNewStream() override; }; } // namespace gimpl diff --git a/modules/gapi/src/executor/gstreamingexecutor.cpp b/modules/gapi/src/executor/gstreamingexecutor.cpp index 557e5ceee4..124b27f39c 100644 --- a/modules/gapi/src/executor/gstreamingexecutor.cpp +++ b/modules/gapi/src/executor/gstreamingexecutor.cpp @@ -157,7 +157,7 @@ void sync_data(cv::GRunArgs &results, cv::GRunArgsP &outputs) *cv::util::get(out_obj) = std::move(cv::util::get(res_obj)); break; default: - GAPI_Assert(false && "This value type is not supported!"); // ...maybe because of STANDALONE mode. + GAPI_Error("This value type is not supported!"); // ...maybe because of STANDALONE mode. break; } } @@ -227,7 +227,7 @@ void sync_data(cv::gimpl::stream::Result &r, cv::GOptRunArgsP &outputs) } break; default: // ...maybe because of STANDALONE mode. - GAPI_Assert(false && "This value type is not supported!"); + GAPI_Error("This value type is not supported!"); break; } } @@ -446,7 +446,7 @@ cv::gimpl::StreamMsg QueueReader::getInputVector(std::vector &in_queues, break; } default: - GAPI_Assert(false && "Unsupported cmd type in getInputVector()"); + GAPI_Error("Unsupported cmd type in getInputVector()"); } } // for(in_queues) @@ -920,7 +920,7 @@ class StreamingOutput final: public cv::gimpl::GIslandExecutable::IOutput m_stops_sent++; break; default: - GAPI_Assert(false && "Unreachable code"); + GAPI_Error("Unreachable code"); } for (auto &&q : m_out_queues[out_idx]) @@ -1115,7 +1115,7 @@ void collectorThread(std::vector in_queues, out_queue.push(Cmd{cv::util::get(result)}); break; default: - GAPI_Assert(false && "Unreachable code"); + GAPI_Error("Unreachable code"); } } } @@ -1288,13 +1288,7 @@ public: // proper graph reshape and islands recompilation cv::gimpl::GStreamingExecutor::GStreamingExecutor(std::unique_ptr &&g_model, const GCompileArgs &comp_args) - : m_orig_graph(std::move(g_model)) - , m_island_graph(GModel::Graph(*m_orig_graph).metadata() - .get().model) - , m_comp_args(comp_args) - , m_gim(*m_island_graph) - , m_desync(GModel::Graph(*m_orig_graph).metadata() - .contains()) + : GAbstractStreamingExecutor(std::move(g_model), comp_args) { GModel::Graph gm(*m_orig_graph); // NB: Right now GIslandModel is acyclic, and all the below code assumes that. @@ -1485,7 +1479,7 @@ cv::gimpl::GStreamingExecutor::GStreamingExecutor(std::unique_ptr && } break; default: - GAPI_Assert(false); + GAPI_Error("InternalError"); break; } // switch(kind) } // for(gim nodes) @@ -1826,9 +1820,9 @@ bool cv::gimpl::GStreamingExecutor::pull(cv::GRunArgsP &&outs) return true; } default: - GAPI_Assert(false && "Unsupported cmd type in pull"); + GAPI_Error("Unsupported cmd type in pull"); } - GAPI_Assert(false && "Unreachable code"); + GAPI_Error("Unreachable code"); } bool cv::gimpl::GStreamingExecutor::pull(cv::GOptRunArgsP &&outs) @@ -1859,10 +1853,10 @@ bool cv::gimpl::GStreamingExecutor::pull(cv::GOptRunArgsP &&outs) return true; } } - GAPI_Assert(false && "Unreachable code"); + GAPI_Error("Unreachable code"); } -std::tuple> cv::gimpl::GStreamingExecutor::pull() +cv::gimpl::GAbstractStreamingExecutor::PyPullResult cv::gimpl::GStreamingExecutor::pull() { using RunArgs = cv::util::variant; bool is_over = false; diff --git a/modules/gapi/src/executor/gstreamingexecutor.hpp b/modules/gapi/src/executor/gstreamingexecutor.hpp index da27f6a646..cb63d4b7f0 100644 --- a/modules/gapi/src/executor/gstreamingexecutor.hpp +++ b/modules/gapi/src/executor/gstreamingexecutor.hpp @@ -12,7 +12,6 @@ // on concurrent_bounded_queue #endif -#include // unique_ptr, shared_ptr #include // thread #include #include @@ -26,9 +25,7 @@ template using QueueClass = cv::gapi::own::concurrent_bounded_queue< #endif // TBB #include "executor/last_value.hpp" -#include - -#include "backends/common/gbackend.hpp" +#include "executor/gabstractstreamingexecutor.hpp" namespace cv { namespace gimpl { @@ -104,7 +101,7 @@ public: // FIXME: Currently all GExecutor comments apply also // to this one. Please document it separately in the future. -class GStreamingExecutor final +class GStreamingExecutor final: public GAbstractStreamingExecutor { protected: // GStreamingExecutor is a state machine described as follows @@ -131,15 +128,9 @@ protected: RUNNING, } state = State::STOPPED; - std::unique_ptr m_orig_graph; - std::shared_ptr m_island_graph; - cv::GCompileArgs m_comp_args; cv::GMetaArgs m_last_metas; util::optional m_reshapable; - cv::gimpl::GIslandModel::Graph m_gim; // FIXME: make const? - const bool m_desync; - // FIXME: Naive executor details are here for now // but then it should be moved to another place struct OpDesc @@ -202,14 +193,14 @@ public: explicit GStreamingExecutor(std::unique_ptr &&g_model, const cv::GCompileArgs &comp_args); ~GStreamingExecutor(); - void setSource(GRunArgs &&args); - void start(); - bool pull(cv::GRunArgsP &&outs); - bool pull(cv::GOptRunArgsP &&outs); - std::tuple> pull(); - bool try_pull(cv::GRunArgsP &&outs); - void stop(); - bool running() const; + void setSource(GRunArgs &&args) override; + void start() override; + bool pull(cv::GRunArgsP &&outs) override; + bool pull(cv::GOptRunArgsP &&outs) override; + PyPullResult pull() override; + bool try_pull(cv::GRunArgsP &&outs) override; + void stop() override; + bool running() const override; }; } // namespace gimpl diff --git a/modules/gapi/src/executor/gtbbexecutor.hpp b/modules/gapi/src/executor/gtbbexecutor.hpp index 678768e402..342ba4817f 100644 --- a/modules/gapi/src/executor/gtbbexecutor.hpp +++ b/modules/gapi/src/executor/gtbbexecutor.hpp @@ -12,7 +12,7 @@ #endif #ifdef HAVE_TBB -#ifndef TBB_SUPPRESS_DEPRECATED_MESSAGES // supress warning +#ifndef TBB_SUPPRESS_DEPRECATED_MESSAGES #define TBB_SUPPRESS_DEPRECATED_MESSAGES 1 #endif #include diff --git a/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp b/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp index 188f162ffd..b9fce83427 100644 --- a/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp +++ b/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp @@ -40,7 +40,7 @@ GStreamerMediaAdapter::GStreamerMediaAdapter(const cv::GFrameDesc& frameDesc, break; } default: { - GAPI_Assert(false && "Non NV12 or GRAY Media format is not expected here"); + GAPI_Error("Non NV12 or GRAY Media format is not expected here"); break; } } @@ -59,7 +59,7 @@ GStreamerMediaAdapter::GStreamerMediaAdapter(const cv::GFrameDesc& frameDesc, break; } default: { - GAPI_Assert(false && "Non NV12 or GRAY Media format is not expected here"); + GAPI_Error("Non NV12 or GRAY Media format is not expected here"); break; } } @@ -160,7 +160,7 @@ cv::MediaFrame::View GStreamerMediaAdapter::access(cv::MediaFrame::Access access break; } default: { - GAPI_Assert(false && "Non NV12 or GRAY Media format is not expected here"); + GAPI_Error("Non NV12 or GRAY Media format is not expected here"); break; } } @@ -171,7 +171,7 @@ cv::MediaFrame::View GStreamerMediaAdapter::access(cv::MediaFrame::Access access } cv::util::any GStreamerMediaAdapter::blobParams() const { - GAPI_Assert(false && "No implementation for GStreamerMediaAdapter::blobParams()"); + GAPI_Error("No implementation for GStreamerMediaAdapter::blobParams()"); } } // namespace gst diff --git a/modules/gapi/src/streaming/gstreamer/gstreamerenv.cpp b/modules/gapi/src/streaming/gstreamer/gstreamerenv.cpp index 138589b9a6..5575c436f9 100644 --- a/modules/gapi/src/streaming/gstreamer/gstreamerenv.cpp +++ b/modules/gapi/src/streaming/gstreamer/gstreamerenv.cpp @@ -69,12 +69,12 @@ GStreamerEnv::~GStreamerEnv() const GStreamerEnv& GStreamerEnv::init() { - GAPI_Assert(false && "Built without GStreamer support!"); + GAPI_Error("Built without GStreamer support!"); } GStreamerEnv::GStreamerEnv() { - GAPI_Assert(false && "Built without GStreamer support!"); + GAPI_Error("Built without GStreamer support!"); } GStreamerEnv::~GStreamerEnv() diff --git a/modules/gapi/src/streaming/gstreamer/gstreamerpipeline.cpp b/modules/gapi/src/streaming/gstreamer/gstreamerpipeline.cpp index 6687076c7e..aaa9b82a34 100644 --- a/modules/gapi/src/streaming/gstreamer/gstreamerpipeline.cpp +++ b/modules/gapi/src/streaming/gstreamer/gstreamerpipeline.cpp @@ -73,7 +73,7 @@ GStreamerPipeline::Priv::~Priv() { } GStreamerPipeline::Priv::Priv(const std::string&) { - GAPI_Assert(false && "Built without GStreamer support!"); + GAPI_Error("Built without GStreamer support!"); } IStreamSource::Ptr GStreamerPipeline::Priv::getStreamingSource(const std::string&, diff --git a/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp b/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp index f1bd438ce2..9fb15729b4 100644 --- a/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp +++ b/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp @@ -205,7 +205,7 @@ void GStreamerSource::Priv::prepareVideoMeta() break; } default: { - GAPI_Assert(false && "Unsupported GStreamerSource FRAME type."); + GAPI_Error("Unsupported GStreamerSource FRAME type."); } } break; @@ -317,7 +317,7 @@ bool GStreamerSource::Priv::retrieveFrame(cv::Mat& data) break; } default: { - GAPI_Assert(false && "retrieveFrame - unsupported GStreamerSource FRAME type."); + GAPI_Error("retrieveFrame - unsupported GStreamerSource FRAME type."); } } } @@ -354,13 +354,13 @@ GStreamerSource::Priv::~Priv() { } GStreamerSource::Priv::Priv(const std::string&, const GStreamerSource::OutputType) { - GAPI_Assert(false && "Built without GStreamer support!"); + GAPI_Error("Built without GStreamer support!"); } GStreamerSource::Priv::Priv(std::shared_ptr, const std::string&, const GStreamerSource::OutputType) { - GAPI_Assert(false && "Built without GStreamer support!"); + GAPI_Error("Built without GStreamer support!"); } bool GStreamerSource::Priv::pull(cv::gapi::wip::Data&) diff --git a/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_cpu.cpp b/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_cpu.cpp index d81c66b901..893d8f1fa1 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_cpu.cpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_cpu.cpp @@ -65,7 +65,7 @@ static surface_ptr_t create_surface_RGB4_(mfxFrameInfo frameInfo, ", offset: " << out_buf_ptr_offset << ", W: " << surfW << ", H: " << surfH); - GAPI_Assert(false && "Invalid offset"); + GAPI_Error("Invalid offset"); } std::unique_ptr handle(new mfxFrameSurface1); @@ -98,7 +98,7 @@ static surface_ptr_t create_surface_other_(mfxFrameInfo frameInfo, ", offset: " << out_buf_ptr_offset << ", W: " << surfW << ", H: " << surfH); - GAPI_Assert(false && "Invalid offset"); + GAPI_Error("Invalid offset"); } std::unique_ptr handle(new mfxFrameSurface1); @@ -209,7 +209,7 @@ VPLCPUAccelerationPolicy::create_surface_pool(size_t pool_size, size_t surface_s if (!pool_table.emplace(preallocated_pool_memory_ptr, std::move(pool)).second) { GAPI_LOG_WARNING(nullptr, "Cannot insert pool, table size: " + std::to_string(pool_table.size()) << ", key: " << preallocated_pool_memory_ptr << " exists"); - GAPI_Assert(false && "Cannot create pool in VPLCPUAccelerationPolicy"); + GAPI_Error("Cannot create pool in VPLCPUAccelerationPolicy"); } return preallocated_pool_memory_ptr; @@ -248,7 +248,7 @@ VPLCPUAccelerationPolicy::surface_weak_ptr_t VPLCPUAccelerationPolicy::get_free_ if (pool_it == pool_table.end()) { GAPI_LOG_WARNING(nullptr, "key is not found, table size: " << pool_table.size()); - GAPI_Assert(false && "Invalid surface key requested in VPLCPUAccelerationPolicy"); + GAPI_Error("Invalid surface key requested in VPLCPUAccelerationPolicy"); } pool_t& requested_pool = pool_it->second; diff --git a/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_dx11.cpp b/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_dx11.cpp index dba05f0169..9fc1f4dc72 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_dx11.cpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_dx11.cpp @@ -14,7 +14,6 @@ #include "logger.hpp" #if defined(HAVE_DIRECTX) && defined(HAVE_D3D11) -#pragma comment(lib,"d3d11.lib") #define D3D11_NO_HELPERS #include @@ -153,7 +152,7 @@ VPLDX11AccelerationPolicy::surface_weak_ptr_t VPLDX11AccelerationPolicy::get_fre } size_t VPLDX11AccelerationPolicy::get_free_surface_count(pool_key_t) const { - GAPI_Assert(false && "get_free_surface_count() is not implemented"); + GAPI_Error("get_free_surface_count() is not implemented"); } size_t VPLDX11AccelerationPolicy::get_surface_count(pool_key_t key) const { @@ -449,39 +448,39 @@ namespace wip { namespace onevpl { VPLDX11AccelerationPolicy::VPLDX11AccelerationPolicy(device_selector_ptr_t selector) : VPLAccelerationPolicy(selector) { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } VPLDX11AccelerationPolicy::~VPLDX11AccelerationPolicy() = default; void VPLDX11AccelerationPolicy::init(session_t ) { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } void VPLDX11AccelerationPolicy::deinit(session_t) { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } VPLDX11AccelerationPolicy::pool_key_t VPLDX11AccelerationPolicy::create_surface_pool(const mfxFrameAllocRequest&, mfxFrameInfo&) { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } VPLDX11AccelerationPolicy::surface_weak_ptr_t VPLDX11AccelerationPolicy::get_free_surface(pool_key_t) { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } size_t VPLDX11AccelerationPolicy::get_free_surface_count(pool_key_t) const { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } size_t VPLDX11AccelerationPolicy::get_surface_count(pool_key_t) const { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } cv::MediaFrame::AdapterPtr VPLDX11AccelerationPolicy::create_frame_adapter(pool_key_t, const FrameConstructorArgs &) { - GAPI_Assert(false && "VPLDX11AccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLDX11AccelerationPolicy unavailable in current configuration"); } } // namespace onevpl } // namespace wip diff --git a/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_va_api.cpp b/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_va_api.cpp index ca5f1de94c..93657b49be 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_va_api.cpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/accel_policy_va_api.cpp @@ -39,10 +39,17 @@ VPLVAAPIAccelerationPolicy::VPLVAAPIAccelerationPolicy(device_selector_ptr_t sel va_handle = reinterpret_cast(devices.begin()->second.get_ptr()); #else // defined(HAVE_VA) || defined(HAVE_VA_INTEL) - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); #endif // defined(HAVE_VA) || defined(HAVE_VA_INTEL) } +#else // __linux__ +VPLVAAPIAccelerationPolicy::VPLVAAPIAccelerationPolicy(device_selector_ptr_t selector) : + VPLAccelerationPolicy(selector) { + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); +} +#endif // __linux__ +#if defined(HAVE_VA) || defined(HAVE_VA_INTEL) VPLVAAPIAccelerationPolicy::~VPLVAAPIAccelerationPolicy() { vaTerminate(va_handle); GAPI_LOG_INFO(nullptr, "destroyed"); @@ -90,45 +97,40 @@ cv::MediaFrame::AdapterPtr VPLVAAPIAccelerationPolicy::create_frame_adapter(pool return cpu_dispatcher->create_frame_adapter(key, params); } -#else // __linux__ - -VPLVAAPIAccelerationPolicy::VPLVAAPIAccelerationPolicy(device_selector_ptr_t selector) : - VPLAccelerationPolicy(selector) { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); -} +#else // defined(HAVE_VA) || defined(HAVE_VA_INTEL) VPLVAAPIAccelerationPolicy::~VPLVAAPIAccelerationPolicy() = default; void VPLVAAPIAccelerationPolicy::init(session_t ) { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); } void VPLVAAPIAccelerationPolicy::deinit(session_t) { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); } VPLVAAPIAccelerationPolicy::pool_key_t VPLVAAPIAccelerationPolicy::create_surface_pool(const mfxFrameAllocRequest&, mfxFrameInfo&) { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); } VPLVAAPIAccelerationPolicy::surface_weak_ptr_t VPLVAAPIAccelerationPolicy::get_free_surface(pool_key_t) { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); } size_t VPLVAAPIAccelerationPolicy::get_free_surface_count(pool_key_t) const { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); } size_t VPLVAAPIAccelerationPolicy::get_surface_count(pool_key_t) const { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); } cv::MediaFrame::AdapterPtr VPLVAAPIAccelerationPolicy::create_frame_adapter(pool_key_t, const FrameConstructorArgs &) { - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current configuration"); } -#endif // __linux__ +#endif // defined(HAVE_VA) || defined(HAVE_VA_INTEL) } // namespace onevpl } // namespace wip } // namespace gapi diff --git a/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.cpp b/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.cpp index 77cfbb18b1..940c704c62 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.cpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.cpp @@ -46,7 +46,7 @@ size_t LockAdapter::read_lock(mfxMemId mid, mfxFrameData &data) { // adapter will throw error if VPL frame allocator fails if (sts != MFX_ERR_NONE) { impl->unlock_shared(); - GAPI_Assert(false && "Cannot lock frame on READ using VPL allocator"); + GAPI_Error("Cannot lock frame on READ using VPL allocator"); } return prev_lock_count; @@ -76,7 +76,7 @@ void LockAdapter::write_lock(mfxMemId mid, mfxFrameData &data) { // adapter will throw error if VPL frame allocator fails if (sts != MFX_ERR_NONE) { impl->unlock(); - GAPI_Assert(false && "Cannot lock frame on WRITE using VPL allocator"); + GAPI_Error("Cannot lock frame on WRITE using VPL allocator"); } } @@ -199,13 +199,13 @@ void DX11AllocationItem::on_first_in_impl(mfxFrameData *ptr) { err = shared_device_context->Map(get_staging_texture_ptr(), 0, mapType, mapFlags, &lockedRect); if (S_OK != err && DXGI_ERROR_WAS_STILL_DRAWING != err) { GAPI_LOG_WARNING(nullptr, "Cannot Map staging texture in device context, error: " << std::to_string(HRESULT_CODE(err))); - GAPI_Assert(false && "Cannot Map staging texture in device context"); + GAPI_Error("Cannot Map staging texture in device context"); } } while (DXGI_ERROR_WAS_STILL_DRAWING == err); if (FAILED(err)) { GAPI_LOG_WARNING(nullptr, "Cannot lock frame"); - GAPI_Assert(false && "Cannot lock frame"); + GAPI_Error("Cannot lock frame"); return ; } diff --git a/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.hpp b/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.hpp index c68a08a3f8..082e3b5291 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.hpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/dx11_alloc_resource.hpp @@ -13,7 +13,6 @@ #ifdef HAVE_DIRECTX #ifdef HAVE_D3D11 -#pragma comment(lib,"d3d11.lib") #define D3D11_NO_HELPERS #define NOMINMAX diff --git a/modules/gapi/src/streaming/onevpl/accelerators/surface/cpu_frame_adapter.cpp b/modules/gapi/src/streaming/onevpl/accelerators/surface/cpu_frame_adapter.cpp index 24a5b9fb7f..f78d97d571 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/surface/cpu_frame_adapter.cpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/surface/cpu_frame_adapter.cpp @@ -65,7 +65,6 @@ MediaFrame::View VPLMediaFrameCPUAdapter::access(MediaFrame::Access) { cv::util::any VPLMediaFrameCPUAdapter::blobParams() const { throw std::runtime_error("VPLMediaFrameCPUAdapter::blobParams() is not implemented"); - return {}; } void VPLMediaFrameCPUAdapter::serialize(cv::gapi::s11n::IOStream&) { diff --git a/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.cpp b/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.cpp index fad26e50a8..01a9a14d43 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.cpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.cpp @@ -114,17 +114,24 @@ MediaFrame::View VPLMediaFrameDX11Adapter::access(MediaFrame::Access mode) { } } -cv::util::any VPLMediaFrameDX11Adapter::blobParams() const { - /*GAPI_Assert(false && "VPLMediaFrameDX11Adapter::blobParams() is not fully integrated" - "in OpenVINO InferenceEngine and would be temporary disable.");*/ -#ifdef HAVE_INF_ENGINE +mfxHDLPair VPLMediaFrameDX11Adapter::getHandle() const { auto surface_ptr_copy = get_surface(); - Surface::data_t& data = surface_ptr_copy->get_data(); - const Surface::info_t& info = surface_ptr_copy->get_info(); + const Surface::data_t& data = surface_ptr_copy->get_data(); NativeHandleAdapter* native_handle_getter = reinterpret_cast(data.MemId); mfxHDLPair handle{}; native_handle_getter->get_handle(data.MemId, reinterpret_cast(handle)); + return handle; +} + +cv::util::any VPLMediaFrameDX11Adapter::blobParams() const { + /*GAPI_Error("VPLMediaFrameDX11Adapter::blobParams() is not fully integrated" + "in OpenVINO InferenceEngine and would be temporary disable.");*/ +#ifdef HAVE_INF_ENGINE + mfxHDLPair handle = getHandle(); + + auto surface_ptr_copy = get_surface(); + const Surface::info_t& info = surface_ptr_copy->get_info(); GAPI_Assert(frame_desc.fmt == MediaFormat::NV12 && "blobParams() for VPLMediaFrameDX11Adapter supports NV12 only"); @@ -155,16 +162,16 @@ cv::util::any VPLMediaFrameDX11Adapter::blobParams() const { return std::make_pair(std::make_pair(y_tdesc, y_params), std::make_pair(uv_tdesc, uv_params)); #else - GAPI_Assert(false && "VPLMediaFrameDX11Adapter::blobParams() is not implemented"); + GAPI_Error("VPLMediaFrameDX11Adapter::blobParams() is not implemented"); #endif // HAVE_INF_ENGINE } void VPLMediaFrameDX11Adapter::serialize(cv::gapi::s11n::IOStream&) { - GAPI_Assert(false && "VPLMediaFrameDX11Adapter::serialize() is not implemented"); + GAPI_Error("VPLMediaFrameDX11Adapter::serialize() is not implemented"); } void VPLMediaFrameDX11Adapter::deserialize(cv::gapi::s11n::IIStream&) { - GAPI_Assert(false && "VPLMediaFrameDX11Adapter::deserialize() is not implemented"); + GAPI_Error("VPLMediaFrameDX11Adapter::deserialize() is not implemented"); } DXGI_FORMAT VPLMediaFrameDX11Adapter::get_dx11_color_format(uint32_t mfx_fourcc) { diff --git a/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.hpp b/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.hpp index 39528ca6a5..a5eddbb407 100644 --- a/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.hpp +++ b/modules/gapi/src/streaming/onevpl/accelerators/surface/dx11_frame_adapter.hpp @@ -37,6 +37,11 @@ public: GAPI_EXPORTS ~VPLMediaFrameDX11Adapter(); MediaFrame::View access(MediaFrame::Access) override; + // FIXME: Consider a better solution since this approach + // is not easily extendable for other adapters (oclcore.cpp) + // FIXME: Use with caution since the handle might become invalid + // due to reference counting + mfxHDLPair getHandle() const; // The default implementation does nothing cv::util::any blobParams() const override; void serialize(cv::gapi::s11n::IOStream&) override; diff --git a/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.cpp b/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.cpp index 26abe3cfb4..83ed99ad91 100644 --- a/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.cpp +++ b/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.cpp @@ -17,26 +17,23 @@ #ifdef HAVE_DIRECTX #ifdef HAVE_D3D11 -#pragma comment(lib,"d3d11.lib") // get rid of generate macro max/min/etc from DX side #define D3D11_NO_HELPERS #define NOMINMAX #include #include -#pragma comment(lib, "dxgi") #undef D3D11_NO_HELPERS #undef NOMINMAX #endif // HAVE_D3D11 #endif // HAVE_DIRECTX #ifdef __linux__ +#include +#include #if defined(HAVE_VA) || defined(HAVE_VA_INTEL) #include "va/va.h" #include "va/va_drm.h" - -#include -#include #endif // defined(HAVE_VA) || defined(HAVE_VA_INTEL) #endif // __linux__ @@ -247,10 +244,10 @@ CfgParamDeviceSelector::CfgParamDeviceSelector(const CfgParams& cfg_params) : suggested_device = IDeviceSelector::create(va_handle, "GPU", AccelType::VAAPI); suggested_context = IDeviceSelector::create(nullptr, AccelType::VAAPI); #else // defined(HAVE_VA) || defined(HAVE_VA_INTEL) - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current linux configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current linux configuration"); #endif // defined(HAVE_VA) || defined(HAVE_VA_INTEL) #else // #ifdef __linux__ - GAPI_Assert(false && "MFX_IMPL_VIA_VAAPI is supported on linux only"); + GAPI_Error("MFX_IMPL_VIA_VAAPI is supported on linux only"); #endif // #ifdef __linux__ break; } @@ -338,10 +335,10 @@ CfgParamDeviceSelector::CfgParamDeviceSelector(Device::Ptr device_ptr, suggested_device = IDeviceSelector::create(device_ptr, device_id, AccelType::VAAPI); suggested_context = IDeviceSelector::create(nullptr, AccelType::VAAPI); #else // defined(HAVE_VA) || defined(HAVE_VA_INTEL) - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current linux configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current linux configuration"); #endif // defined(HAVE_VA) || defined(HAVE_VA_INTEL) #else // #ifdef __linux__ - GAPI_Assert(false && "MFX_IMPL_VIA_VAAPI is supported on linux only"); + GAPI_Error("MFX_IMPL_VIA_VAAPI is supported on linux only"); #endif // #ifdef __linux__ break; } @@ -397,10 +394,10 @@ CfgParamDeviceSelector::CfgParamDeviceSelector(const Device &device, case AccelType::VAAPI: #ifdef __linux__ #if !defined(HAVE_VA) || !defined(HAVE_VA_INTEL) - GAPI_Assert(false && "VPLVAAPIAccelerationPolicy unavailable in current linux configuration"); + GAPI_Error("VPLVAAPIAccelerationPolicy unavailable in current linux configuration"); #endif // defined(HAVE_VA) || defined(HAVE_VA_INTEL) #else // #ifdef __linux__ - GAPI_Assert(false && "MFX_IMPL_VIA_VAAPI is supported on linux only"); + GAPI_Error("MFX_IMPL_VIA_VAAPI is supported on linux only"); #endif // #ifdef __linux__ break; case AccelType::HOST: diff --git a/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.hpp b/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.hpp index f7672ce924..18b468fd86 100644 --- a/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.hpp +++ b/modules/gapi/src/streaming/onevpl/cfg_param_device_selector.hpp @@ -20,7 +20,7 @@ namespace gapi { namespace wip { namespace onevpl { -class PlatformSpecificParams; +struct PlatformSpecificParams; std::vector update_param_with_accel_type(std::vector &¶m_array, AccelType type); struct GAPI_EXPORTS CfgParamDeviceSelector final: public IDeviceSelector { diff --git a/modules/gapi/src/streaming/onevpl/cfg_params_parser.cpp b/modules/gapi/src/streaming/onevpl/cfg_params_parser.cpp index 9f5a68a431..a40564aceb 100644 --- a/modules/gapi/src/streaming/onevpl/cfg_params_parser.cpp +++ b/modules/gapi/src/streaming/onevpl/cfg_params_parser.cpp @@ -60,7 +60,7 @@ private: return ret; } mfxVariant create_impl(const std::string&, const std::string&) { - GAPI_Assert(false && "Something wrong: you should not create mfxVariant " + GAPI_Error("Something wrong: you should not create mfxVariant " "from string directly - native type is lost in this case"); } }; @@ -173,7 +173,7 @@ void extract_optional_param_by_name(const std::string &name, [&out_param](int64_t value) { out_param = cv::util::make_optional(static_cast(value)); }, [&out_param](float_t value) { out_param = cv::util::make_optional(static_cast(value)); }, [&out_param](double_t value) { out_param = cv::util::make_optional(static_cast(value)); }, - [&out_param](void*) { GAPI_Assert(false && "`void*` is unsupported type"); }, + [&out_param](void*) { GAPI_Error("`void*` is unsupported type"); }, [&out_param](const std::string& value) { out_param = cv::util::make_optional(strtoull_or_throw(value.c_str())); }), @@ -189,7 +189,7 @@ unsigned long strtoul_or_throw(const char* str) { ((ret == ULONG_MAX) && errno == ERANGE)) { // nothing parsed from the string, handle errors or exit GAPI_LOG_WARNING(nullptr, "strtoul failed for: " << str); - GAPI_Assert(false && "strtoul_or_throw"); + GAPI_Error("strtoul_or_throw"); } return ret; } @@ -202,7 +202,7 @@ size_t strtoull_or_throw(const char* str) { ((ret == ULLONG_MAX) && errno == ERANGE)) { // nothing parsed from the string, handle errors or exit GAPI_LOG_WARNING(nullptr, "strtoull failed for: " << str); - GAPI_Assert(false && "strtoull_or_throw"); + GAPI_Error("strtoull_or_throw"); } return ret; } @@ -215,7 +215,7 @@ int64_t strtoll_or_throw(const char* str) { ((ret == LONG_MAX || ret == LONG_MIN) && errno == ERANGE)) { // nothing parsed from the string, handle errors or exit GAPI_LOG_WARNING(nullptr, "strtoll failed for: " << str); - GAPI_Assert(false && "strtoll_or_throw"); + GAPI_Error("strtoll_or_throw"); } return ret; } diff --git a/modules/gapi/src/streaming/onevpl/data_provider_defines.hpp b/modules/gapi/src/streaming/onevpl/data_provider_defines.hpp index 5a4c66fef2..2d2bbd05a0 100644 --- a/modules/gapi/src/streaming/onevpl/data_provider_defines.hpp +++ b/modules/gapi/src/streaming/onevpl/data_provider_defines.hpp @@ -18,7 +18,7 @@ struct IDataProvider::mfx_bitstream : public mfxBitstream {}; #else // HAVE_ONEVPL struct IDataProvider::mfx_bitstream { mfx_bitstream() { - GAPI_Assert(false && "Reject to create `mfxBitstream` because library compiled without VPL/MFX support"); + GAPI_Error("Reject to create `mfxBitstream` because library compiled without VPL/MFX support"); } }; #endif // HAVE_ONEVPL diff --git a/modules/gapi/src/streaming/onevpl/default.cpp b/modules/gapi/src/streaming/onevpl/default.cpp new file mode 100644 index 0000000000..67bca7e246 --- /dev/null +++ b/modules/gapi/src/streaming/onevpl/default.cpp @@ -0,0 +1,51 @@ +// 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) 2022 Intel Corporation + +#include +#include + +#include +#include +#include + +#include "cfg_param_device_selector.hpp" + +#ifdef HAVE_ONEVPL + +namespace cv { +namespace gapi { +namespace wip { +namespace onevpl { + +std::shared_ptr getDefaultDeviceSelector(const std::vector& cfg_params) { + std::shared_ptr default_accel_contex(new CfgParamDeviceSelector(cfg_params)); + + return default_accel_contex; +} + +} // namespace onevpl +} // namespace wip +} // namespace gapi +} // namespace cv + +#else // HAVE_ONEVPL + +namespace cv { +namespace gapi { +namespace wip { +namespace onevpl { + +std::shared_ptr getDefaultDeviceSelector(const std::vector&) { + std::cerr << "Cannot utilize getDefaultVPLDeviceAndCtx without HAVE_ONEVPL enabled" << std::endl; + util::throw_error(std::logic_error("Cannot utilize getDefaultVPLDeviceAndCtx without HAVE_ONEVPL enabled")); +} + +} // namespace onevpl +} // namespace wip +} // namespace gapi +} // namespace cv + +#endif // HAVE_ONEVPL diff --git a/modules/gapi/src/streaming/onevpl/demux/async_mfp_demux_data_provider.cpp b/modules/gapi/src/streaming/onevpl/demux/async_mfp_demux_data_provider.cpp index 5d139af824..becd893e09 100644 --- a/modules/gapi/src/streaming/onevpl/demux/async_mfp_demux_data_provider.cpp +++ b/modules/gapi/src/streaming/onevpl/demux/async_mfp_demux_data_provider.cpp @@ -5,14 +5,6 @@ // Copyright (C) 2021 Intel Corporation #ifdef HAVE_ONEVPL #include -#ifdef _WIN32 - -#pragma comment(lib, "Mf.lib") -#pragma comment(lib, "Mfuuid.lib") -#pragma comment(lib, "Mfplat.lib") -#pragma comment(lib, "shlwapi.lib") -#pragma comment(lib, "mfreadwrite.lib") -#endif // _WIN32 #include #include "streaming/onevpl/demux/async_mfp_demux_data_provider.hpp" @@ -753,7 +745,7 @@ bool MFPAsyncDemuxDataProvider::fetch_bitstream_data(std::shared_ptr(out_bitsream->Data)); - GAPI_Assert(false && "invalid bitstream key"); + GAPI_Error("invalid bitstream key"); } if (it->second) { it->second->Unlock(); @@ -796,20 +788,20 @@ bool MFPAsyncDemuxDataProvider::empty() const { #else // _WIN32 MFPAsyncDemuxDataProvider::MFPAsyncDemuxDataProvider(const std::string&) { - GAPI_Assert(false && "Unsupported: Microsoft Media Foundation is not available"); + GAPI_Error("Unsupported: Microsoft Media Foundation is not available"); } IDataProvider::mfx_codec_id_type MFPAsyncDemuxDataProvider::get_mfx_codec_id() const { - GAPI_Assert(false && "Unsupported: Microsoft Media Foundation is not available"); + GAPI_Error("Unsupported: Microsoft Media Foundation is not available"); return std::numeric_limits::max(); } bool MFPAsyncDemuxDataProvider::fetch_bitstream_data(std::shared_ptr &) { - GAPI_Assert(false && "Unsupported: Microsoft Media Foundation is not available"); + GAPI_Error("Unsupported: Microsoft Media Foundation is not available"); return false; } bool MFPAsyncDemuxDataProvider::empty() const { - GAPI_Assert(false && "Unsupported: Microsoft Media Foundation is not available"); + GAPI_Error("Unsupported: Microsoft Media Foundation is not available"); return true; } #endif // _WIN32 diff --git a/modules/gapi/src/streaming/onevpl/engine/decode/decode_engine_legacy.cpp b/modules/gapi/src/streaming/onevpl/engine/decode/decode_engine_legacy.cpp index 0ab8301799..7b0ad8c83d 100644 --- a/modules/gapi/src/streaming/onevpl/engine/decode/decode_engine_legacy.cpp +++ b/modules/gapi/src/streaming/onevpl/engine/decode/decode_engine_legacy.cpp @@ -190,7 +190,7 @@ VPLLegacyDecodeEngine::SessionParam VPLLegacyDecodeEngine::prepare_session_param // TODO make proper direction mfxDecParams.IOPattern = MFX_IOPATTERN_OUT_SYSTEM_MEMORY; } else { - GAPI_Assert(false && "unsupported AccelType from device selector"); + GAPI_Error("unsupported AccelType from device selector"); } // try fetch & decode input data @@ -251,7 +251,8 @@ VPLLegacyDecodeEngine::SessionParam VPLLegacyDecodeEngine::prepare_session_param } - decRequest.Type |= MFX_MEMTYPE_EXTERNAL_FRAME | MFX_MEMTYPE_FROM_DECODE | MFX_MEMTYPE_FROM_VPPIN; + decRequest.Type |= MFX_MEMTYPE_EXTERNAL_FRAME | MFX_MEMTYPE_FROM_DECODE | + MFX_MEMTYPE_FROM_VPPIN | MFX_MEMTYPE_SHARED_RESOURCE; VPLAccelerationPolicy::pool_key_t decode_pool_key = acceleration_policy->create_surface_pool(decRequest, mfxDecParams.mfx.FrameInfo); diff --git a/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_dispatcher.cpp b/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_dispatcher.cpp index 5a08f2bd09..c3f63efc55 100644 --- a/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_dispatcher.cpp +++ b/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_dispatcher.cpp @@ -66,7 +66,7 @@ pp_session VPPPreprocDispatcher::initialize_preproc(const pp_params& initial_fra return sess; } } - GAPI_Assert(false && "Cannot initialize VPP preproc in dispatcher, no suitable worker"); + GAPI_Error("Cannot initialize VPP preproc in dispatcher, no suitable worker"); } cv::MediaFrame VPPPreprocDispatcher::run_sync(const pp_session &session_handle, @@ -80,7 +80,7 @@ cv::MediaFrame VPPPreprocDispatcher::run_sync(const pp_session &session_handle, return w->run_sync(session_handle, in_frame, opt_roi); } } - GAPI_Assert(false && "Cannot invoke VPP preproc in dispatcher, no suitable worker"); + GAPI_Error("Cannot invoke VPP preproc in dispatcher, no suitable worker"); } #else // HAVE_ONEVPL @@ -90,13 +90,13 @@ cv::util::optional VPPPreprocDispatcher::is_applicable(const cv::Medi pp_session VPPPreprocDispatcher::initialize_preproc(const pp_params&, const GFrameDesc&) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } cv::MediaFrame VPPPreprocDispatcher::run_sync(const pp_session &, const cv::MediaFrame&, const cv::util::optional &) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } #endif // HAVE_ONEVPL } // namespace onevpl diff --git a/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_engine.cpp b/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_engine.cpp index 10ce92e20a..cc81df0626 100644 --- a/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_engine.cpp +++ b/modules/gapi/src/streaming/onevpl/engine/preproc/preproc_engine.cpp @@ -249,13 +249,13 @@ pp_session VPPPreprocEngine::initialize_preproc(const pp_params& initial_frame_p sts = MFXCreateSession(mfx_handle, impl_number, &mfx_vpp_session); if (sts != MFX_ERR_NONE) { GAPI_LOG_WARNING(nullptr, "Cannot clone VPP session, error: " << mfxstatus_to_string(sts)); - GAPI_Assert(false && "Cannot continue VPP preprocessing"); + GAPI_Error("Cannot continue VPP preprocessing"); } sts = MFXJoinSession(params.handle, mfx_vpp_session); if (sts != MFX_ERR_NONE) { GAPI_LOG_WARNING(nullptr, "Cannot join VPP sessions, error: " << mfxstatus_to_string(sts)); - GAPI_Assert(false && "Cannot continue VPP preprocessing"); + GAPI_Error("Cannot continue VPP preprocessing"); } GAPI_LOG_INFO(nullptr, "[" << mfx_vpp_session << "] starting pool allocation"); @@ -281,7 +281,7 @@ pp_session VPPPreprocEngine::initialize_preproc(const pp_params& initial_frame_p vppRequests[1].AllocId = std::numeric_limits::max() - request_id++; GAPI_Assert(request_id != std::numeric_limits::max() && "Something wrong"); - vppRequests[1].Type |= MFX_MEMTYPE_FROM_VPPIN; + vppRequests[1].Type |= MFX_MEMTYPE_FROM_VPPIN | MFX_MEMTYPE_SHARED_RESOURCE; vpp_out_pool_key = acceleration_policy->create_surface_pool(vppRequests[1], mfxVPPParams.vpp.Out); @@ -301,7 +301,7 @@ pp_session VPPPreprocEngine::initialize_preproc(const pp_params& initial_frame_p } } catch (const std::exception&) { MFXClose(mfx_vpp_session); - GAPI_Assert(false && "Cannot init preproc resources"); + GAPI_Error("Cannot init preproc resources"); } // create engine session after all diff --git a/modules/gapi/src/streaming/onevpl/engine/preproc/utils.cpp b/modules/gapi/src/streaming/onevpl/engine/preproc/utils.cpp index 6cf7212f3e..f6c210ad9d 100644 --- a/modules/gapi/src/streaming/onevpl/engine/preproc/utils.cpp +++ b/modules/gapi/src/streaming/onevpl/engine/preproc/utils.cpp @@ -28,7 +28,7 @@ cv::MediaFormat fourcc_to_MediaFormat(int value) { default: GAPI_LOG_WARNING(nullptr, "Unsupported FourCC format requested: " << value << ". Cannot cast to cv::MediaFrame"); - GAPI_Assert(false && "Unsupported FOURCC"); + GAPI_Error("Unsupported FOURCC"); } } @@ -44,7 +44,7 @@ int MediaFormat_to_fourcc(cv::MediaFormat value) { GAPI_LOG_WARNING(nullptr, "Unsupported cv::MediaFormat format requested: " << static_cast::type>(value) << ". Cannot cast to FourCC"); - GAPI_Assert(false && "Unsupported cv::MediaFormat"); + GAPI_Error("Unsupported cv::MediaFormat"); } } int MediaFormat_to_chroma(cv::MediaFormat value) { @@ -58,7 +58,7 @@ int MediaFormat_to_chroma(cv::MediaFormat value) { GAPI_LOG_WARNING(nullptr, "Unsupported cv::MediaFormat format requested: " << static_cast::type>(value) << ". Cannot cast to ChromaFormateIdc"); - GAPI_Assert(false && "Unsupported cv::MediaFormat"); + GAPI_Error("Unsupported cv::MediaFormat"); } } diff --git a/modules/gapi/src/streaming/onevpl/engine/preproc_engine_interface.cpp b/modules/gapi/src/streaming/onevpl/engine/preproc_engine_interface.cpp index b5787320d6..62d03e1440 100644 --- a/modules/gapi/src/streaming/onevpl/engine/preproc_engine_interface.cpp +++ b/modules/gapi/src/streaming/onevpl/engine/preproc_engine_interface.cpp @@ -30,7 +30,7 @@ namespace wip { template std::unique_ptr IPreprocEngine::create_preproc_engine_impl(const PreprocEngineArgs& ...) { - GAPI_Assert(false && "Unsupported "); + GAPI_Error("Unsupported "); } template <> @@ -91,7 +91,7 @@ IPreprocEngine::create_preproc_engine_impl(const onevpl::Device &device, } if (!pp_is_created) { GAPI_LOG_WARNING(nullptr, "Cannot create VPP preprocessing engine: configuration unsupported"); - GAPI_Assert(false && "VPP preproc unsupported"); + GAPI_Error("VPP preproc unsupported"); } #endif // HAVE_ONEVPL return dispatcher; diff --git a/modules/gapi/src/streaming/onevpl/engine/transcode/transcode_engine_legacy.cpp b/modules/gapi/src/streaming/onevpl/engine/transcode/transcode_engine_legacy.cpp index 23703bf172..41feb5c7fe 100644 --- a/modules/gapi/src/streaming/onevpl/engine/transcode/transcode_engine_legacy.cpp +++ b/modules/gapi/src/streaming/onevpl/engine/transcode/transcode_engine_legacy.cpp @@ -404,7 +404,7 @@ void VPLLegacyTranscodeEngine::validate_vpp_param(const mfxVideoParam& mfxVPPPar " must be less or equal to \"" << CfgParam::vpp_in_width_name() << "\": " << mfxVPPParams.vpp.In.Width); - GAPI_Assert(false && "Invalid VPP params combination: Width & Crop"); + GAPI_Error("Invalid VPP params combination: Width & Crop"); } if (mfxVPPParams.vpp.In.Height < mfxVPPParams.vpp.In.CropH + mfxVPPParams.vpp.In.CropY) { @@ -416,7 +416,7 @@ void VPLLegacyTranscodeEngine::validate_vpp_param(const mfxVideoParam& mfxVPPPar " must be less or equal to \"" << CfgParam::vpp_in_height_name() << "\": " << mfxVPPParams.vpp.In.Height); - GAPI_Assert(false && "Invalid VPP params combination: Height & Crop"); + GAPI_Error("Invalid VPP params combination: Height & Crop"); } if (mfxVPPParams.vpp.Out.Width < mfxVPPParams.vpp.Out.CropW + mfxVPPParams.vpp.Out.CropX) { @@ -428,7 +428,7 @@ void VPLLegacyTranscodeEngine::validate_vpp_param(const mfxVideoParam& mfxVPPPar " must be less or equal to \"" << CfgParam::vpp_out_width_name() << "\": " << mfxVPPParams.vpp.Out.Width); - GAPI_Assert(false && "Invalid VPP params combination: Width & Crop"); + GAPI_Error("Invalid VPP params combination: Width & Crop"); } if (mfxVPPParams.vpp.Out.Height < mfxVPPParams.vpp.Out.CropH + mfxVPPParams.vpp.Out.CropY) { @@ -440,7 +440,7 @@ void VPLLegacyTranscodeEngine::validate_vpp_param(const mfxVideoParam& mfxVPPPar " must be less or equal to \"" << CfgParam::vpp_out_height_name() << "\": " << mfxVPPParams.vpp.Out.Height); - GAPI_Assert(false && "Invalid VPP params combination: Height & Crop"); + GAPI_Error("Invalid VPP params combination: Height & Crop"); } GAPI_LOG_INFO(nullptr, "Finished VPP param validation"); diff --git a/modules/gapi/src/streaming/onevpl/file_data_provider.cpp b/modules/gapi/src/streaming/onevpl/file_data_provider.cpp index a86d541904..189cb3b070 100644 --- a/modules/gapi/src/streaming/onevpl/file_data_provider.cpp +++ b/modules/gapi/src/streaming/onevpl/file_data_provider.cpp @@ -127,26 +127,23 @@ FileDataProvider::FileDataProvider(const std::string&, source_handle(nullptr, &fclose), codec(std::numeric_limits::max()), bitstream_data_size(bitstream_data_size_value) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } FileDataProvider::~FileDataProvider() = default; IDataProvider::mfx_codec_id_type FileDataProvider::get_mfx_codec_id() const { cv::util::suppress_unused_warning(codec); - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); - return codec; + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } bool FileDataProvider::fetch_bitstream_data(std::shared_ptr &) { cv::util::suppress_unused_warning(bitstream_data_size); - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); - return false; + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } bool FileDataProvider::empty() const { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); - return true; + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } #endif // HAVE_ONEVPL } // namespace onevpl diff --git a/modules/gapi/src/streaming/onevpl/source.cpp b/modules/gapi/src/streaming/onevpl/source.cpp index efcc9bf850..b25edb6e5d 100644 --- a/modules/gapi/src/streaming/onevpl/source.cpp +++ b/modules/gapi/src/streaming/onevpl/source.cpp @@ -73,33 +73,33 @@ GSource::GSource(std::shared_ptr source, #else GSource::GSource(const std::string&, const CfgParams&) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } GSource::GSource(const std::string&, const CfgParams&, const std::string&, void*, void*) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } GSource::GSource(const std::string&, const CfgParams&, const Device &, const Context &) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } GSource::GSource(const std::string&, const CfgParams&, std::shared_ptr) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } GSource::GSource(std::shared_ptr, const CfgParams&) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } GSource::GSource(std::shared_ptr, const CfgParams&, const std::string&, void*, void*) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } GSource::GSource(std::shared_ptr, const CfgParams&, std::shared_ptr) { - GAPI_Assert(false && "Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); + GAPI_Error("Unsupported: G-API compiled without `WITH_GAPI_ONEVPL=ON`"); } #endif diff --git a/modules/gapi/src/streaming/onevpl/source_priv.cpp b/modules/gapi/src/streaming/onevpl/source_priv.cpp index e8d26b41e2..ce7bca8435 100644 --- a/modules/gapi/src/streaming/onevpl/source_priv.cpp +++ b/modules/gapi/src/streaming/onevpl/source_priv.cpp @@ -109,7 +109,7 @@ GSource::Priv::Priv(std::shared_ptr provider, GAPI_LOG_WARNING(nullptr, "MFXSetConfigFilterProperty failed, error: " << mfxstatus_to_string(sts) << " - for \"" << cfg_param_it->get_name() << "\""); - GAPI_Assert(false && "MFXSetConfigFilterProperty failed"); + GAPI_Error("MFXSetConfigFilterProperty failed"); } mfx_param.Type = MFX_VARIANT_TYPE_U32; @@ -123,7 +123,7 @@ GSource::Priv::Priv(std::shared_ptr provider, GAPI_LOG_WARNING(nullptr, "MFXSetConfigFilterProperty failed, error: " << mfxstatus_to_string(sts) << " - for \"mfxImplDescription.mfxVPPDescription.filter.FilterFourCC\""); - GAPI_Assert(false && "MFXSetConfigFilterProperty failed"); + GAPI_Error("MFXSetConfigFilterProperty failed"); } ++cfg_param_it; @@ -227,16 +227,15 @@ GSource::Priv::Priv(std::shared_ptr provider, // TODO Add factory static method in ProcessingEngineBase if (mfx_impl_description->ApiVersion.Major >= VPL_NEW_API_MAJOR_VERSION) { - GAPI_Assert(false && + GAPI_LOG_WARNING(NULL, "GSource mfx_impl_description->ApiVersion.Major >= VPL_NEW_API_MAJOR_VERSION" - " - is not implemented"); + " - is not implemented. Rollback to MFX implementation"); + } + const auto& transcode_params = VPLLegacyTranscodeEngine::get_vpp_params(preferred_params); + if (!transcode_params.empty()) { + engine.reset(new VPLLegacyTranscodeEngine(std::move(acceleration))); } else { - const auto& transcode_params = VPLLegacyTranscodeEngine::get_vpp_params(preferred_params); - if (!transcode_params.empty()) { - engine.reset(new VPLLegacyTranscodeEngine(std::move(acceleration))); - } else { - engine.reset(new VPLLegacyDecodeEngine(std::move(acceleration))); - } + engine.reset(new VPLLegacyDecodeEngine(std::move(acceleration))); } } @@ -318,7 +317,7 @@ std::unique_ptr GSource::Priv::initializeHWAccel(std::sha GAPI_LOG_WARNING(nullptr, "Cannot initialize HW Accel: " "invalid accelerator type: " << std::to_string(accel_mode.Data.U32)); - GAPI_Assert(false && "Cannot initialize HW Accel"); + GAPI_Error("Cannot initialize HW Accel"); } } diff --git a/modules/gapi/src/streaming/onevpl/utils.cpp b/modules/gapi/src/streaming/onevpl/utils.cpp index efd1618b71..a5513f5377 100644 --- a/modules/gapi/src/streaming/onevpl/utils.cpp +++ b/modules/gapi/src/streaming/onevpl/utils.cpp @@ -429,4 +429,5 @@ std::string ext_mem_frame_type_to_cstr(int type) { } // namespace wip } // namespace gapi } // namespace cv + #endif // HAVE_ONEVPL diff --git a/modules/gapi/test/common/gapi_core_tests.hpp b/modules/gapi/test/common/gapi_core_tests.hpp index b5a4628212..6d4a4bd41b 100644 --- a/modules/gapi/test/common/gapi_core_tests.hpp +++ b/modules/gapi/test/common/gapi_core_tests.hpp @@ -41,7 +41,7 @@ inline std::ostream& operator<<(std::ostream& os, mathOp op) CASE(SUB); CASE(MUL); CASE(DIV); - default: GAPI_Assert(false && "unknown mathOp value"); + default: GAPI_Error("unknown mathOp value"); } #undef CASE return os; @@ -57,7 +57,7 @@ inline std::ostream& operator<<(std::ostream& os, bitwiseOp op) CASE(OR); CASE(XOR); CASE(NOT); - default: GAPI_Assert(false && "unknown bitwiseOp value"); + default: GAPI_Error("unknown bitwiseOp value"); } #undef CASE return os; diff --git a/modules/gapi/test/common/gapi_operators_tests.hpp b/modules/gapi/test/common/gapi_operators_tests.hpp index 9a195e0688..1f2fdc7df6 100644 --- a/modules/gapi/test/common/gapi_operators_tests.hpp +++ b/modules/gapi/test/common/gapi_operators_tests.hpp @@ -34,7 +34,7 @@ inline std::ostream& operator<<(std::ostream& os, operation op) CASE(GTR); CASE(LTR); CASE(GER); CASE(LER); CASE(EQR); CASE(NER); CASE(AND); CASE(OR); CASE(XOR); CASE(ANDR); CASE(ORR); CASE(XORR); - default: GAPI_Assert(false && "unknown operation value"); + default: GAPI_Error("unknown operation value"); } #undef CASE return os; @@ -187,7 +187,7 @@ struct g_api_ocv_pair_mat_scalar { CASE(GTR); CASE(LTR); CASE(GER); CASE(LER); CASE(EQR); CASE(NER); CASE(AND); CASE(OR); CASE(XOR); CASE(ANDR); CASE(ORR); CASE(XORR); - default: GAPI_Assert(false && "unknown operation value"); + default: GAPI_Error("unknown operation value"); } } #undef CASE @@ -214,7 +214,7 @@ struct g_api_ocv_pair_mat_mat { CASE(ADD); CASE(SUB); CASE(DIV); CASE(GT); CASE(LT); CASE(GE); CASE(LE); CASE(EQ); CASE(NE); CASE(AND); CASE(OR); CASE(XOR); - default: GAPI_Assert(false && "unknown operation value"); + default: GAPI_Error("unknown operation value"); } } #undef CASE diff --git a/modules/gapi/test/common/gapi_stereo_tests_inl.hpp b/modules/gapi/test/common/gapi_stereo_tests_inl.hpp index e1d0793ec8..f1c7f7d506 100644 --- a/modules/gapi/test/common/gapi_stereo_tests_inl.hpp +++ b/modules/gapi/test/common/gapi_stereo_tests_inl.hpp @@ -25,7 +25,7 @@ TEST_P(TestGAPIStereo, DisparityDepthTest) case format::DEPTH_FLOAT16: dtype = CV_16FC1; break; case format::DEPTH_FLOAT32: dtype = CV_32FC1; break; case format::DISPARITY_FIXED16_12_4: dtype = CV_16SC1; break; - default: GAPI_Assert(false && "Unsupported format in test"); + default: GAPI_Error("Unsupported format in test"); } initOutMats(sz, dtype); @@ -61,7 +61,7 @@ TEST_P(TestGAPIStereo, DisparityDepthTest) case format::DISPARITY_FIXED16_12_4: break; default: - GAPI_Assert(false && "Unsupported format in test"); + GAPI_Error("Unsupported format in test"); } EXPECT_TRUE(cmpF(out_mat_gapi, out_mat_ocv)); diff --git a/modules/gapi/test/common/gapi_tests_common.hpp b/modules/gapi/test/common/gapi_tests_common.hpp index 4c9c2d9700..f84ee05f49 100644 --- a/modules/gapi/test/common/gapi_tests_common.hpp +++ b/modules/gapi/test/common/gapi_tests_common.hpp @@ -373,7 +373,7 @@ public: initMatByPointsVectorRandU>(sz_in); break; default: - GAPI_Assert(false && "Unsupported depth"); + GAPI_Error("Unsupported depth"); break; } } @@ -1065,7 +1065,7 @@ inline std::ostream& operator<<(std::ostream& os, CmpTypes op) CASE(CMP_LT); CASE(CMP_LE); CASE(CMP_NE); - default: GAPI_Assert(false && "unknown CmpTypes value"); + default: GAPI_Error("unknown CmpTypes value"); } #undef CASE return os; @@ -1084,7 +1084,7 @@ inline std::ostream& operator<<(std::ostream& os, NormTypes op) CASE(NORM_HAMMING2); CASE(NORM_RELATIVE); CASE(NORM_MINMAX); - default: GAPI_Assert(false && "unknown NormTypes value"); + default: GAPI_Error("unknown NormTypes value"); } #undef CASE return os; @@ -1100,7 +1100,7 @@ inline std::ostream& operator<<(std::ostream& os, RetrievalModes op) CASE(RETR_CCOMP); CASE(RETR_TREE); CASE(RETR_FLOODFILL); - default: GAPI_Assert(false && "unknown RetrievalModes value"); + default: GAPI_Error("unknown RetrievalModes value"); } #undef CASE return os; @@ -1115,7 +1115,7 @@ inline std::ostream& operator<<(std::ostream& os, ContourApproximationModes op) CASE(CHAIN_APPROX_SIMPLE); CASE(CHAIN_APPROX_TC89_L1); CASE(CHAIN_APPROX_TC89_KCOS); - default: GAPI_Assert(false && "unknown ContourApproximationModes value"); + default: GAPI_Error("unknown ContourApproximationModes value"); } #undef CASE return os; @@ -1134,7 +1134,7 @@ inline std::ostream& operator<<(std::ostream& os, MorphTypes op) CASE(MORPH_TOPHAT); CASE(MORPH_BLACKHAT); CASE(MORPH_HITMISS); - default: GAPI_Assert(false && "unknown MorphTypes value"); + default: GAPI_Error("unknown MorphTypes value"); } #undef CASE return os; @@ -1153,7 +1153,7 @@ inline std::ostream& operator<<(std::ostream& os, DistanceTypes op) CASE(DIST_FAIR); CASE(DIST_WELSCH); CASE(DIST_HUBER); - default: GAPI_Assert(false && "unknown DistanceTypes value"); + default: GAPI_Error("unknown DistanceTypes value"); } #undef CASE return os; @@ -1176,7 +1176,7 @@ inline std::ostream& operator<<(std::ostream& os, KmeansFlags op) case KmeansFlags::KMEANS_PP_CENTERS | KmeansFlags::KMEANS_USE_INITIAL_LABELS: os << "KMEANS_PP_CENTERS | KMEANS_USE_INITIAL_LABELS"; break; - default: GAPI_Assert(false && "unknown KmeansFlags value"); + default: GAPI_Error("unknown KmeansFlags value"); } return os; } diff --git a/modules/gapi/test/common/gapi_video_tests_common.hpp b/modules/gapi/test/common/gapi_video_tests_common.hpp index f3e7e5aa6c..0f755439c2 100644 --- a/modules/gapi/test/common/gapi_video_tests_common.hpp +++ b/modules/gapi/test/common/gapi_video_tests_common.hpp @@ -435,7 +435,7 @@ inline cv::GComputation runOCVnGAPIBuildOptFlowPyramid(TestFunctional&, BuildOpticalFlowPyramidTestOutput&, BuildOpticalFlowPyramidTestOutput&) { - GAPI_Assert(0 && "This function shouldn't be called without opencv_video"); + GAPI_Error("This function shouldn't be called without opencv_video"); } inline cv::GComputation runOCVnGAPIOptFlowLK(TestFunctional&, @@ -444,7 +444,7 @@ inline cv::GComputation runOCVnGAPIOptFlowLK(TestFunctional&, OptFlowLKTestOutput&, OptFlowLKTestOutput&) { - GAPI_Assert(0 && "This function shouldn't be called without opencv_video"); + GAPI_Error("This function shouldn't be called without opencv_video"); } inline cv::GComputation runOCVnGAPIOptFlowLKForPyr(TestFunctional&, @@ -454,7 +454,7 @@ inline cv::GComputation runOCVnGAPIOptFlowLKForPyr(TestFunctional&, OptFlowLKTestOutput&, OptFlowLKTestOutput&) { - GAPI_Assert(0 && "This function shouldn't be called without opencv_video"); + GAPI_Error("This function shouldn't be called without opencv_video"); } inline GComputation runOCVnGAPIOptFlowPipeline(TestFunctional&, @@ -463,7 +463,7 @@ inline GComputation runOCVnGAPIOptFlowPipeline(TestFunctional&, OptFlowLKTestOutput&, std::vector&) { - GAPI_Assert(0 && "This function shouldn't be called without opencv_video"); + GAPI_Error("This function shouldn't be called without opencv_video"); } #endif // HAVE_OPENCV_VIDEO @@ -481,7 +481,7 @@ inline std::ostream& operator<<(std::ostream& os, const BackgroundSubtractorType { CASE(TYPE_BS_MOG2); CASE(TYPE_BS_KNN); - default: GAPI_Assert(false && "unknown BackgroundSubtractor type"); + default: GAPI_Error("unknown BackgroundSubtractor type"); } #undef CASE return os; diff --git a/modules/gapi/test/executor/gtbbexecutor_internal_tests.cpp b/modules/gapi/test/executor/gtbbexecutor_internal_tests.cpp index e1b93af98b..766ec6b0b2 100644 --- a/modules/gapi/test/executor/gtbbexecutor_internal_tests.cpp +++ b/modules/gapi/test/executor/gtbbexecutor_internal_tests.cpp @@ -50,7 +50,7 @@ TEST(TBBExecutor, Basic) { }); q.push(&n); execute(q); - EXPECT_EQ(true, executed); + EXPECT_TRUE(executed); } TEST(TBBExecutor, SerialExecution) { @@ -117,8 +117,8 @@ TEST(TBBExecutor, AsyncBasic) { async_thread.join(); - EXPECT_EQ(true, callback_called); - EXPECT_EQ(true, master_was_blocked_until_callback_called); + EXPECT_TRUE(callback_called); + EXPECT_TRUE(master_was_blocked_until_callback_called); } TEST(TBBExecutor, Dependencies) { diff --git a/modules/gapi/test/gapi_async_test.cpp b/modules/gapi/test/gapi_async_test.cpp index 34a58e90d1..5a7194a17f 100644 --- a/modules/gapi/test/gapi_async_test.cpp +++ b/modules/gapi/test/gapi_async_test.cpp @@ -13,6 +13,7 @@ #include #include +#include namespace opencv_test { diff --git a/modules/gapi/test/infer/gapi_infer_ie_test.cpp b/modules/gapi/test/infer/gapi_infer_ie_test.cpp index 3741438373..f7dc23e1e6 100644 --- a/modules/gapi/test/infer/gapi_infer_ie_test.cpp +++ b/modules/gapi/test/infer/gapi_infer_ie_test.cpp @@ -304,7 +304,7 @@ struct InferWithReshape: public ::testing::Test { InferenceEngine::CNNNetwork net; InferenceEngine::Core plugin; - InferWithReshape() { + void SetUp() { // FIXME: it must be cv::imread(findDataFile("../dnn/grace_hopper_227.png", false)); m_in_mat = cv::Mat(cv::Size(320, 240), CV_8UC3); cv::randu(m_in_mat, 0, 255); @@ -386,6 +386,7 @@ struct InferWithReshapeNV12: public InferWithReshape { cv::Mat m_in_uv; cv::Mat m_in_y; void SetUp() { + InferWithReshape::SetUp(); cv::Size sz{320, 240}; m_in_y = cv::Mat{sz, CV_8UC1}; cv::randu(m_in_y, 0, 255); @@ -2956,6 +2957,105 @@ TEST(TestAgeGender, ThrowBlobAndInputPrecisionMismatchStreaming) } } +struct AgeGenderInferTest: public ::testing::Test { + cv::Mat m_in_mat; + cv::Mat m_gapi_age; + cv::Mat m_gapi_gender; + + cv::gimpl::ie::wrap::Plugin m_plugin; + IE::CNNNetwork m_net; + cv::gapi::ie::detail::ParamDesc m_params; + + using AGInfo = std::tuple; + G_API_NET(AgeGender, , "test-age-gender"); + + void SetUp() { + initDLDTDataPath(); + m_params.model_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); + m_params.weights_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); + m_params.device_id = "CPU"; + + m_plugin = cv::gimpl::ie::wrap::getPlugin(m_params); + m_net = cv::gimpl::ie::wrap::readNetwork(m_params); + setNetParameters(m_net); + + m_in_mat = cv::Mat(cv::Size(320, 240), CV_8UC3); + cv::randu(m_in_mat, 0, 255); + } + + cv::GComputation buildGraph() { + cv::GMat in, age, gender; + std::tie(age, gender) = cv::gapi::infer(in); + return cv::GComputation(cv::GIn(in), cv::GOut(age, gender)); + } + + void validate() { + IE::Blob::Ptr ie_age, ie_gender; + { + auto this_network = cv::gimpl::ie::wrap::loadNetwork(m_plugin, m_net, m_params); + auto infer_request = this_network.CreateInferRequest(); + infer_request.SetBlob("data", cv::gapi::ie::util::to_ie(m_in_mat)); + infer_request.Infer(); + ie_age = infer_request.GetBlob("age_conv3"); + ie_gender = infer_request.GetBlob("prob"); + } + // Validate with IE itself (avoid DNN module dependency here) + normAssert(cv::gapi::ie::util::to_ocv(ie_age), m_gapi_age, "Test age output" ); + normAssert(cv::gapi::ie::util::to_ocv(ie_gender), m_gapi_gender, "Test gender output"); + } +}; + +TEST_F(AgeGenderInferTest, SyncExecution) { + auto pp = cv::gapi::ie::Params { + m_params.model_path, m_params.weights_path, m_params.device_id + }.cfgOutputLayers({ "age_conv3", "prob" }) + .cfgInferMode(cv::gapi::ie::InferMode::Sync); + + buildGraph().apply(cv::gin(m_in_mat), cv::gout(m_gapi_age, m_gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + + validate(); +} + +TEST_F(AgeGenderInferTest, ThrowSyncWithNireqNotEqualToOne) { + auto pp = cv::gapi::ie::Params { + m_params.model_path, m_params.weights_path, m_params.device_id + }.cfgOutputLayers({ "age_conv3", "prob" }) + .cfgInferMode(cv::gapi::ie::InferMode::Sync) + .cfgNumRequests(4u); + + EXPECT_ANY_THROW(buildGraph().apply(cv::gin(m_in_mat), cv::gout(m_gapi_age, m_gapi_gender), + cv::compile_args(cv::gapi::networks(pp)))); +} + +TEST_F(AgeGenderInferTest, ChangeOutputPrecision) { + auto pp = cv::gapi::ie::Params { + m_params.model_path, m_params.weights_path, m_params.device_id + }.cfgOutputLayers({ "age_conv3", "prob" }) + .cfgOutputPrecision(CV_8U); + + for (auto it : m_net.getOutputsInfo()) { + it.second->setPrecision(IE::Precision::U8); + } + + buildGraph().apply(cv::gin(m_in_mat), cv::gout(m_gapi_age, m_gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + validate(); +} + +TEST_F(AgeGenderInferTest, ChangeSpecificOutputPrecison) { + auto pp = cv::gapi::ie::Params { + m_params.model_path, m_params.weights_path, m_params.device_id + }.cfgOutputLayers({ "age_conv3", "prob" }) + .cfgOutputPrecision({{"prob", CV_8U}}); + + m_net.getOutputsInfo().at("prob")->setPrecision(IE::Precision::U8); + + buildGraph().apply(cv::gin(m_in_mat), cv::gout(m_gapi_age, m_gapi_gender), + cv::compile_args(cv::gapi::networks(pp))); + validate(); +} + } // namespace opencv_test #endif // HAVE_INF_ENGINE diff --git a/modules/gapi/test/infer/gapi_infer_onnx_test.cpp b/modules/gapi/test/infer/gapi_infer_onnx_test.cpp index 5b1e6c9d4f..110ae7d964 100644 --- a/modules/gapi/test/infer/gapi_infer_onnx_test.cpp +++ b/modules/gapi/test/infer/gapi_infer_onnx_test.cpp @@ -114,7 +114,7 @@ inline int toCV(ONNXTensorElementDataType prec) { case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: return CV_32F; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: return CV_32S; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: return CV_32S; - default: GAPI_Assert(false && "Unsupported data type"); + default: GAPI_Error("Unsupported data type"); } return -1; } @@ -142,7 +142,7 @@ void copyFromONNX(Ort::Value &v, cv::Mat& mat) { [](int64_t el) { return static_cast(el); }); break; } - default: GAPI_Assert(false && "ONNX. Unsupported data type"); + default: GAPI_Error("ONNX. Unsupported data type"); } } diff --git a/modules/gapi/test/s11n/gapi_s11n_tests.cpp b/modules/gapi/test/s11n/gapi_s11n_tests.cpp index 22398ec92c..0dd88cd5b8 100644 --- a/modules/gapi/test/s11n/gapi_s11n_tests.cpp +++ b/modules/gapi/test/s11n/gapi_s11n_tests.cpp @@ -554,7 +554,7 @@ TEST_F(S11N_Basic, Test_RunArgs_MatScalar) { EXPECT_EQ(scalar, out_scalar); } break; default: - GAPI_Assert(false && "This value type is not supported!"); // ...maybe because of STANDALONE mode. + GAPI_Error("This value type is not supported!"); // ...maybe because of STANDALONE mode. break; } i++; @@ -587,7 +587,7 @@ TEST_F(S11N_Basic, Test_Bind_RunArgs_MatScalar) { EXPECT_EQ(out_scalar->val[2], scalar.val[2]); } break; default: - GAPI_Assert(false && "This value type is not supported!"); // ...maybe because of STANDALONE mode. + GAPI_Error("This value type is not supported!"); // ...maybe because of STANDALONE mode. break; } } diff --git a/modules/gapi/test/streaming/gapi_gstreamer_pipeline_facade_int_tests.cpp b/modules/gapi/test/streaming/gapi_gstreamer_pipeline_facade_int_tests.cpp index 7b809a8847..6a3fb38d5e 100644 --- a/modules/gapi/test/streaming/gapi_gstreamer_pipeline_facade_int_tests.cpp +++ b/modules/gapi/test/streaming/gapi_gstreamer_pipeline_facade_int_tests.cpp @@ -138,9 +138,9 @@ TEST(GStreamerPipelineFacadeTest, IsPlayingUnitTest) "video/x-raw,width=1920,height=1080,framerate=3/1 ! " "appsink name=sink2"); - EXPECT_EQ(false, pipelineFacade.isPlaying()); + EXPECT_FALSE(pipelineFacade.isPlaying()); pipelineFacade.play(); - EXPECT_EQ(true, pipelineFacade.isPlaying()); + EXPECT_TRUE(pipelineFacade.isPlaying()); } TEST(GStreamerPipelineFacadeTest, MTSafetyUnitTest) diff --git a/modules/gapi/test/streaming/gapi_streaming_tests.cpp b/modules/gapi/test/streaming/gapi_streaming_tests.cpp index 111f43bd19..c27ebe3ca2 100644 --- a/modules/gapi/test/streaming/gapi_streaming_tests.cpp +++ b/modules/gapi/test/streaming/gapi_streaming_tests.cpp @@ -49,7 +49,7 @@ std::ostream& operator<< (std::ostream &os, const KernelPackage &e) _C(OCL); _C(OCL_FLUID); #undef _C - default: GAPI_Assert(false); + default: GAPI_Error("InternalError"); } return os; } @@ -298,7 +298,7 @@ void checkPullOverload(const cv::Mat& ref, out_mat = *opt_mat; break; } - default: GAPI_Assert(false && "Incorrect type of Args"); + default: GAPI_Error("Incorrect type of Args"); } EXPECT_EQ(0., cv::norm(ref, out_mat, cv::NORM_INF)); @@ -2420,7 +2420,7 @@ TEST(GAPI_Streaming, TestPythonAPI) switch (args.index()) { case RunArgs::index_of(): out_args = util::get(args); break; - default: GAPI_Assert(false && "Incorrect type of return value"); + default: GAPI_Error("Incorrect type of return value"); } ASSERT_EQ(1u, out_args.size()); diff --git a/modules/gapi/test/streaming/gapi_streaming_vpl_core_test.cpp b/modules/gapi/test/streaming/gapi_streaming_vpl_core_test.cpp index d83b09d7d3..cabfe15d7e 100644 --- a/modules/gapi/test/streaming/gapi_streaming_vpl_core_test.cpp +++ b/modules/gapi/test/streaming/gapi_streaming_vpl_core_test.cpp @@ -8,6 +8,7 @@ #include "../test_precomp.hpp" #include "../common/gapi_streaming_tests_common.hpp" +#include "../common/gapi_tests_common.hpp" #include #include @@ -29,6 +30,7 @@ #ifdef HAVE_ONEVPL #include +#include #include "streaming/onevpl/file_data_provider.hpp" #include "streaming/onevpl/cfg_param_device_selector.hpp" @@ -327,6 +329,72 @@ TEST(OneVPL_Source_CPU_FrameAdapter, InitFrameAdapter) EXPECT_TRUE(0 == surf->get_locks_count()); } +TEST(OneVPL_Source_Default_Source_With_OCL_Backend, Accuracy) +{ + using namespace cv::gapi::wip::onevpl; + + auto create_from_string = [](const std::string& line){ + std::string::size_type name_endline_pos = line.find(':'); + std::string name = line.substr(0, name_endline_pos); + std::string value = line.substr(name_endline_pos + 1); + return CfgParam::create(name, value); + }; + + std::vector source_cfgs; + source_cfgs.push_back(create_from_string("mfxImplDescription.AccelerationMode:MFX_ACCEL_MODE_VIA_D3D11")); + + // Create VPL-based source + std::shared_ptr default_device_selector = getDefaultDeviceSelector(source_cfgs); + + cv::gapi::wip::IStreamSource::Ptr source; + cv::gapi::wip::IStreamSource::Ptr source_cpu; + + auto input = findDataFile("cv/video/768x576.avi"); + try { + source = cv::gapi::wip::make_onevpl_src(input, source_cfgs, default_device_selector); + source_cpu = cv::gapi::wip::make_onevpl_src(input, source_cfgs, default_device_selector); + } catch(...) { + throw SkipTestException("Video file can not be opened"); + } + + // Build the graph w/ OCL backend + cv::GFrame in; // input frame from VPL source + auto bgr_gmat = cv::gapi::streaming::BGR(in); // conversion from VPL source frame to BGR UMat + auto out = cv::gapi::blur(bgr_gmat, cv::Size(4,4)); // ocl kernel of blur operation + + cv::GStreamingCompiled pipeline = cv::GComputation(cv::GIn(in), cv::GOut(out)) + .compileStreaming(std::move(cv::compile_args(cv::gapi::core::ocl::kernels()))); + pipeline.setSource(std::move(source)); + + cv::GStreamingCompiled pipeline_cpu = cv::GComputation(cv::GIn(in), cv::GOut(out)) + .compileStreaming(std::move(cv::compile_args(cv::gapi::core::cpu::kernels()))); + pipeline_cpu.setSource(std::move(source_cpu)); + + // The execution part + cv::Mat out_mat; + std::vector ocl_mats, cpu_mats; + + // Run the pipelines + pipeline.start(); + while (pipeline.pull(cv::gout(out_mat))) + { + ocl_mats.push_back(out_mat); + } + + pipeline_cpu.start(); + while (pipeline_cpu.pull(cv::gout(out_mat))) + { + cpu_mats.push_back(out_mat); + } + + // Compare results + // FIXME: investigate why 2 sources produce different number of frames sometimes + for (size_t i = 0; i < std::min(ocl_mats.size(), cpu_mats.size()); ++i) + { + EXPECT_TRUE(AbsTolerance(1).to_compare_obj()(ocl_mats[i], cpu_mats[i])); + } +} + TEST(OneVPL_Source_CPU_Accelerator, InitDestroy) { using cv::gapi::wip::onevpl::VPLCPUAccelerationPolicy; diff --git a/modules/highgui/src/files_Qt/Material/106.png b/modules/highgui/src/files_Qt/Material/106.png new file mode 100644 index 0000000000..e5c3a11d18 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/106.png differ diff --git a/modules/highgui/src/files_Qt/Material/107.png b/modules/highgui/src/files_Qt/Material/107.png new file mode 100644 index 0000000000..77088f3d1d Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/107.png differ diff --git a/modules/highgui/src/files_Qt/Material/19.png b/modules/highgui/src/files_Qt/Material/19.png new file mode 100644 index 0000000000..88ca017dc1 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/19.png differ diff --git a/modules/highgui/src/files_Qt/Material/23.png b/modules/highgui/src/files_Qt/Material/23.png new file mode 100644 index 0000000000..2c52059bd2 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/23.png differ diff --git a/modules/highgui/src/files_Qt/Material/24.png b/modules/highgui/src/files_Qt/Material/24.png new file mode 100644 index 0000000000..5099983b91 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/24.png differ diff --git a/modules/highgui/src/files_Qt/Material/27.png b/modules/highgui/src/files_Qt/Material/27.png new file mode 100644 index 0000000000..5d37706030 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/27.png differ diff --git a/modules/highgui/src/files_Qt/Material/28.png b/modules/highgui/src/files_Qt/Material/28.png new file mode 100644 index 0000000000..199f74364b Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/28.png differ diff --git a/modules/highgui/src/files_Qt/Material/38.png b/modules/highgui/src/files_Qt/Material/38.png new file mode 100644 index 0000000000..551d42979b Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/38.png differ diff --git a/modules/highgui/src/files_Qt/Material/43.png b/modules/highgui/src/files_Qt/Material/43.png new file mode 100644 index 0000000000..9f8fe8a2b7 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/43.png differ diff --git a/modules/highgui/src/files_Qt/Material/61.png b/modules/highgui/src/files_Qt/Material/61.png new file mode 100644 index 0000000000..50ff1209b3 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/61.png differ diff --git a/modules/highgui/src/files_Qt/Material/7.png b/modules/highgui/src/files_Qt/Material/7.png new file mode 100644 index 0000000000..270467feb2 Binary files /dev/null and b/modules/highgui/src/files_Qt/Material/7.png differ diff --git a/modules/highgui/src/files_Qt/Material/LICENSE b/modules/highgui/src/files_Qt/Material/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/modules/highgui/src/files_Qt/Material/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/modules/highgui/src/files_Qt/Material/Readme.txt b/modules/highgui/src/files_Qt/Material/Readme.txt new file mode 100644 index 0000000000..8d7382be28 --- /dev/null +++ b/modules/highgui/src/files_Qt/Material/Readme.txt @@ -0,0 +1 @@ +Icons for Google Material Design: https://github.com/google/material-design-icons/ \ No newline at end of file diff --git a/modules/highgui/src/files_Qt/Milky/48/1.png b/modules/highgui/src/files_Qt/Milky/48/1.png deleted file mode 100644 index 69b4dee0a5..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/1.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/10.png b/modules/highgui/src/files_Qt/Milky/48/10.png deleted file mode 100644 index 34185e1fa1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/10.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/100.png b/modules/highgui/src/files_Qt/Milky/48/100.png deleted file mode 100644 index a49d1dadc2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/100.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/101.png b/modules/highgui/src/files_Qt/Milky/48/101.png deleted file mode 100644 index 3a95d68c0b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/101.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/102.png b/modules/highgui/src/files_Qt/Milky/48/102.png deleted file mode 100644 index cb6bb1d1bd..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/102.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/103.png b/modules/highgui/src/files_Qt/Milky/48/103.png deleted file mode 100644 index bf0c6617b0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/103.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/104.png b/modules/highgui/src/files_Qt/Milky/48/104.png deleted file mode 100644 index 530ffa1d9f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/104.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/105.png b/modules/highgui/src/files_Qt/Milky/48/105.png deleted file mode 100644 index c0848cb6ca..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/105.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/106.png b/modules/highgui/src/files_Qt/Milky/48/106.png deleted file mode 100644 index e8d1f04742..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/106.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/107.png b/modules/highgui/src/files_Qt/Milky/48/107.png deleted file mode 100644 index 7a5a022d9b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/107.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/108.png b/modules/highgui/src/files_Qt/Milky/48/108.png deleted file mode 100644 index d7f27ab571..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/108.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/109.png b/modules/highgui/src/files_Qt/Milky/48/109.png deleted file mode 100644 index ffb93dad15..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/109.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/11.png b/modules/highgui/src/files_Qt/Milky/48/11.png deleted file mode 100644 index 565ad498e9..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/11.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/110.png b/modules/highgui/src/files_Qt/Milky/48/110.png deleted file mode 100644 index 82c6dd1e3d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/110.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/111.png b/modules/highgui/src/files_Qt/Milky/48/111.png deleted file mode 100644 index 5592dd8721..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/111.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/112.png b/modules/highgui/src/files_Qt/Milky/48/112.png deleted file mode 100644 index cc91c94caf..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/112.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/113.png b/modules/highgui/src/files_Qt/Milky/48/113.png deleted file mode 100644 index 143ea905d2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/113.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/114.png b/modules/highgui/src/files_Qt/Milky/48/114.png deleted file mode 100644 index a44368aba2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/114.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/115.png b/modules/highgui/src/files_Qt/Milky/48/115.png deleted file mode 100644 index f8226fbea5..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/115.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/116.png b/modules/highgui/src/files_Qt/Milky/48/116.png deleted file mode 100644 index 55e7b74d52..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/116.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/117.png b/modules/highgui/src/files_Qt/Milky/48/117.png deleted file mode 100644 index 54db487ebf..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/117.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/118.png b/modules/highgui/src/files_Qt/Milky/48/118.png deleted file mode 100644 index 6693e842c2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/118.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/119.png b/modules/highgui/src/files_Qt/Milky/48/119.png deleted file mode 100644 index 683f36620e..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/119.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/12.png b/modules/highgui/src/files_Qt/Milky/48/12.png deleted file mode 100644 index 72712fe9e1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/12.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/120.png b/modules/highgui/src/files_Qt/Milky/48/120.png deleted file mode 100644 index fe0221b142..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/120.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/121.png b/modules/highgui/src/files_Qt/Milky/48/121.png deleted file mode 100644 index 72fbc036a9..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/121.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/122.png b/modules/highgui/src/files_Qt/Milky/48/122.png deleted file mode 100644 index 6182b8f6b7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/122.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/123.png b/modules/highgui/src/files_Qt/Milky/48/123.png deleted file mode 100644 index aaa02f519d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/123.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/124.png b/modules/highgui/src/files_Qt/Milky/48/124.png deleted file mode 100644 index 273400a027..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/124.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/125.png b/modules/highgui/src/files_Qt/Milky/48/125.png deleted file mode 100644 index 4e0573ca96..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/125.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/126.png b/modules/highgui/src/files_Qt/Milky/48/126.png deleted file mode 100644 index fc4f11dca4..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/126.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/127.png b/modules/highgui/src/files_Qt/Milky/48/127.png deleted file mode 100644 index a2a7af2a8c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/127.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/128.png b/modules/highgui/src/files_Qt/Milky/48/128.png deleted file mode 100644 index f811ee479b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/128.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/129.png b/modules/highgui/src/files_Qt/Milky/48/129.png deleted file mode 100644 index 77e7ebd04b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/129.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/13.png b/modules/highgui/src/files_Qt/Milky/48/13.png deleted file mode 100644 index 6cb5c5dca7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/13.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/130.png b/modules/highgui/src/files_Qt/Milky/48/130.png deleted file mode 100644 index c1fdd36faf..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/130.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/131.png b/modules/highgui/src/files_Qt/Milky/48/131.png deleted file mode 100644 index 858fde1610..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/131.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/14.png b/modules/highgui/src/files_Qt/Milky/48/14.png deleted file mode 100644 index 8d217be464..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/14.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/15.png b/modules/highgui/src/files_Qt/Milky/48/15.png deleted file mode 100644 index 524ab7f099..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/15.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/16.png b/modules/highgui/src/files_Qt/Milky/48/16.png deleted file mode 100644 index 2e011c7396..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/16.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/17.png b/modules/highgui/src/files_Qt/Milky/48/17.png deleted file mode 100644 index 4a7e5de411..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/17.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/18.png b/modules/highgui/src/files_Qt/Milky/48/18.png deleted file mode 100644 index 43f5405f5c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/18.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/19.png b/modules/highgui/src/files_Qt/Milky/48/19.png deleted file mode 100644 index 203510ddd4..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/19.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/2.png b/modules/highgui/src/files_Qt/Milky/48/2.png deleted file mode 100644 index 8f4903eeab..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/2.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/20.png b/modules/highgui/src/files_Qt/Milky/48/20.png deleted file mode 100644 index 1a591ca419..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/20.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/21.png b/modules/highgui/src/files_Qt/Milky/48/21.png deleted file mode 100644 index e65e4acd36..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/21.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/22.png b/modules/highgui/src/files_Qt/Milky/48/22.png deleted file mode 100644 index a81aca1913..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/22.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/23.png b/modules/highgui/src/files_Qt/Milky/48/23.png deleted file mode 100644 index ab9e60cfcb..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/23.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/24.png b/modules/highgui/src/files_Qt/Milky/48/24.png deleted file mode 100644 index 4e5629cb45..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/24.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/25.png b/modules/highgui/src/files_Qt/Milky/48/25.png deleted file mode 100644 index da93a5962d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/25.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/26.png b/modules/highgui/src/files_Qt/Milky/48/26.png deleted file mode 100644 index 6ba5d6c106..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/26.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/27.png b/modules/highgui/src/files_Qt/Milky/48/27.png deleted file mode 100644 index a14e20420b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/27.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/28.png b/modules/highgui/src/files_Qt/Milky/48/28.png deleted file mode 100644 index f0df2d35e8..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/28.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/29.png b/modules/highgui/src/files_Qt/Milky/48/29.png deleted file mode 100644 index 6d79d929fc..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/29.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/3.png b/modules/highgui/src/files_Qt/Milky/48/3.png deleted file mode 100644 index 40d5946680..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/3.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/30.png b/modules/highgui/src/files_Qt/Milky/48/30.png deleted file mode 100644 index 44037a72f4..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/30.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/31.png b/modules/highgui/src/files_Qt/Milky/48/31.png deleted file mode 100644 index b9d421337d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/31.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/32.png b/modules/highgui/src/files_Qt/Milky/48/32.png deleted file mode 100644 index d72749c50d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/32.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/33.png b/modules/highgui/src/files_Qt/Milky/48/33.png deleted file mode 100644 index 85bb86ff02..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/33.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/34.png b/modules/highgui/src/files_Qt/Milky/48/34.png deleted file mode 100644 index fd095ee77b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/34.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/35.png b/modules/highgui/src/files_Qt/Milky/48/35.png deleted file mode 100644 index abb64d6127..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/35.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/36.png b/modules/highgui/src/files_Qt/Milky/48/36.png deleted file mode 100644 index a6d75ffd7b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/36.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/37.png b/modules/highgui/src/files_Qt/Milky/48/37.png deleted file mode 100644 index fc9f361ec0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/37.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/38.png b/modules/highgui/src/files_Qt/Milky/48/38.png deleted file mode 100644 index 81cd7e139b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/38.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/39.png b/modules/highgui/src/files_Qt/Milky/48/39.png deleted file mode 100644 index d76effcd79..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/39.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/4.png b/modules/highgui/src/files_Qt/Milky/48/4.png deleted file mode 100644 index a6a8d07a74..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/4.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/40.png b/modules/highgui/src/files_Qt/Milky/48/40.png deleted file mode 100644 index f17ad6aa10..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/40.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/41.png b/modules/highgui/src/files_Qt/Milky/48/41.png deleted file mode 100644 index 4553c04588..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/41.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/42.png b/modules/highgui/src/files_Qt/Milky/48/42.png deleted file mode 100644 index fb5f9a2a6c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/42.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/43.png b/modules/highgui/src/files_Qt/Milky/48/43.png deleted file mode 100644 index 3c958420b8..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/43.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/44.png b/modules/highgui/src/files_Qt/Milky/48/44.png deleted file mode 100644 index ef3c114d45..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/44.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/45.png b/modules/highgui/src/files_Qt/Milky/48/45.png deleted file mode 100644 index a77fb9e14c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/45.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/46.png b/modules/highgui/src/files_Qt/Milky/48/46.png deleted file mode 100644 index ead4d3e648..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/46.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/47.png b/modules/highgui/src/files_Qt/Milky/48/47.png deleted file mode 100644 index e5a07ce560..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/47.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/48.png b/modules/highgui/src/files_Qt/Milky/48/48.png deleted file mode 100644 index 6092c0c3c2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/48.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/49.png b/modules/highgui/src/files_Qt/Milky/48/49.png deleted file mode 100644 index 120e40a728..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/49.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/5.png b/modules/highgui/src/files_Qt/Milky/48/5.png deleted file mode 100644 index 46df26f5e4..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/5.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/50.png b/modules/highgui/src/files_Qt/Milky/48/50.png deleted file mode 100644 index 39f6d90b91..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/50.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/51.png b/modules/highgui/src/files_Qt/Milky/48/51.png deleted file mode 100644 index b5b77eb9ef..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/51.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/52.png b/modules/highgui/src/files_Qt/Milky/48/52.png deleted file mode 100644 index 2467870d70..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/52.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/53.png b/modules/highgui/src/files_Qt/Milky/48/53.png deleted file mode 100644 index a13b5687fd..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/53.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/54.png b/modules/highgui/src/files_Qt/Milky/48/54.png deleted file mode 100644 index 55ad0090a1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/54.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/55.png b/modules/highgui/src/files_Qt/Milky/48/55.png deleted file mode 100644 index 2167215450..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/55.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/56.png b/modules/highgui/src/files_Qt/Milky/48/56.png deleted file mode 100644 index ef8920b00a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/56.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/57.png b/modules/highgui/src/files_Qt/Milky/48/57.png deleted file mode 100644 index 10dd05f60a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/57.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/58.png b/modules/highgui/src/files_Qt/Milky/48/58.png deleted file mode 100644 index 9bdf543156..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/58.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/59.png b/modules/highgui/src/files_Qt/Milky/48/59.png deleted file mode 100644 index 287cd077cd..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/59.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/6.png b/modules/highgui/src/files_Qt/Milky/48/6.png deleted file mode 100644 index 72853e6856..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/6.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/60.png b/modules/highgui/src/files_Qt/Milky/48/60.png deleted file mode 100644 index 53250defd8..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/60.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/61.png b/modules/highgui/src/files_Qt/Milky/48/61.png deleted file mode 100644 index 5c4a464be6..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/61.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/62.png b/modules/highgui/src/files_Qt/Milky/48/62.png deleted file mode 100644 index 7434c4959d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/62.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/63.png b/modules/highgui/src/files_Qt/Milky/48/63.png deleted file mode 100644 index fa0383b449..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/63.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/64.png b/modules/highgui/src/files_Qt/Milky/48/64.png deleted file mode 100644 index df5cee046c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/64.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/65.png b/modules/highgui/src/files_Qt/Milky/48/65.png deleted file mode 100644 index 863bec1321..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/65.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/66.png b/modules/highgui/src/files_Qt/Milky/48/66.png deleted file mode 100644 index 58fba6bd94..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/66.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/67.png b/modules/highgui/src/files_Qt/Milky/48/67.png deleted file mode 100644 index de3fefa779..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/67.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/68.png b/modules/highgui/src/files_Qt/Milky/48/68.png deleted file mode 100644 index c9b8a72218..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/68.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/69.png b/modules/highgui/src/files_Qt/Milky/48/69.png deleted file mode 100644 index e0ce5b8b1b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/69.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/7.png b/modules/highgui/src/files_Qt/Milky/48/7.png deleted file mode 100644 index 832772b59d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/7.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/70.png b/modules/highgui/src/files_Qt/Milky/48/70.png deleted file mode 100644 index 3acf30c20e..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/70.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/71.png b/modules/highgui/src/files_Qt/Milky/48/71.png deleted file mode 100644 index bf3c32a23d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/71.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/72.png b/modules/highgui/src/files_Qt/Milky/48/72.png deleted file mode 100644 index b821a98bb8..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/72.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/73.png b/modules/highgui/src/files_Qt/Milky/48/73.png deleted file mode 100644 index e5ca83924e..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/73.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/74.png b/modules/highgui/src/files_Qt/Milky/48/74.png deleted file mode 100644 index c275e1d013..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/74.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/75.png b/modules/highgui/src/files_Qt/Milky/48/75.png deleted file mode 100644 index 392792a72a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/75.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/76.png b/modules/highgui/src/files_Qt/Milky/48/76.png deleted file mode 100644 index cc2d75d7c0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/76.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/77.png b/modules/highgui/src/files_Qt/Milky/48/77.png deleted file mode 100644 index 80a0cace67..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/77.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/78.png b/modules/highgui/src/files_Qt/Milky/48/78.png deleted file mode 100644 index 548115c6e8..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/78.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/79.png b/modules/highgui/src/files_Qt/Milky/48/79.png deleted file mode 100644 index 9e311fcb95..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/79.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/8.png b/modules/highgui/src/files_Qt/Milky/48/8.png deleted file mode 100644 index 7301dccfda..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/8.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/80.png b/modules/highgui/src/files_Qt/Milky/48/80.png deleted file mode 100644 index 44270bc96d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/80.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/81.png b/modules/highgui/src/files_Qt/Milky/48/81.png deleted file mode 100644 index d9018259a3..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/81.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/82.png b/modules/highgui/src/files_Qt/Milky/48/82.png deleted file mode 100644 index feb4ea2f6d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/82.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/83.png b/modules/highgui/src/files_Qt/Milky/48/83.png deleted file mode 100644 index 01abaad499..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/83.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/84.png b/modules/highgui/src/files_Qt/Milky/48/84.png deleted file mode 100644 index 230c12fc70..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/84.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/85.png b/modules/highgui/src/files_Qt/Milky/48/85.png deleted file mode 100644 index 27a0be088a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/85.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/86.png b/modules/highgui/src/files_Qt/Milky/48/86.png deleted file mode 100644 index 915e31c246..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/86.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/87.png b/modules/highgui/src/files_Qt/Milky/48/87.png deleted file mode 100644 index b1bc27b7c0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/87.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/88.png b/modules/highgui/src/files_Qt/Milky/48/88.png deleted file mode 100644 index cbdca6af47..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/88.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/89.png b/modules/highgui/src/files_Qt/Milky/48/89.png deleted file mode 100644 index afcbf38181..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/89.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/9.png b/modules/highgui/src/files_Qt/Milky/48/9.png deleted file mode 100644 index 4ece823fe3..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/9.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/90.png b/modules/highgui/src/files_Qt/Milky/48/90.png deleted file mode 100644 index ae4acb35a7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/90.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/91.png b/modules/highgui/src/files_Qt/Milky/48/91.png deleted file mode 100644 index d83fe86554..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/91.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/92.png b/modules/highgui/src/files_Qt/Milky/48/92.png deleted file mode 100644 index 48e5a942a5..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/92.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/93.png b/modules/highgui/src/files_Qt/Milky/48/93.png deleted file mode 100644 index 94eb7aca9a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/93.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/94.png b/modules/highgui/src/files_Qt/Milky/48/94.png deleted file mode 100644 index 1e6999cbf4..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/94.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/95.png b/modules/highgui/src/files_Qt/Milky/48/95.png deleted file mode 100644 index c8c8f163be..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/95.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/96.png b/modules/highgui/src/files_Qt/Milky/48/96.png deleted file mode 100644 index daad4ea7ce..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/96.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/97.png b/modules/highgui/src/files_Qt/Milky/48/97.png deleted file mode 100644 index b76ec545b0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/97.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/98.png b/modules/highgui/src/files_Qt/Milky/48/98.png deleted file mode 100644 index 5694611ab0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/98.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/48/99.png b/modules/highgui/src/files_Qt/Milky/48/99.png deleted file mode 100644 index 2cbc952177..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/48/99.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/1.png b/modules/highgui/src/files_Qt/Milky/64/1.png deleted file mode 100644 index 36a19f6e0e..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/1.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/10.png b/modules/highgui/src/files_Qt/Milky/64/10.png deleted file mode 100644 index 28e0be1f5f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/10.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/100.png b/modules/highgui/src/files_Qt/Milky/64/100.png deleted file mode 100644 index fbcfbaabbb..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/100.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/101.png b/modules/highgui/src/files_Qt/Milky/64/101.png deleted file mode 100644 index 03fe6ff940..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/101.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/102.png b/modules/highgui/src/files_Qt/Milky/64/102.png deleted file mode 100644 index 22d7c0e879..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/102.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/103.png b/modules/highgui/src/files_Qt/Milky/64/103.png deleted file mode 100644 index cb66ba2761..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/103.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/104.png b/modules/highgui/src/files_Qt/Milky/64/104.png deleted file mode 100644 index a394b07b24..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/104.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/105.png b/modules/highgui/src/files_Qt/Milky/64/105.png deleted file mode 100644 index ddbcc04c2c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/105.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/106.png b/modules/highgui/src/files_Qt/Milky/64/106.png deleted file mode 100644 index 4ac2b56418..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/106.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/107.png b/modules/highgui/src/files_Qt/Milky/64/107.png deleted file mode 100644 index dd8443c6a6..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/107.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/108.png b/modules/highgui/src/files_Qt/Milky/64/108.png deleted file mode 100644 index a87579a454..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/108.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/109.png b/modules/highgui/src/files_Qt/Milky/64/109.png deleted file mode 100644 index c42d2d5f3f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/109.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/11.png b/modules/highgui/src/files_Qt/Milky/64/11.png deleted file mode 100644 index 1002f34573..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/11.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/110.png b/modules/highgui/src/files_Qt/Milky/64/110.png deleted file mode 100644 index d75974a6e2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/110.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/111.png b/modules/highgui/src/files_Qt/Milky/64/111.png deleted file mode 100644 index c61e685766..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/111.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/112.png b/modules/highgui/src/files_Qt/Milky/64/112.png deleted file mode 100644 index 9dda7f92a2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/112.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/113.png b/modules/highgui/src/files_Qt/Milky/64/113.png deleted file mode 100644 index 2aa9474e98..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/113.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/114.png b/modules/highgui/src/files_Qt/Milky/64/114.png deleted file mode 100644 index e06723f3d1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/114.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/115.png b/modules/highgui/src/files_Qt/Milky/64/115.png deleted file mode 100644 index fcb11c78f2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/115.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/116.png b/modules/highgui/src/files_Qt/Milky/64/116.png deleted file mode 100644 index a3227c7bf0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/116.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/117.png b/modules/highgui/src/files_Qt/Milky/64/117.png deleted file mode 100644 index 91991ab672..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/117.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/118.png b/modules/highgui/src/files_Qt/Milky/64/118.png deleted file mode 100644 index 50c4cfc4c6..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/118.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/119.png b/modules/highgui/src/files_Qt/Milky/64/119.png deleted file mode 100644 index d8974f32a1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/119.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/12.png b/modules/highgui/src/files_Qt/Milky/64/12.png deleted file mode 100644 index 822bc42e23..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/12.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/120.png b/modules/highgui/src/files_Qt/Milky/64/120.png deleted file mode 100644 index feba57f381..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/120.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/121.png b/modules/highgui/src/files_Qt/Milky/64/121.png deleted file mode 100644 index 01679abe84..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/121.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/122.png b/modules/highgui/src/files_Qt/Milky/64/122.png deleted file mode 100644 index 4ee45a6d1f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/122.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/123.png b/modules/highgui/src/files_Qt/Milky/64/123.png deleted file mode 100644 index eaeb829a85..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/123.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/124.png b/modules/highgui/src/files_Qt/Milky/64/124.png deleted file mode 100644 index 3babae1199..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/124.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/125.png b/modules/highgui/src/files_Qt/Milky/64/125.png deleted file mode 100644 index 35d91e0d6f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/125.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/126.png b/modules/highgui/src/files_Qt/Milky/64/126.png deleted file mode 100644 index d8715004ad..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/126.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/127.png b/modules/highgui/src/files_Qt/Milky/64/127.png deleted file mode 100644 index bf1f7739e0..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/127.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/128.png b/modules/highgui/src/files_Qt/Milky/64/128.png deleted file mode 100644 index b9a903cb82..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/128.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/129.png b/modules/highgui/src/files_Qt/Milky/64/129.png deleted file mode 100644 index 000826f463..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/129.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/13.png b/modules/highgui/src/files_Qt/Milky/64/13.png deleted file mode 100644 index c4ae0c42fa..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/13.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/130.png b/modules/highgui/src/files_Qt/Milky/64/130.png deleted file mode 100644 index fbadb80bfa..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/130.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/131.png b/modules/highgui/src/files_Qt/Milky/64/131.png deleted file mode 100644 index 636052f5a3..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/131.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/14.png b/modules/highgui/src/files_Qt/Milky/64/14.png deleted file mode 100644 index ce96e04ceb..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/14.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/15.png b/modules/highgui/src/files_Qt/Milky/64/15.png deleted file mode 100644 index eb9dca50a9..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/15.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/16.png b/modules/highgui/src/files_Qt/Milky/64/16.png deleted file mode 100644 index 9acf8c20bd..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/16.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/17.png b/modules/highgui/src/files_Qt/Milky/64/17.png deleted file mode 100644 index 18d286ab22..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/17.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/18.png b/modules/highgui/src/files_Qt/Milky/64/18.png deleted file mode 100644 index 3b76256bab..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/18.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/19.png b/modules/highgui/src/files_Qt/Milky/64/19.png deleted file mode 100644 index b0de5da46b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/19.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/2.png b/modules/highgui/src/files_Qt/Milky/64/2.png deleted file mode 100644 index 3523cc6706..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/2.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/20.png b/modules/highgui/src/files_Qt/Milky/64/20.png deleted file mode 100644 index fbd1fee4ff..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/20.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/21.png b/modules/highgui/src/files_Qt/Milky/64/21.png deleted file mode 100644 index 4c94f54422..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/21.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/22.png b/modules/highgui/src/files_Qt/Milky/64/22.png deleted file mode 100644 index f7f0c3d9ec..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/22.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/23.png b/modules/highgui/src/files_Qt/Milky/64/23.png deleted file mode 100644 index 11b0899f5c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/23.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/24.png b/modules/highgui/src/files_Qt/Milky/64/24.png deleted file mode 100644 index 94a8e94969..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/24.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/25.png b/modules/highgui/src/files_Qt/Milky/64/25.png deleted file mode 100644 index 4e650dc817..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/25.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/26.png b/modules/highgui/src/files_Qt/Milky/64/26.png deleted file mode 100644 index 6dec633836..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/26.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/27.png b/modules/highgui/src/files_Qt/Milky/64/27.png deleted file mode 100644 index 1ab2410c77..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/27.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/28.png b/modules/highgui/src/files_Qt/Milky/64/28.png deleted file mode 100644 index 7d4d62435f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/28.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/29.png b/modules/highgui/src/files_Qt/Milky/64/29.png deleted file mode 100644 index 74a499650a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/29.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/3.png b/modules/highgui/src/files_Qt/Milky/64/3.png deleted file mode 100644 index be382224b9..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/3.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/30.png b/modules/highgui/src/files_Qt/Milky/64/30.png deleted file mode 100644 index d1fbb72085..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/30.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/31.png b/modules/highgui/src/files_Qt/Milky/64/31.png deleted file mode 100644 index 70d95908f7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/31.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/32.png b/modules/highgui/src/files_Qt/Milky/64/32.png deleted file mode 100644 index 11b66ad441..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/32.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/33.png b/modules/highgui/src/files_Qt/Milky/64/33.png deleted file mode 100644 index c76151cc32..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/33.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/34.png b/modules/highgui/src/files_Qt/Milky/64/34.png deleted file mode 100644 index 1b009aac09..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/34.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/35.png b/modules/highgui/src/files_Qt/Milky/64/35.png deleted file mode 100644 index c9b408445f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/35.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/36.png b/modules/highgui/src/files_Qt/Milky/64/36.png deleted file mode 100644 index d5aad30b69..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/36.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/37.png b/modules/highgui/src/files_Qt/Milky/64/37.png deleted file mode 100644 index b0898c8cc9..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/37.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/38.png b/modules/highgui/src/files_Qt/Milky/64/38.png deleted file mode 100644 index 0aa9224a6b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/38.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/39.png b/modules/highgui/src/files_Qt/Milky/64/39.png deleted file mode 100644 index f67ae06b5e..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/39.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/4.png b/modules/highgui/src/files_Qt/Milky/64/4.png deleted file mode 100644 index eb1a5facd2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/4.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/40.png b/modules/highgui/src/files_Qt/Milky/64/40.png deleted file mode 100644 index f39b08537f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/40.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/41.png b/modules/highgui/src/files_Qt/Milky/64/41.png deleted file mode 100644 index 3061701f56..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/41.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/42.png b/modules/highgui/src/files_Qt/Milky/64/42.png deleted file mode 100644 index 1b979766a7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/42.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/43.png b/modules/highgui/src/files_Qt/Milky/64/43.png deleted file mode 100644 index f3d9e5d435..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/43.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/44.png b/modules/highgui/src/files_Qt/Milky/64/44.png deleted file mode 100644 index a549bfe0cb..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/44.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/45.png b/modules/highgui/src/files_Qt/Milky/64/45.png deleted file mode 100644 index 123fa1a3dd..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/45.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/46.png b/modules/highgui/src/files_Qt/Milky/64/46.png deleted file mode 100644 index 4347e52916..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/46.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/47.png b/modules/highgui/src/files_Qt/Milky/64/47.png deleted file mode 100644 index 87502ceed3..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/47.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/48.png b/modules/highgui/src/files_Qt/Milky/64/48.png deleted file mode 100644 index 6cc7164b61..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/48.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/49.png b/modules/highgui/src/files_Qt/Milky/64/49.png deleted file mode 100644 index d6686e9cd1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/49.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/5.png b/modules/highgui/src/files_Qt/Milky/64/5.png deleted file mode 100644 index 9d3c5a3683..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/5.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/50.png b/modules/highgui/src/files_Qt/Milky/64/50.png deleted file mode 100644 index 0bc5d92887..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/50.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/51.png b/modules/highgui/src/files_Qt/Milky/64/51.png deleted file mode 100644 index ea40b9386c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/51.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/52.png b/modules/highgui/src/files_Qt/Milky/64/52.png deleted file mode 100644 index 27c41bd615..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/52.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/53.png b/modules/highgui/src/files_Qt/Milky/64/53.png deleted file mode 100644 index 8c88aaab2a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/53.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/54.png b/modules/highgui/src/files_Qt/Milky/64/54.png deleted file mode 100644 index 0f8c5cbb68..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/54.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/55.png b/modules/highgui/src/files_Qt/Milky/64/55.png deleted file mode 100644 index 533f2dee64..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/55.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/56.png b/modules/highgui/src/files_Qt/Milky/64/56.png deleted file mode 100644 index 28abf7af20..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/56.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/57.png b/modules/highgui/src/files_Qt/Milky/64/57.png deleted file mode 100644 index fd98c7ee00..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/57.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/58.png b/modules/highgui/src/files_Qt/Milky/64/58.png deleted file mode 100644 index 6142acc806..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/58.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/59.png b/modules/highgui/src/files_Qt/Milky/64/59.png deleted file mode 100644 index 2ae78f3235..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/59.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/6.png b/modules/highgui/src/files_Qt/Milky/64/6.png deleted file mode 100644 index 5ae8a8e67d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/6.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/60.png b/modules/highgui/src/files_Qt/Milky/64/60.png deleted file mode 100644 index 71adc818f7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/60.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/61.png b/modules/highgui/src/files_Qt/Milky/64/61.png deleted file mode 100644 index 09b12bdc9c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/61.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/62.png b/modules/highgui/src/files_Qt/Milky/64/62.png deleted file mode 100644 index 374d4c5050..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/62.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/63.png b/modules/highgui/src/files_Qt/Milky/64/63.png deleted file mode 100644 index ac4f07af60..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/63.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/64.png b/modules/highgui/src/files_Qt/Milky/64/64.png deleted file mode 100644 index 79e38d1052..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/64.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/65.png b/modules/highgui/src/files_Qt/Milky/64/65.png deleted file mode 100644 index f4e9a2880c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/65.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/66.png b/modules/highgui/src/files_Qt/Milky/64/66.png deleted file mode 100644 index b98c32d065..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/66.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/67.png b/modules/highgui/src/files_Qt/Milky/64/67.png deleted file mode 100644 index 7251a6710d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/67.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/68.png b/modules/highgui/src/files_Qt/Milky/64/68.png deleted file mode 100644 index 894c9aab32..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/68.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/69.png b/modules/highgui/src/files_Qt/Milky/64/69.png deleted file mode 100644 index 2312bc6c1f..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/69.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/7.png b/modules/highgui/src/files_Qt/Milky/64/7.png deleted file mode 100644 index e97ab37ce8..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/7.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/70.png b/modules/highgui/src/files_Qt/Milky/64/70.png deleted file mode 100644 index d07791b287..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/70.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/71.png b/modules/highgui/src/files_Qt/Milky/64/71.png deleted file mode 100644 index fa32faa00d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/71.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/72.png b/modules/highgui/src/files_Qt/Milky/64/72.png deleted file mode 100644 index c5a01bf7bc..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/72.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/73.png b/modules/highgui/src/files_Qt/Milky/64/73.png deleted file mode 100644 index 9e98cb0697..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/73.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/74.png b/modules/highgui/src/files_Qt/Milky/64/74.png deleted file mode 100644 index 2c7ecf3053..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/74.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/75.png b/modules/highgui/src/files_Qt/Milky/64/75.png deleted file mode 100644 index eecf17ecd4..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/75.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/76.png b/modules/highgui/src/files_Qt/Milky/64/76.png deleted file mode 100644 index ac3e66bbf7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/76.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/77.png b/modules/highgui/src/files_Qt/Milky/64/77.png deleted file mode 100644 index c56fcc8325..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/77.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/78.png b/modules/highgui/src/files_Qt/Milky/64/78.png deleted file mode 100644 index 65a81a990c..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/78.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/79.png b/modules/highgui/src/files_Qt/Milky/64/79.png deleted file mode 100644 index 073d726ab7..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/79.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/8.png b/modules/highgui/src/files_Qt/Milky/64/8.png deleted file mode 100644 index 639ac9a1f1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/8.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/80.png b/modules/highgui/src/files_Qt/Milky/64/80.png deleted file mode 100644 index 6354dc45b9..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/80.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/81.png b/modules/highgui/src/files_Qt/Milky/64/81.png deleted file mode 100644 index 8ddba8b635..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/81.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/82.png b/modules/highgui/src/files_Qt/Milky/64/82.png deleted file mode 100644 index 1145b3b5ea..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/82.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/83.png b/modules/highgui/src/files_Qt/Milky/64/83.png deleted file mode 100644 index ad2d75f2db..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/83.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/84.png b/modules/highgui/src/files_Qt/Milky/64/84.png deleted file mode 100644 index 9e1ba3421b..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/84.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/85.png b/modules/highgui/src/files_Qt/Milky/64/85.png deleted file mode 100644 index baf7045789..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/85.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/86.png b/modules/highgui/src/files_Qt/Milky/64/86.png deleted file mode 100644 index bbbe97a4f1..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/86.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/87.png b/modules/highgui/src/files_Qt/Milky/64/87.png deleted file mode 100644 index 39f26f1fbb..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/87.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/88.png b/modules/highgui/src/files_Qt/Milky/64/88.png deleted file mode 100644 index 1cae0fca90..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/88.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/89.png b/modules/highgui/src/files_Qt/Milky/64/89.png deleted file mode 100644 index dfcf14de74..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/89.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/9.png b/modules/highgui/src/files_Qt/Milky/64/9.png deleted file mode 100644 index cce6c7ef88..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/9.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/90.png b/modules/highgui/src/files_Qt/Milky/64/90.png deleted file mode 100644 index 3721677dd5..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/90.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/91.png b/modules/highgui/src/files_Qt/Milky/64/91.png deleted file mode 100644 index 86e9d71fa8..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/91.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/92.png b/modules/highgui/src/files_Qt/Milky/64/92.png deleted file mode 100644 index b3a5a4550a..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/92.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/93.png b/modules/highgui/src/files_Qt/Milky/64/93.png deleted file mode 100644 index f0b5818d07..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/93.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/94.png b/modules/highgui/src/files_Qt/Milky/64/94.png deleted file mode 100644 index aca0ff936d..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/94.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/95.png b/modules/highgui/src/files_Qt/Milky/64/95.png deleted file mode 100644 index 8f3e520dca..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/95.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/96.png b/modules/highgui/src/files_Qt/Milky/64/96.png deleted file mode 100644 index 6aeffcf943..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/96.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/97.png b/modules/highgui/src/files_Qt/Milky/64/97.png deleted file mode 100644 index 3c664086e2..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/97.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/98.png b/modules/highgui/src/files_Qt/Milky/64/98.png deleted file mode 100644 index 0a11c643bb..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/98.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/64/99.png b/modules/highgui/src/files_Qt/Milky/64/99.png deleted file mode 100644 index 122cf2de06..0000000000 Binary files a/modules/highgui/src/files_Qt/Milky/64/99.png and /dev/null differ diff --git a/modules/highgui/src/files_Qt/Milky/README.txt b/modules/highgui/src/files_Qt/Milky/README.txt deleted file mode 100644 index 01ab431457..0000000000 --- a/modules/highgui/src/files_Qt/Milky/README.txt +++ /dev/null @@ -1,19 +0,0 @@ -From: -http://iconeden.com/icon/milky-a-free-vector-iconset.html - -License Agreement - -This is a legal agreement between you (the downloader) and IconEden.com. On download of any royalty-free icons from our website you agree to the following: - -All of the icons remain the property of IconEden.com. The icons can be used royalty-free by the license for any personal or commercial project including web application, web design, software application, mobile application, documentation, presentation, computer game, advertising, film, video. - -You may modify the icons in shape, color, and/or file format and use the modified icons royalty-free according to the license terms for any personal or commercial product. - -The license does not permit the following uses: - - 1. The icons may not be resold, sublicensed, rented, transferred or otherwise made available for use or detached from a product, software application or web page; - 2. The icons may not be placed on any electronic bulletin board or downloadable format; - -You may not use, or allow anyone else to use the icons to create pornographic, libelous, obscene, or defamatory material. - -All icon files are provided "as is". You agree not to hold IconEden.com liable for any damages that may occur due to use, or inability to use, icons or image data from IconEden.com. diff --git a/modules/highgui/src/files_Qt/stylesheet_trackbar.qss b/modules/highgui/src/files_Qt/stylesheet_trackbar.qss deleted file mode 100644 index 9d5fd02051..0000000000 --- a/modules/highgui/src/files_Qt/stylesheet_trackbar.qss +++ /dev/null @@ -1,58 +0,0 @@ -/* //from http://thesmithfam.org/blog/2010/03/10/fancy-qslider-stylesheet/ */ - -QSlider::groove:horizontal { -border: 1px solid #bbb; -background: white; -height: 10px; -border-radius: 4px; -} - -QSlider::sub-page:horizontal { -background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, -stop: 0 #66e, stop: 1 #bbf); -background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, -stop: 0 #bbf, stop: 1 #55f); -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::add-page:horizontal { -background: #fff; -border: 1px solid #777; -height: 10px; -border-radius: 4px; -} - -QSlider::handle:horizontal { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, -stop:0 #eee, stop:1 #ccc); -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 4px; -} - -QSlider::handle:horizontal:hover { -background: qlineargradient(x1:0, y1:0, x2:1, y2:1, -stop:0 #fff, stop:1 #ddd); -border: 1px solid #444; -border-radius: 4px; -} - -QSlider::sub-page:horizontal:disabled { -background: #bbb; -border-color: #999; -} - -QSlider::add-page:horizontal:disabled { -background: #eee; -border-color: #999; -} - -QSlider::handle:horizontal:disabled { -background: #eee; -border: 1px solid #aaa; -border-radius: 4px; -} diff --git a/modules/highgui/src/precomp.hpp b/modules/highgui/src/precomp.hpp index 2daa82143f..30510f0593 100644 --- a/modules/highgui/src/precomp.hpp +++ b/modules/highgui/src/precomp.hpp @@ -141,6 +141,7 @@ double cvGetOpenGlProp_W32(const char* name); double cvGetOpenGlProp_GTK(const char* name); double cvGetPropVisible_W32(const char* name); +double cvGetPropVisible_COCOA(const char* name); double cvGetPropTopmost_W32(const char* name); double cvGetPropTopmost_COCOA(const char* name); diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index f166b2c874..8f90edcbd1 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -365,6 +365,8 @@ double cv::getWindowProperty(const String& name, int prop_id) return cvGetPropVisible_QT(name.c_str()); #elif defined(HAVE_WIN32UI) return cvGetPropVisible_W32(name.c_str()); + #elif defined(HAVE_COCOA) + return cvGetPropVisible_COCOA(name.c_str()); #else return -1; #endif diff --git a/modules/highgui/src/window_QT.cpp b/modules/highgui/src/window_QT.cpp index 9f1cdc2827..09bc7b486e 100644 --- a/modules/highgui/src/window_QT.cpp +++ b/modules/highgui/src/window_QT.cpp @@ -2068,8 +2068,6 @@ void CvWindow::createToolBar() { myToolBar = new QToolBar(this); myToolBar->setFloatable(false); //is not a window - myToolBar->setFixedHeight(28); - myToolBar->setMinimumWidth(1); foreach (QAction *a, vect_QActions) myToolBar->addAction(a); diff --git a/modules/highgui/src/window_QT.qrc b/modules/highgui/src/window_QT.qrc index efdd8c29a9..d68175a987 100644 --- a/modules/highgui/src/window_QT.qrc +++ b/modules/highgui/src/window_QT.qrc @@ -1,16 +1,15 @@ - files_Qt/Milky/48/28.png - files_Qt/Milky/48/23.png - files_Qt/Milky/48/19.png - files_Qt/Milky/48/24.png - files_Qt/Milky/48/27.png - files_Qt/Milky/48/61.png - files_Qt/Milky/48/106.png - files_Qt/Milky/48/107.png - files_Qt/Milky/48/7.png - files_Qt/Milky/48/43.png - files_Qt/Milky/48/38.png - files_Qt/stylesheet_trackbar.qss + files_Qt/Material/28.png + files_Qt/Material/23.png + files_Qt/Material/19.png + files_Qt/Material/24.png + files_Qt/Material/27.png + files_Qt/Material/61.png + files_Qt/Material/106.png + files_Qt/Material/107.png + files_Qt/Material/7.png + files_Qt/Material/43.png + files_Qt/Material/38.png diff --git a/modules/highgui/src/window_cocoa.mm b/modules/highgui/src/window_cocoa.mm index 16d3f83ecc..46f22fe6e7 100644 --- a/modules/highgui/src/window_cocoa.mm +++ b/modules/highgui/src/window_cocoa.mm @@ -439,6 +439,9 @@ void setTrackbarPosImpl(const char* trackbar_name, const char* window_name, int slider = [[window sliders] valueForKey:[NSString stringWithFormat:@"%s", trackbar_name]]; if(slider) { [[slider slider] setIntValue:pos]; + if([slider respondsToSelector:@selector(handleSlider)]) { + [slider performSelector:@selector(handleSlider)]; + } } } [localpool5 drain]; @@ -551,6 +554,8 @@ int namedWindowImpl( const char* name, int flags ) [window setContentView:[[CVView alloc] init]]; + [NSApp activateIgnoringOtherApps:YES]; + [window setHasShadow:YES]; [window setAcceptsMouseMovedEvents:YES]; [window useOptimizedDrawing:YES]; @@ -704,6 +709,31 @@ void cvSetModeWindow_COCOA( const char* name, double prop_value ) __END__; } +double cvGetPropVisible_COCOA(const char* name) +{ + double result = -1; + CVWindow* window = nil; + + CV_FUNCNAME("cvGetPropVisible_COCOA"); + + __BEGIN__; + if (name == NULL) + { + CV_ERROR(CV_StsNullPtr, "NULL name string"); + } + + window = cvGetWindow(name); + if (window == NULL) + { + CV_ERROR(CV_StsNullPtr, "NULL window"); + } + + result = window.isVisible ? 1 : 0; + + __END__; + return result; +} + double cvGetPropTopmost_COCOA(const char* name) { double result = -1; @@ -1154,7 +1184,7 @@ static NSSize constrainAspectRatio(NSSize base, NSSize constraint) { [slider setMaxValue:100]; [slider setContinuous:YES]; [slider setTarget:self]; - [slider setAction:@selector(sliderChanged:)]; + [slider setAction:@selector(handleSliderNotification:)]; [self addSubview:slider]; [self setAutoresizingMask:NSViewWidthSizable]; @@ -1164,8 +1194,12 @@ static NSSize constrainAspectRatio(NSSize base, NSSize constraint) { return self; } -- (void)sliderChanged:(NSNotification *)notification { +- (void)handleSliderNotification:(NSNotification *)notification { (void)notification; + [self handleSlider]; +} + +- (void)handleSlider { int pos = [slider intValue]; NSString *temp = [self initialName]; NSString *text = [NSString stringWithFormat:@"%@ %d", temp, pos]; diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index 1468227283..d85642616d 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -2061,28 +2061,23 @@ static LRESULT CALLBACK HGToolbarProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA case WM_NCCALCSIZE: { LRESULT ret = CallWindowProc(window.toolbar.toolBarProc, hwnd, uMsg, wParam, lParam); - int rows = (int)SendMessage(hwnd, TB_GETROWS, 0, 0); - if (window.toolbar.rows != rows) + auto& trakbars = window.toolbar.trackbars; + + for (auto it = trakbars.begin(); it != trakbars.end(); ++it) { - SendMessage(window.toolbar.toolbar, TB_BUTTONCOUNT, 0, 0); - auto& trakbars = window.toolbar.trackbars; - - for (auto it = trakbars.begin(); it != trakbars.end(); ++it) - { - auto trackbar = *it; - CV_Assert(trackbar); - RECT rect = { 0 }; - SendMessage(window.toolbar.toolbar, TB_GETITEMRECT, - (WPARAM)trackbar->id, (LPARAM)&rect); - MoveWindow(trackbar->hwnd, rect.left + HG_BUDDY_WIDTH, rect.top, - rect.right - rect.left - HG_BUDDY_WIDTH, - rect.bottom - rect.top, FALSE); - MoveWindow(trackbar->buddy, rect.left, rect.top, - HG_BUDDY_WIDTH, rect.bottom - rect.top, FALSE); - } - window.toolbar.rows = rows; + auto trackbar = *it; + CV_Assert(trackbar); + RECT rect = { 0 }; + SendMessage(window.toolbar.toolbar, TB_GETITEMRECT, + (WPARAM)trackbar->id, (LPARAM)&rect); + MoveWindow(trackbar->hwnd, rect.left + HG_BUDDY_WIDTH, rect.top, + rect.right - rect.left - HG_BUDDY_WIDTH, + rect.bottom - rect.top, FALSE); + MoveWindow(trackbar->buddy, rect.left, rect.top, + HG_BUDDY_WIDTH, rect.bottom - rect.top, FALSE); } + window.toolbar.rows = static_cast(SendMessage(hwnd, TB_GETROWS, 0, 0)); return ret; } } @@ -2149,7 +2144,7 @@ static void showSaveDialog(CvWindow& window) #endif ofn.hwndOwner = window.hwnd; ofn.lpstrFilter = -#ifdef HAVE_PNG +#if defined(HAVE_PNG) || defined(HAVE_SPNG) "Portable Network Graphics files (*.png)\0*.png\0" #endif "Windows bitmap (*.bmp;*.dib)\0*.bmp;*.dib\0" @@ -2175,7 +2170,7 @@ static void showSaveDialog(CvWindow& window) ofn.lpstrFile = szFileName; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN | OFN_NOCHANGEDIR; -#ifdef HAVE_PNG +#if defined(HAVE_PNG) || defined(HAVE_SPNG) ofn.lpstrDefExt = "png"; #else ofn.lpstrDefExt = "bmp"; @@ -2329,12 +2324,6 @@ std::shared_ptr icvFindTrackbarByName(CvWindow& window, const std::s } return std::shared_ptr(); } -static inline -std::shared_ptr icvFindTrackbarByName(const std::shared_ptr& window, const std::string& name) -{ - CV_Assert(window); - return icvFindTrackbarByName(window, name); -} static std::shared_ptr createTrackbar_(CvWindow& window, const std::string& trackbar_name, @@ -2423,7 +2412,7 @@ std::shared_ptr createTrackbar_(CvWindow& window, const std::string& /* Retrieve current buttons count */ int bcount = (int)SendMessage(window.toolbar.toolbar, TB_BUTTONCOUNT, 0, 0); - if (bcount > 1) + if (bcount > 0) { /* If this is not the first button then we need to separate it from the previous one */ @@ -2467,7 +2456,7 @@ std::shared_ptr createTrackbar_(CvWindow& window, const std::string& tbis.dwMask = TBIF_SIZE; RECT rect = { 0 }; - GetClientRect(window.hwnd, &rect); + GetClientRect(window.toolbar.toolbar, &rect); tbis.cx = (unsigned short)(rect.right - rect.left); SendMessage(window.toolbar.toolbar, TB_SETBUTTONINFO, @@ -2479,7 +2468,7 @@ std::shared_ptr createTrackbar_(CvWindow& window, const std::string& /* Create a slider */ auto trackbar = std::make_shared(window, trackbar_name); - trackbar->id = bcount; + trackbar->id = tbs.idCommand; window.toolbar.trackbars.push_back(trackbar); auto slider_name = cv::format("Trackbar%p", trackbar.get()); @@ -2568,7 +2557,7 @@ int getTrackbarPosImpl(const char* trackbar_name, const char* window_name) if (!window) CV_Error_(Error::StsNullPtr, ("NULL window: '%s'", window_name)); - auto trackbar = icvFindTrackbarByName(window, trackbar_name); + auto trackbar = icvFindTrackbarByName(*window, trackbar_name); if (!trackbar) CV_Error_(Error::StsNullPtr, ("NULL trackbar: '%s'", trackbar_name)); @@ -2589,7 +2578,7 @@ void setTrackbarPosImpl(const char* trackbar_name, const char* window_name, int if (!window) CV_Error_(Error::StsNullPtr, ("NULL window: '%s'", window_name)); - auto trackbar = icvFindTrackbarByName(window, trackbar_name); + auto trackbar = icvFindTrackbarByName(*window, trackbar_name); if (!trackbar) CV_Error_(Error::StsNullPtr, ("NULL trackbar: '%s'", trackbar_name)); @@ -2621,7 +2610,7 @@ void setTrackbarMaxImpl(const char* trackbar_name, const char* window_name, int if (!window) CV_Error_(Error::StsNullPtr, ("NULL window: '%s'", window_name)); - auto trackbar = icvFindTrackbarByName(window, trackbar_name); + auto trackbar = icvFindTrackbarByName(*window, trackbar_name); if (!trackbar) CV_Error_(Error::StsNullPtr, ("NULL trackbar: '%s'", trackbar_name)); @@ -2650,7 +2639,7 @@ void setTrackbarMinImpl(const char* trackbar_name, const char* window_name, int if (!window) CV_Error_(Error::StsNullPtr, ("NULL window: '%s'", window_name)); - auto trackbar = icvFindTrackbarByName(window, trackbar_name); + auto trackbar = icvFindTrackbarByName(*window, trackbar_name); if (!trackbar) CV_Error_(Error::StsNullPtr, ("NULL trackbar: '%s'", trackbar_name)); diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index b9c4683aa9..1572543aff 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -24,6 +24,12 @@ if(HAVE_WEBP) list(APPEND GRFMT_LIBS ${WEBP_LIBRARIES}) endif() +if(HAVE_SPNG) + add_definitions(${SPNG_DEFINITIONS}) + ocv_include_directories(${SPNG_INCLUDE_DIR}) + list(APPEND GRFMT_LIBS ${SPNG_LIBRARY}) +endif() + if(HAVE_PNG) add_definitions(${PNG_DEFINITIONS}) ocv_include_directories(${PNG_INCLUDE_DIR}) @@ -67,7 +73,7 @@ if(HAVE_OPENEXR) endif() endif() -if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR) +if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG) ocv_include_directories(${ZLIB_INCLUDE_DIRS}) list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES}) endif() @@ -179,7 +185,7 @@ endif() if(TARGET opencv_test_imgcodecs AND HAVE_OPENEXR AND "$ENV{OPENCV_IO_ENABLE_OPENEXR}") ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_ENABLE_OPENEXR_TESTS=1) endif() -if(TARGET opencv_test_imgcodecs AND HAVE_PNG AND NOT (PNG_VERSION VERSION_LESS "1.6.31")) +if(TARGET opencv_test_imgcodecs AND ((HAVE_PNG AND NOT (PNG_VERSION VERSION_LESS "1.6.31")) OR HAVE_SPNG)) # details: https://github.com/glennrp/libpng/commit/68cb0aaee3de6371b81a4613476d9b33e43e95b1 ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_PNG_WITH_EXIF=1) endif() diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 67ca34fe55..52495eb651 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -97,7 +97,9 @@ enum ImwriteFlags { IMWRITE_PXM_BINARY = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1. IMWRITE_EXR_TYPE = (3 << 4) + 0, /* 48 */ //!< override EXR storage type (FLOAT (FP32) is default) IMWRITE_EXR_COMPRESSION = (3 << 4) + 1, /* 49 */ //!< override EXR compression type (ZIP_COMPRESSION = 3 is default) + IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2, /* 50 */ //!< override EXR DWA compression level (45 is default) IMWRITE_WEBP_QUALITY = 64, //!< For WEBP, it can be a quality from 1 to 100 (the higher is the better). By default (without any parameter) and for quality above 100 the lossless compression is used. + IMWRITE_HDR_COMPRESSION = (5 << 4) + 0, /* 80 */ //!< specify HDR compression IMWRITE_PAM_TUPLETYPE = 128,//!< For PAM, sets the TUPLETYPE field to the corresponding string value that is defined for the format IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI @@ -160,6 +162,12 @@ enum ImwritePAMFlags { IMWRITE_PAM_FORMAT_RGB_ALPHA = 5 }; +//! Imwrite HDR specific values for IMWRITE_HDR_COMPRESSION parameter key +enum ImwriteHDRCompressionFlags { + IMWRITE_HDR_COMPRESSION_NONE = 0, + IMWRITE_HDR_COMPRESSION_RLE = 1 +}; + //! @} imgcodecs_flags /** @brief Loads an image from a file. @@ -306,6 +314,20 @@ reallocations when the function is called repeatedly for images of the same size */ CV_EXPORTS Mat imdecode( InputArray buf, int flags, Mat* dst); +/** @brief Reads a multi-page image from a buffer in memory. + +The function imdecodemulti reads a multi-page image from the specified buffer in the memory. If the buffer is too short or +contains invalid data, the function returns false. + +See cv::imreadmulti for the list of supported formats and flags description. + +@note In the case of color images, the decoded images will have the channels stored in **B G R** order. +@param buf Input array or vector of bytes. +@param flags The same flags as in cv::imread, see cv::ImreadModes. +@param mats A vector of Mat objects holding each page, if more than one. +*/ +CV_EXPORTS_W bool imdecodemulti(InputArray buf, int flags, CV_OUT std::vector& mats); + /** @brief Encodes an image into a memory buffer. The function imencode compresses the image and stores it in the memory buffer that is resized to fit the @@ -332,6 +354,51 @@ CV_EXPORTS_W bool haveImageReader( const String& filename ); */ CV_EXPORTS_W bool haveImageWriter( const String& filename ); +/** @brief To read Multi Page images on demand + +The ImageCollection class provides iterator API to read multi page images on demand. Create iterator +to the collection of the images and iterate over the collection. Decode the necessary page with operator*. + +The performance of page decoding is O(1) if collection is increment sequentially. If the user wants to access random page, +then the time Complexity is O(n) because the collection has to be reinitialized every time in order to go to the correct page. +However, the intermediate pages are not decoded during the process, so typically it's quite fast. +This is required because multipage codecs does not support going backwards. +After decoding the one page, it is stored inside the collection cache. Hence, trying to get Mat object from already decoded page is O(1). +If you need memory, you can use .releaseCache() method to release cached index. +The space complexity is O(n) if all pages are decoded into memory. The user is able to decode and release images on demand. +*/ +class CV_EXPORTS ImageCollection { +public: + struct CV_EXPORTS iterator { + iterator(ImageCollection* col); + iterator(ImageCollection* col, int end); + Mat& operator*(); + Mat* operator->(); + iterator& operator++(); + iterator operator++(int); + friend bool operator== (const iterator& a, const iterator& b) { return a.m_curr == b.m_curr; } + friend bool operator!= (const iterator& a, const iterator& b) { return a.m_curr != b.m_curr; } + + private: + ImageCollection* m_pCollection; + int m_curr; + }; + + ImageCollection(); + ImageCollection(const String& filename, int flags); + void init(const String& img, int flags); + size_t size() const; + const Mat& at(int index); + const Mat& operator[](int index); + void releaseCache(int index); + iterator begin(); + iterator end(); + + class Impl; + Ptr getImpl(); +protected: + Ptr pImpl; +}; //! @} imgcodecs diff --git a/modules/imgcodecs/perf/perf_jpeg.cpp b/modules/imgcodecs/perf/perf_jpeg.cpp new file mode 100644 index 0000000000..694e2e698e --- /dev/null +++ b/modules/imgcodecs/perf/perf_jpeg.cpp @@ -0,0 +1,38 @@ +// 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 "perf_precomp.hpp" + +namespace opencv_test +{ +using namespace perf; + +PERF_TEST(JPEG, Decode) +{ + String filename = getDataPath("stitching/boat1.jpg"); + + FILE *f = fopen(filename.c_str(), "rb"); + fseek(f, 0, SEEK_END); + long len = ftell(f); + fseek(f, 0, SEEK_SET); + vector file_buf((size_t)len); + EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f)); + fclose(f); f = NULL; + + TEST_CYCLE() imdecode(file_buf, IMREAD_UNCHANGED); + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST(JPEG, Encode) +{ + String filename = getDataPath("stitching/boat1.jpg"); + cv::Mat src = imread(filename); + + vector buf; + TEST_CYCLE() imencode(".jpg", src, buf); + + SANITY_CHECK_NOTHING(); +} + +} // namespace \ No newline at end of file diff --git a/modules/imgcodecs/perf/perf_png.cpp b/modules/imgcodecs/perf/perf_png.cpp new file mode 100644 index 0000000000..7893d841c4 --- /dev/null +++ b/modules/imgcodecs/perf/perf_png.cpp @@ -0,0 +1,41 @@ +// 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 "perf_precomp.hpp" + +namespace opencv_test +{ +using namespace perf; + +typedef perf::TestBaseWithParam PNG; + +PERF_TEST(PNG, decode) +{ + String filename = getDataPath("perf/2560x1600.png"); + + FILE *f = fopen(filename.c_str(), "rb"); + fseek(f, 0, SEEK_END); + long len = ftell(f); + fseek(f, 0, SEEK_SET); + vector file_buf((size_t)len); + EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f)); + fclose(f); f = NULL; + + TEST_CYCLE() imdecode(file_buf, IMREAD_UNCHANGED); + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST(PNG, encode) +{ + String filename = getDataPath("perf/2560x1600.png"); + cv::Mat src = imread(filename); + + vector buf; + TEST_CYCLE() imencode(".png", src, buf); + + SANITY_CHECK_NOTHING(); +} + +} // namespace diff --git a/modules/imgcodecs/src/grfmt_exr.cpp b/modules/imgcodecs/src/grfmt_exr.cpp index 0585035202..786b9d176b 100644 --- a/modules/imgcodecs/src/grfmt_exr.cpp +++ b/modules/imgcodecs/src/grfmt_exr.cpp @@ -691,6 +691,14 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) CV_Error(Error::StsBadArg, "IMWRITE_EXR_COMPRESSION is invalid or not supported"); } } + if (params[i] == IMWRITE_EXR_DWA_COMPRESSION_LEVEL) + { +#if OPENEXR_VERSION_MAJOR >= 3 + header.dwaCompressionLevel() = params[i + 1]; +#else + CV_LOG_ONCE_WARNING(NULL, "Setting `IMWRITE_EXR_DWA_COMPRESSION_LEVEL` not supported in OpenEXR version " + std::to_string(OPENEXR_VERSION_MAJOR) + " (version 3 is required)"); +#endif + } } if( channels == 3 || channels == 4 ) diff --git a/modules/imgcodecs/src/grfmt_hdr.cpp b/modules/imgcodecs/src/grfmt_hdr.cpp index a274b2233c..6e2f565e32 100644 --- a/modules/imgcodecs/src/grfmt_hdr.cpp +++ b/modules/imgcodecs/src/grfmt_hdr.cpp @@ -141,14 +141,28 @@ bool HdrEncoder::write( const Mat& input_img, const std::vector& params ) if(img.depth() != CV_32F) { img.convertTo(img, CV_32FC3, 1/255.0f); } - CV_Assert(params.empty() || params[0] == HDR_NONE || params[0] == HDR_RLE); + + int compression = IMWRITE_HDR_COMPRESSION_RLE; + for (size_t i = 0; i + 1 < params.size(); i += 2) + { + switch (params[i]) + { + case IMWRITE_HDR_COMPRESSION: + compression = params[i + 1]; + break; + default: + break; + } + } + CV_Check(compression, compression == IMWRITE_HDR_COMPRESSION_NONE || compression == IMWRITE_HDR_COMPRESSION_RLE, ""); + FILE *fout = fopen(m_filename.c_str(), "wb"); if(!fout) { return false; } RGBE_WriteHeader(fout, img.cols, img.rows, NULL); - if(params.empty() || params[0] == HDR_RLE) { + if (compression == IMWRITE_HDR_COMPRESSION_RLE) { RGBE_WritePixels_RLE(fout, const_cast(img.ptr()), img.cols, img.rows); } else { RGBE_WritePixels(fout, const_cast(img.ptr()), img.cols * img.rows); diff --git a/modules/imgcodecs/src/grfmt_hdr.hpp b/modules/imgcodecs/src/grfmt_hdr.hpp index fa29fbe0d2..f0a920083f 100644 --- a/modules/imgcodecs/src/grfmt_hdr.hpp +++ b/modules/imgcodecs/src/grfmt_hdr.hpp @@ -50,12 +50,6 @@ namespace cv { -enum HdrCompression -{ - HDR_NONE = 0, - HDR_RLE = 1 -}; - // Radiance rgbe (.hdr) reader class HdrDecoder CV_FINAL : public BaseImageDecoder { diff --git a/modules/imgcodecs/src/grfmt_spng.cpp b/modules/imgcodecs/src/grfmt_spng.cpp new file mode 100644 index 0000000000..e3c61164e4 --- /dev/null +++ b/modules/imgcodecs/src/grfmt_spng.cpp @@ -0,0 +1,754 @@ +// 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" + +#ifdef HAVE_SPNG + +/****************************************************************************************\ + This part of the file implements PNG codec on base of libspng library, + in particular, this code is based on example.c from libspng + (see 3rdparty/libspng/LICENSE for copyright notice) +\****************************************************************************************/ + +#ifndef _LFS64_LARGEFILE +#define _LFS64_LARGEFILE 0 +#endif +#ifndef _FILE_OFFSET_BITS +#define _FILE_OFFSET_BITS 0 +#endif + +#include +#include + +#include "grfmt_spng.hpp" + +/* + * libspng does not support RGB -> Gray conversion. In order to decode colorful images as grayscale + * we need conversion functions. In the previous png implementation(grfmt_png), the author was set + * to particular values for rgb coefficients. OpenCV icvCvt_BGR2Gray function values does not match + * with these values. (png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 );) For this codec implementation, + * slightly modified versions are implemented in the below of this page. +*/ +void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int _swap_rb); + +void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step, + uchar *gray, int gray_step, + cv::Size size, int _swap_rb); + +void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step, + ushort *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb); + +namespace cv +{ + +/////////////////////// SPngDecoder /////////////////// + +SPngDecoder::SPngDecoder() +{ + m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa"; + m_color_type = 0; + m_ctx = 0; + m_f = 0; + m_buf_supported = true; + m_buf_pos = 0; + m_bit_depth = 0; +} + +SPngDecoder::~SPngDecoder() +{ + close(); +} + +ImageDecoder SPngDecoder::newDecoder() const +{ + return makePtr(); +} + +void SPngDecoder::close() +{ + if (m_f) + { + fclose(m_f); + m_f = 0; + } + + if (m_ctx) + { + struct spng_ctx *ctx = (struct spng_ctx *)m_ctx; + spng_ctx_free(ctx); + m_ctx = 0; + } +} + +int SPngDecoder::readDataFromBuf(void *sp_ctx, void *user, void *dst, size_t size) +{ + /* + * typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length) + * Type definition for callback passed to spng_set_png_stream() for decoders. + * A read callback function should copy length bytes to dest and return 0 or SPNG_IO_EOF/SPNG_IO_ERROR on error. + */ + CV_UNUSED(sp_ctx); + SPngDecoder *decoder = (SPngDecoder *)(user); + CV_Assert(decoder); + + const Mat &buf = decoder->m_buf; + if (decoder->m_buf_pos + size > buf.cols * buf.rows * buf.elemSize()) + { + return SPNG_IO_ERROR; + } + memcpy(dst, decoder->m_buf.ptr() + decoder->m_buf_pos, size); + decoder->m_buf_pos += size; + + return 0; +} + +bool SPngDecoder::readHeader() +{ + volatile bool result = false; + close(); + + spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32); + + if (!ctx) + { + spng_ctx_free(ctx); + return false; + } + + m_ctx = ctx; + + if (!m_buf.empty()) + spng_set_png_stream((struct spng_ctx *)m_ctx, (spng_rw_fn *)readDataFromBuf, this); + else + { + m_f = fopen(m_filename.c_str(), "rb"); + if (m_f) + { + spng_set_png_file(ctx, m_f); + } + } + + if (!m_buf.empty() || m_f) + { + struct spng_ihdr ihdr; + int ret = spng_get_ihdr(ctx, &ihdr); + + if (ret == SPNG_OK) + { + m_width = static_cast(ihdr.width); + m_height = static_cast(ihdr.height); + m_color_type = ihdr.color_type; + m_bit_depth = ihdr.bit_depth; + + if (ihdr.bit_depth <= 8 || ihdr.bit_depth == 16) + { + int num_trans; + switch (ihdr.color_type) + { + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_INDEXED: + struct spng_trns trns; + num_trans = !spng_get_trns(ctx, &trns); + if (num_trans > 0) + m_type = CV_8UC4; + else + m_type = CV_8UC3; + break; + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + m_type = CV_8UC4; + break; + default: + m_type = CV_8UC1; + } + if (ihdr.bit_depth == 16) + m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); + result = true; + } + } + } + + return result; +} + +bool SPngDecoder::readData(Mat &img) +{ + volatile bool result = false; + bool color = img.channels() > 1; + + struct spng_ctx *png_ptr = (struct spng_ctx *)m_ctx; + + if (m_ctx && m_width && m_height) + { + int fmt = SPNG_FMT_PNG; + + struct spng_trns trns; + int have_trns = spng_get_trns((struct spng_ctx *)m_ctx, &trns); + + int decode_flags = 0; + if (have_trns == SPNG_OK) + { + decode_flags = SPNG_DECODE_TRNS; + } + if (img.channels() == 4) + { + if (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || + m_color_type == SPNG_COLOR_TYPE_INDEXED || + m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) + fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; + else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE) + fmt = m_bit_depth == 16 ? SPNG_FMT_GA16 : SPNG_FMT_GA8; + else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) + { + fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; + } + else + fmt = SPNG_FMT_RGBA8; + } + if (img.channels() == 3) + { + fmt = SPNG_FMT_RGB8; + if ((m_color_type == SPNG_COLOR_TYPE_GRAYSCALE || m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) && + m_bit_depth == 16) + fmt = SPNG_FMT_RGB8; + else if (m_bit_depth == 16) + fmt = SPNG_FMT_PNG; + } + else if (img.channels() == 1) + { + if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth <= 8) + fmt = SPNG_FMT_G8; + else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth == 16) + { + if (img.depth() == CV_8U || img.depth() == CV_8S) + { + fmt = SPNG_FMT_RGB8; + } + else + { + fmt = SPNG_FMT_PNG; + } + } + else if (m_color_type == SPNG_COLOR_TYPE_INDEXED || + m_color_type == SPNG_COLOR_TYPE_TRUECOLOR) + { + if (img.depth() == CV_8U || img.depth() == CV_8S) + { + fmt = SPNG_FMT_RGB8; + } + else + { + fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8; + } + } + else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || fmt == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) + { + if (img.depth() == CV_8U || img.depth() == CV_8S) + { + fmt = SPNG_FMT_RGB8; + } + else + { + fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; + } + } + else + fmt = SPNG_FMT_RGB8; + } + + size_t image_width, image_size = 0; + int ret = spng_decoded_image_size(png_ptr, fmt, &image_size); + struct spng_ihdr ihdr; + spng_get_ihdr(png_ptr, &ihdr); + + if (ret == SPNG_OK) + { + image_width = image_size / m_height; + + ret = spng_decode_image(png_ptr, nullptr, 0, fmt, SPNG_DECODE_PROGRESSIVE | decode_flags); + if (ret == SPNG_OK) + { + struct spng_row_info row_info{}; + + // If user wants to read image as grayscale(IMREAD_GRAYSCALE), but image format is not + // decode image then convert to grayscale + if (!color && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA8 || fmt == SPNG_FMT_RGBA16)) + { + if (ihdr.interlace_method == 0) + { + AutoBuffer buffer; + buffer.allocate(image_width); + if (fmt == SPNG_FMT_RGB8) + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer.data(), image_width); + spngCvt_BGR2Gray_8u_C3C1R( + buffer.data(), + 0, + img.data + row_info.row_num * img.step, + 0, Size(m_width, 1), 2); + } while (ret == SPNG_OK); + } + else if (fmt == SPNG_FMT_RGBA8) + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer.data(), image_width); + spngCvt_BGRA2Gray_8u_C4C1R( + buffer.data(), + 0, + img.data + row_info.row_num * img.step, + 0, Size(m_width, 1), 2); + } while (ret == SPNG_OK); + } + else if (fmt == SPNG_FMT_RGBA16) + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer.data(), image_width); + spngCvt_BGRA2Gray_16u_CnC1R( + reinterpret_cast(buffer.data()), 0, + reinterpret_cast(img.data + row_info.row_num * img.step), + 0, Size(m_width, 1), + 4, 2); + } while (ret == SPNG_OK); + } + } + else + { + AutoBuffer imageBuffer(image_size); + ret = spng_decode_image(png_ptr, imageBuffer.data(), image_size, fmt, 0); + int step = m_width * img.channels(); + if (fmt == SPNG_FMT_RGB8) + { + spngCvt_BGR2Gray_8u_C3C1R( + imageBuffer.data(), + step, + img.data, + step, Size(m_width, m_height), 2); + } + else if (fmt == SPNG_FMT_RGBA8) + { + spngCvt_BGRA2Gray_8u_C4C1R( + imageBuffer.data(), + step, + img.data, + step, Size(m_width, m_height), 2); + } + else if (fmt == SPNG_FMT_RGBA16) + { + spngCvt_BGRA2Gray_16u_CnC1R( + reinterpret_cast(imageBuffer.data()), step / 3, + reinterpret_cast(img.data), + step / 3, Size(m_width, m_height), + 4, 2); + } + } + } + else if (color) + { // RGB -> BGR, convert row by row if png is non-interlaced, otherwise convert image as one + int step = m_width * img.channels(); + AutoBuffer _buffer(m_height); + uchar **buffer = _buffer.data(); + for (int y = 0; y < m_height; y++) + { + buffer[y] = img.data + y * img.step; + } + if (img.channels() == 4 && m_bit_depth == 16) + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); + if (ihdr.interlace_method == 0) + { + icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast(buffer[row_info.row_num]), 0, + reinterpret_cast(buffer[row_info.row_num]), 0, + Size(m_width, 1)); + } + } while (ret == SPNG_OK); + if (ihdr.interlace_method) + { + icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast(img.data), step * 2, reinterpret_cast(img.data), step * 2, Size(m_width, m_height)); + } + } + else if (img.channels() == 4) + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); + if (ihdr.interlace_method == 0) + { + icvCvt_RGBA2BGRA_8u_C4R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1)); + } + } while (ret == SPNG_OK); + if (ihdr.interlace_method) + { + icvCvt_RGBA2BGRA_8u_C4R(img.data, step, img.data, step, Size(m_width, m_height)); + } + } + else if (fmt == SPNG_FMT_PNG) + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); + if (ihdr.interlace_method == 0) + { + icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(buffer[row_info.row_num]), 0, + reinterpret_cast(buffer[row_info.row_num]), 0, Size(m_width, 1)); + } + } while (ret == SPNG_OK); + if (ihdr.interlace_method) + { + icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(img.data), step, + reinterpret_cast(img.data), step, Size(m_width, m_height)); + } + } + else + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); + if (ihdr.interlace_method == 0) + { + icvCvt_RGB2BGR_8u_C3R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1)); + } + } while (ret == SPNG_OK); + if (ihdr.interlace_method) + { + icvCvt_RGB2BGR_8u_C3R(img.data, step, img.data, step, Size(m_width, m_height)); + } + } + } + else + { + do + { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, img.data + row_info.row_num * image_width, image_width); + } while (ret == SPNG_OK); + } + } + + if (ret == SPNG_EOI) + { + ret = spng_decode_chunks(png_ptr); + if(ret == SPNG_OK) result = true; + struct spng_exif exif_s{}; + ret = spng_get_exif(png_ptr, &exif_s); + if (ret == SPNG_OK) + { + if (exif_s.data && exif_s.length > 0) + { + result = m_exif.parseExif((unsigned char *)exif_s.data, exif_s.length); + } + } + } + } + } + + return result; +} + +/////////////////////// SPngEncoder /////////////////// + +SPngEncoder::SPngEncoder() +{ + m_description = "Portable Network Graphics files (*.png)"; + m_buf_supported = true; +} + +SPngEncoder::~SPngEncoder() +{ +} + +bool SPngEncoder::isFormatSupported(int depth) const +{ + return depth == CV_8U || depth == CV_16U; +} + +ImageEncoder SPngEncoder::newEncoder() const +{ + return makePtr(); +} + +int SPngEncoder::writeDataToBuf(void *ctx, void *user, void *dst_src, size_t length) +{ + CV_UNUSED(ctx); + SPngEncoder *encoder = (SPngEncoder *)(user); + CV_Assert(encoder && encoder->m_buf); + size_t cursz = encoder->m_buf->size(); + encoder->m_buf->resize(cursz + length); + memcpy(&(*encoder->m_buf)[cursz], dst_src, length); + return 0; +} + +bool SPngEncoder::write(const Mat &img, const std::vector ¶ms) +{ + int fmt; + spng_ctx *ctx = spng_ctx_new(SPNG_CTX_ENCODER); + FILE *volatile f = 0; + int width = img.cols, height = img.rows; + int depth = img.depth(), channels = img.channels(); + volatile bool result = false; + + if (depth != CV_8U && depth != CV_16U) + return false; + + if (ctx) + { + struct spng_ihdr ihdr = {}; + ihdr.height = height; + ihdr.width = width; + int compression_level = -1; // Invalid value to allow setting 0-9 as valid + 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; + } + } + fmt = channels == 1 ? SPNG_COLOR_TYPE_GRAYSCALE : channels == 3 ? SPNG_COLOR_TYPE_TRUECOLOR + : SPNG_COLOR_TYPE_TRUECOLOR_ALPHA; + + ihdr.bit_depth = depth == CV_8U ? isBilevel ? 1 : 8 : 16; + ihdr.color_type = fmt; + ihdr.interlace_method = SPNG_INTERLACE_NONE; + ihdr.filter_method = SPNG_FILTER_NONE; + ihdr.compression_method = 0; + spng_set_ihdr(ctx, &ihdr); + + if (m_buf) + { + spng_set_png_stream(ctx, (spng_rw_fn *)writeDataToBuf, this); + } + else + { + f = fopen(m_filename.c_str(), "wb"); + if (f) + spng_set_png_file(ctx, f); + } + + if (m_buf || f) + { + if (compression_level >= 0) + { + spng_set_option(ctx, SPNG_IMG_COMPRESSION_LEVEL, compression_level); + } + else + { + spng_set_option(ctx, SPNG_FILTER_CHOICE, SPNG_FILTER_CHOICE_SUB); + spng_set_option(ctx, SPNG_IMG_COMPRESSION_LEVEL, Z_BEST_SPEED); + } + spng_set_option(ctx, SPNG_IMG_COMPRESSION_STRATEGY, compression_strategy); + + int ret; + spng_encode_chunks(ctx); + ret = spng_encode_image(ctx, nullptr, 0, SPNG_FMT_PNG, SPNG_ENCODE_PROGRESSIVE); + if (channels > 1) + { + int error; + if (ret == SPNG_OK) + { + if (depth == CV_16U) + { + AutoBuffer buff2; + buff2.allocate(height); + for (int y = 0; y < height; y++) + buff2[y] = reinterpret_cast(img.data + y * img.step); + + AutoBuffer _buffer; + _buffer.allocate(width * channels * 2); + for (int y = 0; y < height; y++) + { + if (channels == 3) + { + icvCvt_BGR2RGB_16u_C3R(buff2[y], 0, + _buffer.data(), 0, Size(width, 1)); + } + else if (channels == 4) + { + icvCvt_BGRA2RGBA_16u_C4R(buff2[y], 0, + _buffer.data(), 0, Size(width, 1)); + } + error = spng_encode_row(ctx, _buffer.data(), width * channels * 2); + if (error) + break; + } + } + else + { + AutoBuffer buff; + buff.allocate(height); + for (int y = 0; y < height; y++) + buff[y] = img.data + y * img.step; + + AutoBuffer _buffer; + _buffer.allocate(width * channels); + for (int y = 0; y < height; y++) + { + if (channels == 3) + { + icvCvt_BGR2RGB_8u_C3R(buff[y], 0, _buffer.data(), 0, Size(width, 1)); + } + else if (channels == 4) + { + icvCvt_BGRA2RGBA_8u_C4R(buff[y], 0, _buffer.data(), 0, Size(width, 1)); + } + error = spng_encode_row(ctx, _buffer.data(), width * channels); + if (error) + break; + } + } + if (error == SPNG_EOI) + { // success + spng_encode_chunks(ctx); + ret = SPNG_OK; + } + } + } + else + { + int error; + for (int y = 0; y < height; y++) + { + error = spng_encode_row(ctx, img.data + y * img.step, width * channels * (depth == CV_16U ? 2 : 1)); + if (error) + break; + } + if (error == SPNG_EOI) + { // success + spng_encode_chunks(ctx); + ret = SPNG_OK; + } + } + if (ret == SPNG_OK) + result = true; + } + } + + spng_ctx_free(ctx); + if (f) + fclose((FILE *)f); + + return result; +} + +} + +void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int _swap_rb) +{ + int i; + for (; size.height--; gray += gray_step) + { + double cBGR0 = 0.1140441895; + double cBGR2 = 0.2989807129; + if (_swap_rb) + std::swap(cBGR0, cBGR2); + for (i = 0; i < size.width; i++, bgr += 3) + { + int t = static_cast(cBGR0 * bgr[0] + 0.5869750977 * bgr[1] + cBGR2 * bgr[2]); + gray[i] = (uchar)t; + } + + bgr += bgr_step - size.width * 3; + } +} + +void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step, + uchar *gray, int gray_step, + cv::Size size, int _swap_rb) +{ + int i; + for (; size.height--; gray += gray_step) + { + double cBGR0 = 0.1140441895; + double cBGR2 = 0.2989807129; + if (_swap_rb) + std::swap(cBGR0, cBGR2); + for (i = 0; i < size.width; i++, bgra += 4) + { + int t = cBGR0 * bgra[0] + 0.5869750977 * bgra[1] + cBGR2 * bgra[2]; + gray[i] = (uchar)t; + } + + bgra += rgba_step - size.width * 4; + } +} + +void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step, + ushort *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb) +{ + int i; + for (; size.height--; gray += gray_step) + { + double cBGR0 = 0.1140441895; + double cBGR2 = 0.2989807129; + if (_swap_rb) + std::swap(cBGR0, cBGR2); + for (i = 0; i < size.width; i++, bgr += ncn) + { + int t = cBGR0 * bgr[0] + 0.5869750977 * bgr[1] + cBGR2 * bgr[2]; + gray[i] = (ushort)t; + } + + bgr += bgr_step - size.width * ncn; + } +} + +#endif + +/* End of file. */ diff --git a/modules/imgcodecs/src/grfmt_spng.hpp b/modules/imgcodecs/src/grfmt_spng.hpp new file mode 100644 index 0000000000..648fa99ff6 --- /dev/null +++ b/modules/imgcodecs/src/grfmt_spng.hpp @@ -0,0 +1,60 @@ +// 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_SPNG_H_ +#define _GRFMT_SPNG_H_ + +#ifdef HAVE_SPNG + +#include "grfmt_base.hpp" +#include "bitstrm.hpp" + +namespace cv +{ + +class SPngDecoder CV_FINAL : public BaseImageDecoder +{ +public: + + SPngDecoder(); + virtual ~SPngDecoder(); + + bool readData( Mat& img ) CV_OVERRIDE; + bool readHeader() CV_OVERRIDE; + void close(); + + ImageDecoder newDecoder() const CV_OVERRIDE; + +protected: + + static int readDataFromBuf(void* sp_ctx, void *user, void* dst, size_t size); + + int m_bit_depth; + void* m_ctx; + FILE* m_f; + int m_color_type; + size_t m_buf_pos; +}; + + +class SPngEncoder CV_FINAL : public BaseImageEncoder +{ +public: + SPngEncoder(); + virtual ~SPngEncoder(); + + bool isFormatSupported( int depth ) const CV_OVERRIDE; + bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + + ImageEncoder newEncoder() const CV_OVERRIDE; + +protected: + static int writeDataToBuf(void *ctx, void *user, void *dst_src, size_t length); +}; + +} + +#endif + +#endif/*_GRFMT_PNG_H_*/ diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index a2b55ea2ff..5320c1a77a 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -238,7 +238,6 @@ public: bool TiffDecoder::readHeader() { bool result = false; - TIFF* tif = static_cast(m_tif.get()); if (!tif) { @@ -312,6 +311,18 @@ bool TiffDecoder::readHeader() result = true; break; } + case 4: + //support 4-bit palette. + if (photometric == PHOTOMETRIC_PALETTE) + { + CV_Check((int)sample_format, sample_format == SAMPLEFORMAT_UINT || sample_format == SAMPLEFORMAT_INT, ""); + int depth = sample_format == SAMPLEFORMAT_INT ? CV_8S : CV_8U; + m_type = CV_MAKETYPE(depth, 3); + result = true; + } + else + CV_Error(cv::Error::StsError, "bitsperpixel value is 4 should be palette."); + break; case 8: { //Palette color, the value of the component is used as an index into the red, @@ -425,18 +436,15 @@ static void fixOrientationFull(Mat &img, int orientation) * For 8 bit some corrections are done by TIFFReadRGBAStrip/Tile already. * Not so for 16/32/64 bit. */ -static void fixOrientation(Mat &img, uint16 orientation, int dst_bpp) +static void fixOrientation(Mat &img, uint16 orientation, bool isOrientationFull) { - switch(dst_bpp) { - case 8: - fixOrientationPartial(img, orientation); - break; - - case 16: - case 32: - case 64: - fixOrientationFull(img, orientation); - break; + if( isOrientationFull ) + { + fixOrientationFull(img, orientation); + } + else + { + fixOrientationPartial(img, orientation); } } @@ -620,17 +628,7 @@ bool TiffDecoder::readData( Mat& img ) (img_orientation == ORIENTATION_BOTRIGHT || img_orientation == ORIENTATION_RIGHTBOT || img_orientation == ORIENTATION_BOTLEFT || img_orientation == ORIENTATION_LEFTBOT); int wanted_channels = normalizeChannelsNumber(img.channels()); - - if (dst_bpp == 8) - { - char errmsg[1024]; - if (!TIFFRGBAImageOK(tif, errmsg)) - { - CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg); - close(); - return false; - } - } + bool doReadScanline = false; uint32 tile_width0 = m_width, tile_height0 = 0; @@ -660,21 +658,123 @@ bool TiffDecoder::readData( Mat& img ) const uint64_t MAX_TILE_SIZE = (CV_BIG_UINT(1) << 30); CV_CheckLE((int)ncn, 4, ""); CV_CheckLE((int)bpp, 64, ""); - CV_Assert(((uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte)) < MAX_TILE_SIZE) && "TIFF tile size is too large: >= 1Gb"); if (dst_bpp == 8) { - // we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA - bpp = 8; - ncn = 4; + const int _ncn = 4; // Read RGBA + const int _bpp = 8; // Read 8bit + + // if buffer_size(as 32bit RGBA) >= MAX_TILE_SIZE*95%, + // we will use TIFFReadScanline function. + + if ( + (uint64_t)tile_width0 * tile_height0 * _ncn * std::max(1, (int)(_bpp / bitsPerByte)) + >= + ( (uint64_t) MAX_TILE_SIZE * 95 / 100) + ) + { + uint16_t planerConfig = (uint16)-1; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig)); + + doReadScanline = (!is_tiled) // no tile + && + ( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) ) + && + ( ( bpp == 8 ) || ( bpp == 16 ) ) + && + (tile_height0 == (uint32_t) m_height) // single strip + && + ( + (photometric == PHOTOMETRIC_MINISWHITE) + || + (photometric == PHOTOMETRIC_MINISBLACK) + || + (photometric == PHOTOMETRIC_RGB) + ) + && + (planerConfig != PLANARCONFIG_SEPARATE); + + // Currently only EXTRASAMPLE_ASSOCALPHA is supported. + if ( doReadScanline && ( ncn == 4 ) ) + { + uint16_t extra_samples_num; + uint16_t *extra_samples = NULL; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples )); + doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA ); + } + } + + if ( !doReadScanline ) + { + // we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA + bpp = 8; + ncn = 4; + + char errmsg[1024]; + if (!TIFFRGBAImageOK(tif, errmsg)) + { + CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg); + close(); + return false; + } + } + } + else if (dst_bpp == 16) + { + // if buffer_size >= MAX_TILE_SIZE*95%, + // we will use TIFFReadScanline function. + if ( + (uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte)) + >= + MAX_TILE_SIZE * 95 / 100 + ) + { + uint16_t planerConfig = (uint16)-1; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig)); + + doReadScanline = (!is_tiled) // no tile + && + ( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) ) + && + ( ( bpp == 8 ) || ( bpp == 16 ) ) + && + (tile_height0 == (uint32_t) m_height) // single strip + && + ( + (photometric == PHOTOMETRIC_MINISWHITE) + || + (photometric == PHOTOMETRIC_MINISBLACK) + || + (photometric == PHOTOMETRIC_RGB) + ) + && + (planerConfig != PLANARCONFIG_SEPARATE); + + // Currently only EXTRASAMPLE_ASSOCALPHA is supported. + if ( doReadScanline && ( ncn == 4 ) ) + { + uint16_t extra_samples_num; + uint16_t *extra_samples = NULL; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples )); + doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA ); + } + } } else if (dst_bpp == 32 || dst_bpp == 64) { CV_Assert(ncn == img.channels()); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)); } + + if ( doReadScanline ) + { + // Read each scanlines. + tile_height0 = 1; + } + const size_t src_buffer_bytes_per_row = divUp(static_cast(ncn * tile_width0 * bpp), static_cast(bitsPerByte)); const size_t src_buffer_size = tile_height0 * src_buffer_bytes_per_row; + CV_CheckLT(src_buffer_size, MAX_TILE_SIZE, "buffer_size is too large: >= 1Gb"); const size_t src_buffer_unpacked_bytes_per_row = divUp(static_cast(ncn * tile_width0 * dst_bpp), static_cast(bitsPerByte)); const size_t src_buffer_unpacked_size = tile_height0 * src_buffer_unpacked_bytes_per_row; const bool needsUnpacking = (bpp < dst_bpp); @@ -682,8 +782,20 @@ bool TiffDecoder::readData( Mat& img ) uchar* src_buffer = _src_buffer.data(); AutoBuffer _src_buffer_unpacked(needsUnpacking ? src_buffer_unpacked_size : 0); uchar* src_buffer_unpacked = needsUnpacking ? _src_buffer_unpacked.data() : nullptr; + + if ( doReadScanline ) + { + CV_CheckGE(src_buffer_size, + static_cast(TIFFScanlineSize(tif)), + "src_buffer_size is smaller than TIFFScanlineSize()."); + } + int tileidx = 0; + #define MAKE_FLAG(a,b) ( (a << 8) | b ) + const int convert_flag = MAKE_FLAG( ncn, wanted_channels ); + const bool isNeedConvert16to8 = ( doReadScanline ) && ( bpp == 16 ) && ( dst_bpp == 8); + for (int y = 0; y < m_height; y += (int)tile_height0) { int tile_height = std::min((int)tile_height0, m_height - y); @@ -699,7 +811,29 @@ bool TiffDecoder::readData( Mat& img ) case 8: { uchar* bstart = src_buffer; - if (!is_tiled) + if (doReadScanline) + { + CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32*)src_buffer, y) >= 0); + + if ( isNeedConvert16to8 ) + { + // Convert buffer image from 16bit to 8bit. + int ix; + for ( ix = 0 ; ix < tile_width * ncn - 4; ix += 4 ) + { + src_buffer[ ix ] = src_buffer[ ix * 2 + 1 ]; + src_buffer[ ix + 1 ] = src_buffer[ ix * 2 + 3 ]; + src_buffer[ ix + 2 ] = src_buffer[ ix * 2 + 5 ]; + src_buffer[ ix + 3 ] = src_buffer[ ix * 2 + 7 ]; + } + + for ( ; ix < tile_width * ncn ; ix ++ ) + { + src_buffer[ ix ] = src_buffer[ ix * 2 + 1]; + } + } + } + else if (!is_tiled) { CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32*)src_buffer)); } @@ -710,9 +844,65 @@ bool TiffDecoder::readData( Mat& img ) bstart += (tile_height0 - tile_height) * tile_width0 * 4; } + uchar* img_line_buffer = (uchar*) img.ptr(y, 0); + for (int i = 0; i < tile_height; i++) { - if (color) + if (doReadScanline) + { + switch ( convert_flag ) + { + case MAKE_FLAG( 1, 1 ): // GRAY to GRAY + memcpy( (void*) img_line_buffer, + (void*) bstart, + tile_width * sizeof(uchar) ); + break; + + case MAKE_FLAG( 1, 3 ): // GRAY to BGR + icvCvt_Gray2BGR_8u_C1C3R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 3, 1): // RGB to GRAY + icvCvt_BGR2Gray_8u_C3C1R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 3, 3 ): // RGB to BGR + icvCvt_BGR2RGB_8u_C3R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 4, 1 ): // RGBA to GRAY + icvCvt_BGRA2Gray_8u_C4C1R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 4, 3 ): // RGBA to BGR + icvCvt_BGRA2BGR_8u_C4C3R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1), 2 ); + break; + + case MAKE_FLAG( 4, 4 ): // RGBA to BGRA + icvCvt_BGRA2RGBA_8u_C4R(bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + default: + CV_LOG_ONCE_ERROR(NULL, "OpenCV TIFF(line " << __LINE__ << "): Unsupported convertion :" + << " bpp = " << bpp << " ncn = " << (int)ncn + << " wanted_channels =" << wanted_channels ); + break; + } + #undef MAKE_FLAG + } + else if (color) { if (wanted_channels == 4) { @@ -741,7 +931,11 @@ bool TiffDecoder::readData( Mat& img ) case 16: { - if (!is_tiled) + if (doReadScanline) + { + CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32*)src_buffer, y) >= 0); + } + else if (!is_tiled) { CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32*)src_buffer, src_buffer_size) >= 0); } @@ -862,7 +1056,11 @@ bool TiffDecoder::readData( Mat& img ) } if (bpp < dst_bpp) img *= (1<<(dst_bpp-bpp)); - fixOrientation(img, img_orientation, dst_bpp); + + // If TIFFReadRGBA* function is used -> fixOrientationPartial(). + // Otherwise -> fixOrientationFull(). + fixOrientation(img, img_orientation, + ( ( dst_bpp != 8 ) && ( !doReadScanline ) ) ); } if (m_hdr && depth >= CV_32F) @@ -887,6 +1085,7 @@ TiffEncoder::~TiffEncoder() ImageEncoder TiffEncoder::newEncoder() const { + cv_tiffSetErrorHandler(); return makePtr(); } diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index 3860abb64e..6f5cdfb6ab 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -126,6 +126,8 @@ bool WebPDecoder::readHeader() WebPBitstreamFeatures features; if (VP8_STATUS_OK == WebPGetFeatures(header, sizeof(header), &features)) { + CV_CheckEQ(features.has_animation, 0, "WebP backend does not support animated webp images"); + m_width = features.width; m_height = features.height; diff --git a/modules/imgcodecs/src/grfmts.hpp b/modules/imgcodecs/src/grfmts.hpp index 496cb192d5..637538d223 100644 --- a/modules/imgcodecs/src/grfmts.hpp +++ b/modules/imgcodecs/src/grfmts.hpp @@ -49,6 +49,7 @@ #include "grfmt_pxm.hpp" #include "grfmt_pfm.hpp" #include "grfmt_tiff.hpp" +#include "grfmt_spng.hpp" #include "grfmt_png.hpp" #include "grfmt_jpeg2000.hpp" #include "grfmt_jpeg2000_openjpeg.hpp" diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index e9b6d0517c..e7b5c7035f 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -54,6 +54,8 @@ #include #include #include +#include + /****************************************************************************************\ @@ -167,7 +169,10 @@ struct ImageCodecInitializer decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); #endif - #ifdef HAVE_PNG + #ifdef HAVE_SPNG + decoders.push_back( makePtr() ); + encoders.push_back( makePtr() ); + #elif defined(HAVE_PNG) decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); #endif @@ -658,57 +663,14 @@ bool imreadmulti(const String& filename, std::vector& mats, int start, int static size_t imcount_(const String& filename, int flags) { - /// Search for the relevant decoder to handle the imagery - ImageDecoder decoder; - -#ifdef HAVE_GDAL - if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) { - decoder = GdalDecoder().newDecoder(); + try{ + ImageCollection collection(filename, flags); + return collection.size(); + } catch(cv::Exception const& e) { + // Reading header or finding decoder for the filename is failed + std::cerr << "imcount_('" << filename << "'): can't read header or can't find decoder: " << e.what() << std::endl << std::flush; } - else { -#else - CV_UNUSED(flags); -#endif - decoder = findDecoder(filename); -#ifdef HAVE_GDAL - } -#endif - - /// if no decoder was found, return nothing. - if (!decoder) { - return 0; - } - - /// 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 0; - } - catch (const cv::Exception& e) - { - std::cerr << "imcount_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush; - return 0; - } - catch (...) - { - std::cerr << "imcount_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush; - return 0; - } - - size_t result = 1; - - - while (decoder->nextPage()) - { - ++result; - } - - return result; + return 0; } size_t imcount(const String& filename, int flags) @@ -754,7 +716,9 @@ static bool imwrite_( const String& filename, const std::vector& img_vec, } encoder->setDestination( filename ); - CV_Assert(params.size() <= CV_IO_MAX_IMAGE_PARAMS*2); + + CV_Check(params.size(), (params.size() & 1) == 0, "Encoding 'params' must be key-value pairs"); + CV_CheckLE(params.size(), (size_t)(CV_IO_MAX_IMAGE_PARAMS*2), ""); bool code = false; try { @@ -967,6 +931,157 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst ) return *dst; } +static bool +imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int count) +{ + CV_Assert(!buf.empty()); + CV_Assert(buf.isContinuous()); + CV_Assert(buf.checkVector(1, CV_8U) > 0); + Mat buf_row = buf.reshape(1, 1); // decoders expects single row, avoid issues with vector columns + + String filename; + + ImageDecoder decoder = findDecoder(buf_row); + if (!decoder) + return 0; + + if (count < 0) { + count = std::numeric_limits::max(); + } + + if (!decoder->setSource(buf_row)) + { + filename = tempfile(); + FILE* f = fopen(filename.c_str(), "wb"); + if (!f) + return 0; + size_t bufSize = buf_row.total() * buf.elemSize(); + if (fwrite(buf_row.ptr(), 1, bufSize, f) != bufSize) + { + fclose(f); + CV_Error(Error::StsError, "failed to write image data to temporary file"); + } + if (fclose(f) != 0) + { + CV_Error(Error::StsError, "failed to write image data to temporary file"); + } + decoder->setSource(filename); + } + + // read the header to make sure it succeeds + bool success = false; + try + { + // read the header to make sure it succeeds + if (decoder->readHeader()) + success = true; + } + catch (const cv::Exception& e) + { + std::cerr << "imreadmulti_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush; + } + catch (...) + { + std::cerr << "imreadmulti_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush; + } + + int current = start; + while (success && current > 0) + { + if (!decoder->nextPage()) + { + success = false; + break; + } + --current; + } + + if (!success) + { + decoder.release(); + if (!filename.empty()) + { + if (0 != remove(filename.c_str())) + { + std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush; + } + } + return 0; + } + + while (current < count) + { + // grab the decoded type + int type = decoder->type(); + if ((flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED) + { + if ((flags & IMREAD_ANYDEPTH) == 0) + type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type)); + + if ((flags & IMREAD_COLOR) != 0 || + ((flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1)) + type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3); + else + type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1); + } + + // 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) + { + std::cerr << "imreadmulti_('" << filename << "'): can't read data: " << e.what() << std::endl << std::flush; + } + catch (...) + { + std::cerr << "imreadmulti_('" << filename << "'): can't read data: unknown exception" << std::endl << std::flush; + } + 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); + } + + mats.push_back(mat); + if (!decoder->nextPage()) + { + break; + } + ++current; + } + + if (!filename.empty()) + { + if (0 != remove(filename.c_str())) + { + std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush; + } + } + + if (!success) + mats.clear(); + return !mats.empty(); +} + +bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector& mats) +{ + CV_TRACE_FUNCTION(); + + Mat buf = _buf.getMat(); + return imdecodemulti_(buf, flags, mats, 0, -1); +} + bool imencode( const String& ext, InputArray _image, std::vector& buf, const std::vector& params ) { @@ -990,6 +1105,9 @@ bool imencode( const String& ext, InputArray _image, image = temp; } + CV_Check(params.size(), (params.size() & 1) == 0, "Encoding 'params' must be key-value pairs"); + CV_CheckLE(params.size(), (size_t)(CV_IO_MAX_IMAGE_PARAMS*2), ""); + bool code; if( encoder->setDestination(buf) ) { @@ -1032,6 +1150,247 @@ bool haveImageWriter( const String& filename ) return !encoder.empty(); } +class ImageCollection::Impl { +public: + Impl() = default; + Impl(const std::string& filename, int flags); + void init(String const& filename, int flags); + size_t size() const; + Mat& at(int index); + Mat& operator[](int index); + void releaseCache(int index); + ImageCollection::iterator begin(ImageCollection* ptr); + ImageCollection::iterator end(ImageCollection* ptr); + Mat read(); + int width() const; + int height() const; + bool readHeader(); + Mat readData(); + bool advance(); + int currentIndex() const; + void reset(); + +private: + String m_filename; + int m_flags{}; + std::size_t m_size{}; + int m_width{}; + int m_height{}; + int m_current{}; + std::vector m_pages; + ImageDecoder m_decoder; +}; + +ImageCollection::Impl::Impl(std::string const& filename, int flags) { + this->init(filename, flags); +} + +void ImageCollection::Impl::init(String const& filename, int flags) { + m_filename = filename; + m_flags = flags; + +#ifdef HAVE_GDAL + if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) { + m_decoder = GdalDecoder().newDecoder(); + } + else { +#endif + m_decoder = findDecoder(filename); +#ifdef HAVE_GDAL + } +#endif + + + CV_Assert(m_decoder); + m_decoder->setSource(filename); + CV_Assert(m_decoder->readHeader()); + + // count the pages of the image collection + size_t count = 1; + while(m_decoder->nextPage()) count++; + + m_size = count; + m_pages.resize(m_size); + // Reinitialize the decoder because we advanced to the last page while counting the pages of the image +#ifdef HAVE_GDAL + if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) { + m_decoder = GdalDecoder().newDecoder(); + } + else { +#endif + m_decoder = findDecoder(m_filename); +#ifdef HAVE_GDAL + } +#endif + + m_decoder->setSource(m_filename); + m_decoder->readHeader(); +} + +size_t ImageCollection::Impl::size() const { return m_size; } + +Mat ImageCollection::Impl::read() { + auto result = this->readHeader(); + if(!result) { + return {}; + } + return this->readData(); +} + +int ImageCollection::Impl::width() const { + return m_width; +} + +int ImageCollection::Impl::height() const { + return m_height; +} + +bool ImageCollection::Impl::readHeader() { + bool status = m_decoder->readHeader(); + m_width = m_decoder->width(); + m_height = m_decoder->height(); + return status; +} + +// readHeader must be called before calling this method +Mat ImageCollection::Impl::readData() { + int type = m_decoder->type(); + if ((m_flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && m_flags != IMREAD_UNCHANGED) { + if ((m_flags & IMREAD_ANYDEPTH) == 0) + type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type)); + + if ((m_flags & IMREAD_COLOR) != 0 || + ((m_flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1)) + type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3); + else + type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1); + } + + // established the required input image size + Size size = validateInputImageSize(Size(m_width, m_height)); + + Mat mat(size.height, size.width, type); + bool success = false; + try { + if (m_decoder->readData(mat)) + success = true; + } + catch (const cv::Exception &e) { + std::cerr << "ImageCollection class: can't read data: " << e.what() << std::endl << std::flush; + } + catch (...) { + std::cerr << "ImageCollection class:: can't read data: unknown exception" << std::endl << std::flush; + } + if (!success) + return cv::Mat(); + + if ((m_flags & IMREAD_IGNORE_ORIENTATION) == 0 && m_flags != IMREAD_UNCHANGED) { + ApplyExifOrientation(m_decoder->getExifTag(ORIENTATION), mat); + } + + return mat; +} + +bool ImageCollection::Impl::advance() { ++m_current; return m_decoder->nextPage(); } + +int ImageCollection::Impl::currentIndex() const { return m_current; } + +ImageCollection::iterator ImageCollection::Impl::begin(ImageCollection* ptr) { return ImageCollection::iterator(ptr); } + +ImageCollection::iterator ImageCollection::Impl::end(ImageCollection* ptr) { return ImageCollection::iterator(ptr, static_cast(this->size())); } + +void ImageCollection::Impl::reset() { + m_current = 0; +#ifdef HAVE_GDAL + if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) { + m_decoder = GdalDecoder().newDecoder(); + } + else { +#endif + m_decoder = findDecoder(m_filename); +#ifdef HAVE_GDAL + } +#endif + + m_decoder->setSource(m_filename); + m_decoder->readHeader(); +} + +Mat& ImageCollection::Impl::at(int index) { + CV_Assert(index >= 0 && size_t(index) < m_size); + return operator[](index); +} + +Mat& ImageCollection::Impl::operator[](int index) { + if(m_pages.at(index).empty()) { + // We can't go backward in multi images. If the page is not in vector yet, + // go back to first page and advance until the desired page and read it into memory + if(m_current != index) { + reset(); + for(int i = 0; i != index && advance(); ++i) {} + } + m_pages[index] = read(); + } + return m_pages[index]; +} + +void ImageCollection::Impl::releaseCache(int index) { + CV_Assert(index >= 0 && size_t(index) < m_size); + m_pages[index].release(); +} + +/* ImageCollection API*/ + +ImageCollection::ImageCollection() : pImpl(new Impl()) {} + +ImageCollection::ImageCollection(const std::string& filename, int flags) : pImpl(new Impl(filename, flags)) {} + +void ImageCollection::init(const String& img, int flags) { pImpl->init(img, flags); } + +size_t ImageCollection::size() const { return pImpl->size(); } + +const Mat& ImageCollection::at(int index) { return pImpl->at(index); } + +const Mat& ImageCollection::operator[](int index) { return pImpl->operator[](index); } + +void ImageCollection::releaseCache(int index) { pImpl->releaseCache(index); } + +Ptr ImageCollection::getImpl() { return pImpl; } + +/* Iterator API */ + +ImageCollection::iterator ImageCollection::begin() { return pImpl->begin(this); } + +ImageCollection::iterator ImageCollection::end() { return pImpl->end(this); } + +ImageCollection::iterator::iterator(ImageCollection* col) : m_pCollection(col), m_curr(0) {} + +ImageCollection::iterator::iterator(ImageCollection* col, int end) : m_pCollection(col), m_curr(end) {} + +Mat& ImageCollection::iterator::operator*() { + CV_Assert(m_pCollection); + return m_pCollection->getImpl()->operator[](m_curr); +} + +Mat* ImageCollection::iterator::operator->() { + CV_Assert(m_pCollection); + return &m_pCollection->getImpl()->operator[](m_curr); +} + +ImageCollection::iterator& ImageCollection::iterator::operator++() { + if(m_pCollection->pImpl->currentIndex() == m_curr) { + m_pCollection->pImpl->advance(); + } + m_curr++; + return *this; +} + +ImageCollection::iterator ImageCollection::iterator::operator++(int) { + iterator tmp = *this; + ++(*this); + return tmp; +} + } /* End of file. */ diff --git a/modules/imgcodecs/test/test_grfmt.cpp b/modules/imgcodecs/test/test_grfmt.cpp index a6af4b92e2..fa6c54c8d0 100644 --- a/modules/imgcodecs/test/test_grfmt.cpp +++ b/modules/imgcodecs/test/test_grfmt.cpp @@ -211,7 +211,7 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq) const string all_exts[] = { -#ifdef HAVE_PNG +#if defined(HAVE_PNG) || defined(HAVE_SPNG) ".png", #endif #ifdef HAVE_TIFF @@ -343,21 +343,45 @@ TEST(Imgcodecs_Hdr, regression) Mat img_no_rle = imread(name_no_rle, -1); ASSERT_FALSE(img_no_rle.empty()) << "Could not open " << name_no_rle; - double min = 0.0, max = 1.0; - minMaxLoc(abs(img_rle - img_no_rle), &min, &max); - ASSERT_FALSE(max > DBL_EPSILON); + EXPECT_EQ(cvtest::norm(img_rle, img_no_rle, NORM_INF), 0.0); + string tmp_file_name = tempfile(".hdr"); - vectorparam(1); + vector param(2); + param[0] = IMWRITE_HDR_COMPRESSION; for(int i = 0; i < 2; i++) { - param[0] = i; + param[1] = i; imwrite(tmp_file_name, img_rle, param); Mat written_img = imread(tmp_file_name, -1); - ASSERT_FALSE(written_img.empty()) << "Could not open " << tmp_file_name; - minMaxLoc(abs(img_rle - written_img), &min, &max); - ASSERT_FALSE(max > DBL_EPSILON); + EXPECT_EQ(cvtest::norm(written_img, img_rle, NORM_INF), 0.0); } remove(tmp_file_name.c_str()); } + +TEST(Imgcodecs_Hdr, regression_imencode) +{ + string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/"; + string name = folder + "rle.hdr"; + Mat img_ref = imread(name, -1); + ASSERT_FALSE(img_ref.empty()) << "Could not open " << name; + + vector params(2); + params[0] = IMWRITE_HDR_COMPRESSION; + { + vector buf; + params[1] = IMWRITE_HDR_COMPRESSION_NONE; + imencode(".hdr", img_ref, buf, params); + Mat img = imdecode(buf, -1); + EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0); + } + { + vector buf; + params[1] = IMWRITE_HDR_COMPRESSION_RLE; + imencode(".hdr", img_ref, buf, params); + Mat img = imdecode(buf, -1); + EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0); + } +} + #endif #ifdef HAVE_IMGCODEC_PXM diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index f71fabc7e4..cdc7da39b2 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -5,7 +5,7 @@ namespace opencv_test { namespace { -#ifdef HAVE_PNG +#if defined(HAVE_PNG) || defined(HAVE_SPNG) TEST(Imgcodecs_Png, write_big) { @@ -186,6 +186,225 @@ const string exif_files[] = INSTANTIATE_TEST_CASE_P(ExifFiles, Imgcodecs_PNG_Exif, testing::ValuesIn(exif_files)); + +typedef testing::TestWithParam Imgcodecs_Png_PngSuite; + +TEST_P(Imgcodecs_Png_PngSuite, decode) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "pngsuite/" + GetParam() + ".png"; + const string xml_filename = root + "pngsuite/" + GetParam() + ".xml"; + FileStorage fs(xml_filename, FileStorage::READ); + EXPECT_TRUE(fs.isOpened()); + + Mat src = imread(filename, IMREAD_UNCHANGED); + Mat gt; + fs.getFirstTopLevelNode() >> gt; + + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt); +} + +const string pngsuite_files[] = +{ + "basi0g01", + "basi0g02", + "basi0g04", + "basi0g08", + "basi0g16", + "basi2c08", + "basi2c16", + "basi3p01", + "basi3p02", + "basi3p04", + "basi3p08", + "basi4a08", + "basi4a16", + "basi6a08", + "basi6a16", + "basn0g01", + "basn0g02", + "basn0g04", + "basn0g08", + "basn0g16", + "basn2c08", + "basn2c16", + "basn3p01", + "basn3p02", + "basn3p04", + "basn3p08", + "basn4a08", + "basn4a16", + "basn6a08", + "basn6a16", + "bgai4a08", + "bgai4a16", + "bgan6a08", + "bgan6a16", + "bgbn4a08", + "bggn4a16", + "bgwn6a08", + "bgyn6a16", + "ccwn2c08", + "ccwn3p08", + "cdfn2c08", + "cdhn2c08", + "cdsn2c08", + "cdun2c08", + "ch1n3p04", + "ch2n3p08", + "cm0n0g04", + "cm7n0g04", + "cm9n0g04", + "cs3n2c16", + "cs3n3p08", + "cs5n2c08", + "cs5n3p08", + "cs8n2c08", + "cs8n3p08", + "ct0n0g04", + "ct1n0g04", + "cten0g04", + "ctfn0g04", + "ctgn0g04", + "cthn0g04", + "ctjn0g04", + "ctzn0g04", + "exif2c08", + "f00n0g08", + "f00n2c08", + "f01n0g08", + "f01n2c08", + "f02n0g08", + "f02n2c08", + "f03n0g08", + "f03n2c08", + "f04n0g08", + "f04n2c08", + "f99n0g04", + "g03n0g16", + "g03n2c08", + "g03n3p04", + "g04n0g16", + "g04n2c08", + "g04n3p04", + "g05n0g16", + "g05n2c08", + "g05n3p04", + "g07n0g16", + "g07n2c08", + "g07n3p04", + "g10n0g16", + "g10n2c08", + "g10n3p04", + "g25n0g16", + "g25n2c08", + "g25n3p04", + "oi1n0g16", + "oi1n2c16", + "oi2n0g16", + "oi2n2c16", + "oi4n0g16", + "oi4n2c16", + "oi9n0g16", + "oi9n2c16", + "pp0n2c16", + "pp0n6a08", + "ps1n0g08", + "ps1n2c16", + "ps2n0g08", + "ps2n2c16", + "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", + "tbbn0g04", + "tbbn2c16", + "tbbn3p08", + "tbgn2c16", + "tbgn3p08", + "tbrn2c08", + "tbwn0g16", + "tbwn3p08", + "tbyn3p08", + "tm3n3p02", + "tp0n0g08", + "tp0n2c08", + "tp0n3p08", + "tp1n3p08", + "z00n2c08", + "z03n2c08", + "z06n2c08", + "z09n2c08", +}; + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite, + testing::ValuesIn(pngsuite_files)); + +typedef testing::TestWithParam Imgcodecs_Png_PngSuite_Corrupted; + +TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "pngsuite/" + GetParam() + ".png"; + + Mat src = imread(filename, IMREAD_UNCHANGED); + + // Corrupted files should not be read + EXPECT_TRUE(src.empty()); +} + +const string pngsuite_files_corrupted[] = { + "xc1n0g08", + "xc9n2c08", + "xcrn0g04", + "xcsn0g01", + "xd0n2c08", + "xd3n2c08", + "xd9n2c08", + "xdtn0g01", + "xhdn0g08", + "xlfn0g04", + "xs1n0g01", + "xs2n0g01", + "xs4n0g01", + "xs7n0g01", +}; + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Corrupted, + testing::ValuesIn(pngsuite_files_corrupted)); + #endif // HAVE_PNG }} // namespace diff --git a/modules/imgcodecs/test/test_read_write.cpp b/modules/imgcodecs/test/test_read_write.cpp index 9dbd2e33c7..2320147a4e 100644 --- a/modules/imgcodecs/test/test_read_write.cpp +++ b/modules/imgcodecs/test/test_read_write.cpp @@ -28,7 +28,7 @@ const tuple images[] = #ifdef HAVE_JPEG make_tuple("../cv/imgproc/stuff.jpg", Size(640, 480)), #endif -#ifdef HAVE_PNG +#if defined(HAVE_PNG) || defined(HAVE_SPNG) make_tuple("../cv/shared/pic1.png", Size(400, 300)), #endif make_tuple("../highgui/readwrite/ordinary.bmp", Size(480, 272)), @@ -148,7 +148,7 @@ typedef string Ext; typedef testing::TestWithParam Imgcodecs_Image; const string exts[] = { -#ifdef HAVE_PNG +#if defined(HAVE_PNG) || defined(HAVE_SPNG) "png", #endif #ifdef HAVE_TIFF @@ -303,4 +303,201 @@ TEST(Imgcodecs_Image, write_umat) EXPECT_EQ(0, remove(dst_name.c_str())); } +TEST(Imgcodecs_Image, multipage_collection_size) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + + ImageCollection collection(filename, IMREAD_ANYCOLOR); + EXPECT_EQ((std::size_t)6, collection.size()); +} + +TEST(Imgcodecs_Image, multipage_collection_read_pages_iterator) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + const string page_files[] = { + root + "readwrite/multipage_p1.tif", + root + "readwrite/multipage_p2.tif", + root + "readwrite/multipage_p3.tif", + root + "readwrite/multipage_p4.tif", + root + "readwrite/multipage_p5.tif", + root + "readwrite/multipage_p6.tif" + }; + + ImageCollection collection(filename, IMREAD_ANYCOLOR); + + auto collectionBegin = collection.begin(); + for(size_t i = 0; i < collection.size(); ++i, ++collectionBegin) + { + double diff = cv::norm(collectionBegin.operator*(), imread(page_files[i]), NORM_INF); + EXPECT_EQ(0., diff); + } +} + +TEST(Imgcodecs_Image, multipage_collection_two_iterator) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + const string page_files[] = { + root + "readwrite/multipage_p1.tif", + root + "readwrite/multipage_p2.tif", + root + "readwrite/multipage_p3.tif", + root + "readwrite/multipage_p4.tif", + root + "readwrite/multipage_p5.tif", + root + "readwrite/multipage_p6.tif" + }; + + ImageCollection collection(filename, IMREAD_ANYCOLOR); + auto firstIter = collection.begin(); + auto secondIter = collection.begin(); + + // Decode all odd pages then decode even pages -> 1, 0, 3, 2 ... + firstIter++; + for(size_t i = 1; i < collection.size(); i += 2, ++firstIter, ++firstIter, ++secondIter, ++secondIter) { + Mat mat = *firstIter; + double diff = cv::norm(mat, imread(page_files[i]), NORM_INF); + EXPECT_EQ(0., diff); + Mat evenMat = *secondIter; + diff = cv::norm(evenMat, imread(page_files[i-1]), NORM_INF); + EXPECT_EQ(0., diff); + } +} + +TEST(Imgcodecs_Image, multipage_collection_operator_plusplus) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + + // operator++ test + ImageCollection collection(filename, IMREAD_ANYCOLOR); + auto firstIter = collection.begin(); + auto secondIter = firstIter++; + + // firstIter points to second page, secondIter points to first page + double diff = cv::norm(*firstIter, *secondIter, NORM_INF); + EXPECT_NE(diff, 0.); +} + +TEST(Imgcodecs_Image, multipage_collection_backward_decoding) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + const string page_files[] = { + root + "readwrite/multipage_p1.tif", + root + "readwrite/multipage_p2.tif", + root + "readwrite/multipage_p3.tif", + root + "readwrite/multipage_p4.tif", + root + "readwrite/multipage_p5.tif", + root + "readwrite/multipage_p6.tif" + }; + + ImageCollection collection(filename, IMREAD_ANYCOLOR); + EXPECT_EQ((size_t)6, collection.size()); + + // backward decoding -> 5,4,3,2,1,0 + for(int i = (int)collection.size() - 1; i >= 0; --i) + { + cv::Mat ithPage = imread(page_files[i]); + EXPECT_FALSE(ithPage.empty()); + double diff = cv::norm(collection[i], ithPage, NORM_INF); + EXPECT_EQ(diff, 0.); + } + + for(int i = 0; i < (int)collection.size(); ++i) + { + collection.releaseCache(i); + } + + double diff = cv::norm(collection[2], imread(page_files[2]), NORM_INF); + EXPECT_EQ(diff, 0.); +} + +TEST(ImgCodecs, multipage_collection_decoding_range_based_for_loop_test) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + const string page_files[] = { + root + "readwrite/multipage_p1.tif", + root + "readwrite/multipage_p2.tif", + root + "readwrite/multipage_p3.tif", + root + "readwrite/multipage_p4.tif", + root + "readwrite/multipage_p5.tif", + root + "readwrite/multipage_p6.tif" + }; + + ImageCollection collection(filename, IMREAD_ANYCOLOR); + + size_t index = 0; + for(auto &i: collection) + { + cv::Mat ithPage = imread(page_files[index]); + EXPECT_FALSE(ithPage.empty()); + double diff = cv::norm(i, ithPage, NORM_INF); + EXPECT_EQ(0., diff); + ++index; + } + EXPECT_EQ(index, collection.size()); + + index = 0; + for(auto &&i: collection) + { + cv::Mat ithPage = imread(page_files[index]); + EXPECT_FALSE(ithPage.empty()); + double diff = cv::norm(i, ithPage, NORM_INF); + EXPECT_EQ(0., diff); + ++index; + } + EXPECT_EQ(index, collection.size()); +} + +TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + + ImageCollection imcol(filename, IMREAD_ANYCOLOR); + + auto it0 = imcol.begin(), it1 = it0, it2 = it0; + vector img(6); + for (int i = 0; i < 6; i++) { + img[i] = *it0; + it0->release(); + ++it0; + } + + for (int i = 0; i < 3; i++) { + ++it2; + } + + for (int i = 0; i < 3; i++) { + auto img2 = *it2; + auto img1 = *it1; + ++it2; + ++it1; + EXPECT_TRUE(cv::norm(img2, img[i+3], NORM_INF) == 0); + EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0); + } +} + + +TEST(Imgcodecs_Params, imwrite_regression_22752) +{ + const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0)); + vector params; + params.push_back(IMWRITE_JPEG_QUALITY); +// params.push_back(100)); // Forget it. + EXPECT_ANY_THROW(cv::imwrite("test.jpg", img, params)); // parameters size or missing JPEG codec +} + +TEST(Imgcodecs_Params, imencode_regression_22752) +{ + const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0)); + vector params; + params.push_back(IMWRITE_JPEG_QUALITY); +// params.push_back(100)); // Forget it. + vector buf; + EXPECT_ANY_THROW(cv::imencode("test.jpg", img, buf, params)); // parameters size or missing JPEG codec +} + }} // namespace diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index b7b6b95d83..c7d04cd9b9 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -2,6 +2,8 @@ // 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" +#include "opencv2/core/utils/logger.hpp" +#include "opencv2/core/utils/configuration.private.hpp" namespace opencv_test { namespace { @@ -46,6 +48,432 @@ TEST(Imgcodecs_Tiff, decode_tile16384x16384) EXPECT_EQ(0, remove(file4.c_str())); } +//================================================================================================== +// See https://github.com/opencv/opencv/issues/22388 + +/** + * Dummy enum to show combination of IMREAD_*. + */ +enum ImreadMixModes +{ + IMREAD_MIX_UNCHANGED = IMREAD_UNCHANGED , + IMREAD_MIX_GRAYSCALE = IMREAD_GRAYSCALE , + IMREAD_MIX_COLOR = IMREAD_COLOR , + IMREAD_MIX_GRAYSCALE_ANYDEPTH = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH , + IMREAD_MIX_GRAYSCALE_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYCOLOR, + IMREAD_MIX_GRAYSCALE_ANYDEPTH_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR, + IMREAD_MIX_COLOR_ANYDEPTH = IMREAD_COLOR | IMREAD_ANYDEPTH , + IMREAD_MIX_COLOR_ANYCOLOR = IMREAD_COLOR | IMREAD_ANYCOLOR, + IMREAD_MIX_COLOR_ANYDEPTH_ANYCOLOR = IMREAD_COLOR | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR +}; + +typedef tuple< uint64_t, tuple, ImreadMixModes > Bufsize_and_Type; +typedef testing::TestWithParam Imgcodecs_Tiff_decode_Huge; + +static inline +void PrintTo(const ImreadMixModes& val, std::ostream* os) +{ + PrintTo( static_cast(val), os ); +} + +TEST_P(Imgcodecs_Tiff_decode_Huge, regression) +{ + // Get test parameters + const uint64_t buffer_size = get<0>(GetParam()); + const string mat_type_string = get<0>(get<1>(GetParam())); + const int mat_type = get<1>(get<1>(GetParam())); + const int imread_mode = get<2>(GetParam()); + + // Detect data file + const string req_filename = cv::format("readwrite/huge-tiff/%s_%zu.tif", mat_type_string.c_str(), (size_t)buffer_size); + const string filename = findDataFile( req_filename ); + + // Preparation process for test + { + // Convert from mat_type and buffer_size to tiff file information. + const uint64_t width = 32768; + int ncn = CV_MAT_CN(mat_type); + int depth = ( CV_MAT_DEPTH(mat_type) == CV_16U) ? 2 : 1; // 16bit or 8 bit + const uint64_t height = (uint64_t) buffer_size / width / ncn / depth; + const uint64_t base_scanline_size = (uint64_t) width * ncn * depth; + const uint64_t base_strip_size = (uint64_t) base_scanline_size * height; + + // To avoid exception about pixel size, check it. + static const size_t CV_IO_MAX_IMAGE_PIXELS = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PIXELS", 1 << 30); + uint64_t pixels = (uint64_t) width * height; + if ( pixels > CV_IO_MAX_IMAGE_PIXELS ) + { + throw SkipTestException( cv::format("Test is skipped( pixels(%zu) > CV_IO_MAX_IMAGE_PIXELS(%zu) )", + (size_t)pixels, CV_IO_MAX_IMAGE_PIXELS) ); + } + + // If buffer_size >= 1GB * 95%, TIFFReadScanline() is used. + const uint64_t BUFFER_SIZE_LIMIT_FOR_READS_CANLINE = (uint64_t) 1024*1024*1024*95/100; + const bool doReadScanline = ( base_strip_size >= BUFFER_SIZE_LIMIT_FOR_READS_CANLINE ); + + // Update ncn and depth for destination Mat. + switch ( imread_mode ) + { + case IMREAD_UNCHANGED: + break; + case IMREAD_GRAYSCALE: + ncn = 1; + depth = 1; + break; + case IMREAD_GRAYSCALE | IMREAD_ANYDEPTH: + ncn = 1; + break; + case IMREAD_GRAYSCALE | IMREAD_ANYCOLOR: + ncn = (ncn == 1)?1:3; + depth = 1; + break; + case IMREAD_GRAYSCALE | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH: + ncn = (ncn == 1)?1:3; + break; + case IMREAD_COLOR: + ncn = 3; + depth = 1; + break; + case IMREAD_COLOR | IMREAD_ANYDEPTH: + ncn = 3; + break; + case IMREAD_COLOR | IMREAD_ANYCOLOR: + ncn = 3; + depth = 1; + break; + case IMREAD_COLOR | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR: + ncn = 3; + break; + default: + break; + } + + // Memory usage for Destination Mat + const uint64_t memory_usage_cvmat = (uint64_t) width * ncn * depth * height; + + // Memory usage for Work memory in libtiff. + uint64_t memory_usage_tiff = 0; + if ( ( depth == 1 ) && ( !doReadScanline ) ) + { + // TIFFReadRGBA*() request to allocate RGBA(32bit) buffer. + memory_usage_tiff = (uint64_t) + width * + 4 * // ncn = RGBA + 1 * // dst_bpp = 8 bpp + height; + } + else + { + // TIFFReadEncodedStrip() or TIFFReadScanline() request to allocate strip memory. + memory_usage_tiff = base_strip_size; + } + + // Memory usage for Work memory in imgcodec/grfmt_tiff.cpp + const uint64_t memory_usage_work = + ( doReadScanline ) ? base_scanline_size // for TIFFReadScanline() + : base_strip_size; // for TIFFReadRGBA*() or TIFFReadEncodedStrip() + + // Total memory usage. + const uint64_t memory_usage_total = + memory_usage_cvmat + // Destination Mat + memory_usage_tiff + // Work memory in libtiff + memory_usage_work; // Work memory in imgcodecs + + // Output memory usage log. + CV_LOG_DEBUG(NULL, cv::format("OpenCV TIFF-test: memory usage info : mat(%zu), libtiff(%zu), work(%zu) -> total(%zu)", + (size_t)memory_usage_cvmat, (size_t)memory_usage_tiff, (size_t)memory_usage_work, (size_t)memory_usage_total) ); + + // Add test tags. + if ( memory_usage_total >= (uint64_t) 6144 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_14GB, CV_TEST_TAG_VERYLONG ); + } + else if ( memory_usage_total >= (uint64_t) 2048 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_6GB, CV_TEST_TAG_VERYLONG ); + } + else if ( memory_usage_total >= (uint64_t) 1024 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_LONG ); + } + else if ( memory_usage_total >= (uint64_t) 512 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_1GB ); + } + else if ( memory_usage_total >= (uint64_t) 200 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_512MB ); + } + else + { + // do nothing. + } + } + + // TEST Main + + cv::Mat img; + ASSERT_NO_THROW( img = cv::imread(filename, imread_mode) ); + ASSERT_FALSE(img.empty()); + + /** + * Test marker pixels at each corners. + * + * 0xAn,0x00 ... 0x00, 0xBn + * 0x00,0x00 ... 0x00, 0x00 + * : : : : + * 0x00,0x00 ... 0x00, 0x00 + * 0xCn,0x00 .., 0x00, 0xDn + * + */ + +#define MAKE_FLAG(from_type, to_type) (((uint64_t)from_type << 32 ) | to_type ) + + switch ( MAKE_FLAG(mat_type, img.type() ) ) + { + // GRAY TO GRAY + case MAKE_FLAG(CV_8UC1, CV_8UC1): + case MAKE_FLAG(CV_16UC1, CV_8UC1): + EXPECT_EQ( 0xA0, img.at(0, 0) ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1) ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1) ); + break; + + // RGB/RGBA TO BGR + case MAKE_FLAG(CV_8UC3, CV_8UC3): + case MAKE_FLAG(CV_8UC4, CV_8UC3): + case MAKE_FLAG(CV_16UC3, CV_8UC3): + case MAKE_FLAG(CV_16UC4, CV_8UC3): + EXPECT_EQ( 0xA2, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA1, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA0, img.at(0, 0) [2] ); + EXPECT_EQ( 0xB2, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB1, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xC2, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC1, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xD2, img.at(img.rows-1, img.cols-1)[0] ); + EXPECT_EQ( 0xD1, img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1)[2] ); + break; + + // RGBA TO BGRA + case MAKE_FLAG(CV_8UC4, CV_8UC4): + case MAKE_FLAG(CV_16UC4, CV_8UC4): + EXPECT_EQ( 0xA2, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA1, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA0, img.at(0, 0) [2] ); + EXPECT_EQ( 0xA3, img.at(0, 0) [3] ); + EXPECT_EQ( 0xB2, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB1, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xB3, img.at(0, img.cols-1)[3] ); + EXPECT_EQ( 0xC2, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC1, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xC3, img.at(img.rows-1, 0) [3] ); + EXPECT_EQ( 0xD2, img.at(img.rows-1, img.cols-1)[0] ); + EXPECT_EQ( 0xD1, img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1)[2] ); + EXPECT_EQ( 0xD3, img.at(img.rows-1, img.cols-1)[3] ); + break; + + // RGB/RGBA to GRAY + case MAKE_FLAG(CV_8UC3, CV_8UC1): + case MAKE_FLAG(CV_8UC4, CV_8UC1): + case MAKE_FLAG(CV_16UC3, CV_8UC1): + case MAKE_FLAG(CV_16UC4, CV_8UC1): + EXPECT_LE( 0xA0, img.at(0, 0) ); + EXPECT_GE( 0xA2, img.at(0, 0) ); + EXPECT_LE( 0xB0, img.at(0, img.cols-1) ); + EXPECT_GE( 0xB2, img.at(0, img.cols-1) ); + EXPECT_LE( 0xC0, img.at(img.rows-1, 0) ); + EXPECT_GE( 0xC2, img.at(img.rows-1, 0) ); + EXPECT_LE( 0xD0, img.at(img.rows-1, img.cols-1) ); + EXPECT_GE( 0xD2, img.at(img.rows-1, img.cols-1) ); + break; + + // GRAY to BGR + case MAKE_FLAG(CV_8UC1, CV_8UC3): + case MAKE_FLAG(CV_16UC1, CV_8UC3): + EXPECT_EQ( 0xA0, img.at(0, 0) [0] ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1)[0] ); + // R==G==B + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [1] ); + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [2] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[1] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[2] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[2] ); + break; + + // GRAY TO GRAY + case MAKE_FLAG(CV_16UC1, CV_16UC1): + EXPECT_EQ( 0xA090, img.at(0, 0) ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1) ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) ); + EXPECT_EQ( 0xD060, img.at(img.rows-1, img.cols-1) ); + break; + + // RGB/RGBA TO BGR + case MAKE_FLAG(CV_16UC3, CV_16UC3): + case MAKE_FLAG(CV_16UC4, CV_16UC3): + EXPECT_EQ( 0xA292, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA191, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA090, img.at(0, 0) [2] ); + EXPECT_EQ( 0xB282, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB181, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xC272, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC171, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xD262, img.at(img.rows-1, img.cols-1)[0] ); + EXPECT_EQ( 0xD161, img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( 0xD060, img.at(img.rows-1, img.cols-1)[2] ); + break; + + // RGBA TO RGBA + case MAKE_FLAG(CV_16UC4, CV_16UC4): + EXPECT_EQ( 0xA292, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA191, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA090, img.at(0, 0) [2] ); + EXPECT_EQ( 0xA393, img.at(0, 0) [3] ); + EXPECT_EQ( 0xB282, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB181, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xB383, img.at(0, img.cols-1)[3] ); + EXPECT_EQ( 0xC272, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC171, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xC373, img.at(img.rows-1, 0) [3] ); + EXPECT_EQ( 0xD262, img.at(img.rows-1,img.cols-1) [0] ); + EXPECT_EQ( 0xD161, img.at(img.rows-1,img.cols-1) [1] ); + EXPECT_EQ( 0xD060, img.at(img.rows-1,img.cols-1) [2] ); + EXPECT_EQ( 0xD363, img.at(img.rows-1,img.cols-1) [3] ); + break; + + // RGB/RGBA to GRAY + case MAKE_FLAG(CV_16UC3, CV_16UC1): + case MAKE_FLAG(CV_16UC4, CV_16UC1): + EXPECT_LE( 0xA090, img.at(0, 0) ); + EXPECT_GE( 0xA292, img.at(0, 0) ); + EXPECT_LE( 0xB080, img.at(0, img.cols-1) ); + EXPECT_GE( 0xB282, img.at(0, img.cols-1) ); + EXPECT_LE( 0xC070, img.at(img.rows-1, 0) ); + EXPECT_GE( 0xC272, img.at(img.rows-1, 0) ); + EXPECT_LE( 0xD060, img.at(img.rows-1, img.cols-1) ); + EXPECT_GE( 0xD262, img.at(img.rows-1, img.cols-1) ); + break; + + // GRAY to RGB + case MAKE_FLAG(CV_16UC1, CV_16UC3): + EXPECT_EQ( 0xA090, img.at(0, 0) [0] ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xD060, img.at(img.rows-1, img.cols-1)[0] ); + // R==G==B + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [1] ); + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [2] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[1] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[2] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[2] ); + break; + + // No supported. + // (1) 8bit to 16bit + case MAKE_FLAG(CV_8UC1, CV_16UC1): + case MAKE_FLAG(CV_8UC1, CV_16UC3): + case MAKE_FLAG(CV_8UC1, CV_16UC4): + case MAKE_FLAG(CV_8UC3, CV_16UC1): + case MAKE_FLAG(CV_8UC3, CV_16UC3): + case MAKE_FLAG(CV_8UC3, CV_16UC4): + case MAKE_FLAG(CV_8UC4, CV_16UC1): + case MAKE_FLAG(CV_8UC4, CV_16UC3): + case MAKE_FLAG(CV_8UC4, CV_16UC4): + // (2) GRAY/RGB TO RGBA + case MAKE_FLAG(CV_8UC1, CV_8UC4): + case MAKE_FLAG(CV_8UC3, CV_8UC4): + case MAKE_FLAG(CV_16UC1, CV_8UC4): + case MAKE_FLAG(CV_16UC3, CV_8UC4): + case MAKE_FLAG(CV_16UC1, CV_16UC4): + case MAKE_FLAG(CV_16UC3, CV_16UC4): + default: + FAIL() << cv::format("Unknown test pattern: from = %d ( %d, %d) to = %d ( %d, %d )", + mat_type, (int)CV_MAT_CN(mat_type ), ( CV_MAT_DEPTH(mat_type )==CV_16U)?16:8, + img.type(), (int)CV_MAT_CN(img.type() ), ( CV_MAT_DEPTH(img.type() )==CV_16U)?16:8); + break; + } + +#undef MAKE_FLAG +} + +// Basic Test +const Bufsize_and_Type Imgcodecs_Tiff_decode_Huge_list_basic[] = +{ + make_tuple,ImreadMixModes>( 1073479680ull, make_tuple("CV_8UC1", CV_8UC1), IMREAD_MIX_COLOR ), + make_tuple,ImreadMixModes>( 2147483648ull, make_tuple("CV_16UC4", CV_16UC4), IMREAD_MIX_COLOR ), +}; + +INSTANTIATE_TEST_CASE_P(Imgcodecs_Tiff, Imgcodecs_Tiff_decode_Huge, + testing::ValuesIn( Imgcodecs_Tiff_decode_Huge_list_basic ) +); + +// Full Test + +/** + * Test lists for combination of IMREAD_*. + */ +const ImreadMixModes all_modes_Huge_Full[] = +{ + IMREAD_MIX_UNCHANGED, + IMREAD_MIX_GRAYSCALE, + IMREAD_MIX_GRAYSCALE_ANYDEPTH, + IMREAD_MIX_GRAYSCALE_ANYCOLOR, + IMREAD_MIX_GRAYSCALE_ANYDEPTH_ANYCOLOR, + IMREAD_MIX_COLOR, + IMREAD_MIX_COLOR_ANYDEPTH, + IMREAD_MIX_COLOR_ANYCOLOR, + IMREAD_MIX_COLOR_ANYDEPTH_ANYCOLOR, +}; + +const uint64_t huge_buffer_sizes_decode_Full[] = +{ + 1048576ull, // 1 * 1024 * 1024 + 1073479680ull, // 1024 * 1024 * 1024 - 32768 * 4 * 2 + 1073741824ull, // 1024 * 1024 * 1024 + 2147483648ull, // 2048 * 1024 * 1024 +}; + +const tuple mat_types_Full[] = +{ + make_tuple("CV_8UC1", CV_8UC1), // 8bit GRAY + make_tuple("CV_8UC3", CV_8UC3), // 24bit RGB + make_tuple("CV_8UC4", CV_8UC4), // 32bit RGBA + make_tuple("CV_16UC1", CV_16UC1), // 16bit GRAY + make_tuple("CV_16UC3", CV_16UC3), // 48bit RGB + make_tuple("CV_16UC4", CV_16UC4), // 64bit RGBA +}; + +INSTANTIATE_TEST_CASE_P(DISABLED_Imgcodecs_Tiff_Full, Imgcodecs_Tiff_decode_Huge, + testing::Combine( + testing::ValuesIn(huge_buffer_sizes_decode_Full), + testing::ValuesIn(mat_types_Full), + testing::ValuesIn(all_modes_Huge_Full) + ) +); + + +//================================================================================================== + TEST(Imgcodecs_Tiff, write_read_16bit_big_little_endian) { // see issue #2601 "16-bit Grayscale TIFF Load Failures Due to Buffer Underflow and Endianness" @@ -359,6 +787,16 @@ TEST(Imgcodecs_Tiff, read_palette_color_image) ASSERT_EQ(CV_8UC3, img.type()); } +TEST(Imgcodecs_Tiff, read_4_bit_palette_color_image) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filenameInput = root + "readwrite/4-bit_palette_color.tif"; + + const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED); + ASSERT_FALSE(img.empty()); + ASSERT_EQ(CV_8UC3, img.type()); +} + TEST(Imgcodecs_Tiff, readWrite_predictor) { /* see issue #21871 @@ -432,6 +870,43 @@ TEST_P(Imgcodecs_Tiff_Modes, decode_multipage) } } +TEST_P(Imgcodecs_Tiff_Modes, decode_multipage_use_memory_buffer) +{ + const int mode = GetParam(); + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/multipage.tif"; + const string page_files[] = { + "readwrite/multipage_p1.tif", + "readwrite/multipage_p2.tif", + "readwrite/multipage_p3.tif", + "readwrite/multipage_p4.tif", + "readwrite/multipage_p5.tif", + "readwrite/multipage_p6.tif" + }; + const size_t page_count = sizeof(page_files) / sizeof(page_files[0]); + vector pages; + + FILE* fp = fopen(filename.c_str(), "rb"); + ASSERT_TRUE(fp != NULL); + fseek(fp, 0, SEEK_END); + long pos = ftell(fp); + + std::vector buf; + buf.resize((size_t)pos); + fseek(fp, 0, SEEK_SET); + buf.resize(fread(&buf[0], 1, buf.size(), fp)); + fclose(fp); + + bool res = imdecodemulti(buf, mode, pages); + ASSERT_TRUE(res == true); + ASSERT_EQ(page_count, pages.size()); + for (size_t i = 0; i < page_count; i++) + { + const Mat page = imread(root + page_files[i], mode); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), page, pages[i]); + } +} + const int all_modes[] = { IMREAD_UNCHANGED, diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 89ef00be42..15384028de 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1625,6 +1625,23 @@ CV_EXPORTS_W void blur( InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT ); +/** @brief Blurs an image using the stackBlur. + +The function applies and stackBlur to an image. +stackBlur can generate similar results as Gaussian blur, and the time consumption does not increase with the increase of kernel size. +It creates a kind of moving stack of colors whilst scanning through the image. Thereby it just has to add one new block of color to the right side +of the stack and remove the leftmost color. The remaining colors on the topmost layer of the stack are either added on or reduced by one, +depending on if they are on the right or on the left side of the stack. The only supported borderType is BORDER_REPLICATE. +Original paper was proposed by Mario Klingemann, which can be found http://underdestruction.com/2004/02/25/stackblur-2004. + +@param src input image. The number of channels can be arbitrary, but the depth should be one of +CV_8U, CV_16U, CV_16S or CV_32F. +@param dst output image of the same size and type as src. +@param ksize stack-blurring kernel size. The ksize.width and ksize.height can differ but they both must be +positive and odd. +*/ +CV_EXPORTS_W void stackBlur(InputArray src, OutputArray dst, Size ksize); + /** @brief Convolves an image with the kernel. The function applies an arbitrary linear filter to an image. In-place operation is supported. When @@ -2119,23 +2136,24 @@ transform. @param image 8-bit, single-channel binary source image. The image may be modified by the function. @param lines Output vector of lines. Each line is represented by a 2 or 3 element vector -\f$(\rho, \theta)\f$ or \f$(\rho, \theta, \textrm{votes})\f$ . \f$\rho\f$ is the distance from the coordinate origin \f$(0,0)\f$ (top-left corner of -the image). \f$\theta\f$ is the line rotation angle in radians ( -\f$0 \sim \textrm{vertical line}, \pi/2 \sim \textrm{horizontal line}\f$ ). +\f$(\rho, \theta)\f$ or \f$(\rho, \theta, \textrm{votes})\f$, where \f$\rho\f$ is the distance from +the coordinate origin \f$(0,0)\f$ (top-left corner of the image), \f$\theta\f$ is the line rotation +angle in radians ( \f$0 \sim \textrm{vertical line}, \pi/2 \sim \textrm{horizontal line}\f$ ), and \f$\textrm{votes}\f$ is the value of accumulator. @param rho Distance resolution of the accumulator in pixels. @param theta Angle resolution of the accumulator in radians. -@param threshold Accumulator threshold parameter. Only those lines are returned that get enough +@param threshold %Accumulator threshold parameter. Only those lines are returned that get enough votes ( \f$>\texttt{threshold}\f$ ). -@param srn For the multi-scale Hough transform, it is a divisor for the distance resolution rho . +@param srn For the multi-scale Hough transform, it is a divisor for the distance resolution rho. The coarse accumulator distance resolution is rho and the accurate accumulator resolution is -rho/srn . If both srn=0 and stn=0 , the classical Hough transform is used. Otherwise, both these +rho/srn. If both srn=0 and stn=0, the classical Hough transform is used. Otherwise, both these parameters should be positive. @param stn For the multi-scale Hough transform, it is a divisor for the distance resolution theta. @param min_theta For standard and multi-scale Hough transform, minimum angle to check for lines. Must fall between 0 and max_theta. -@param max_theta For standard and multi-scale Hough transform, maximum angle to check for lines. -Must fall between min_theta and CV_PI. +@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. */ CV_EXPORTS_W void HoughLines( InputArray image, OutputArray lines, double rho, double theta, int threshold, @@ -2163,7 +2181,7 @@ And this is the output of the above program in case of the probabilistic Hough t line segment. @param rho Distance resolution of the accumulator in pixels. @param theta Angle resolution of the accumulator in radians. -@param threshold Accumulator threshold parameter. Only those lines are returned that get enough +@param threshold %Accumulator threshold parameter. Only those lines are returned that get enough votes ( \f$>\texttt{threshold}\f$ ). @param minLineLength Minimum line length. Line segments shorter than that are rejected. @param maxLineGap Maximum allowed gap between points on the same line to link them. @@ -2182,13 +2200,14 @@ The function finds lines in a set of points using a modification of the Hough tr @param lines Output vector of found lines. Each vector is encoded as a vector \f$(votes, rho, theta)\f$. The larger the value of 'votes', the higher the reliability of the Hough line. @param lines_max Max count of Hough lines. -@param threshold Accumulator threshold parameter. Only those lines are returned that get enough +@param threshold %Accumulator threshold parameter. Only those lines are returned that get enough votes ( \f$>\texttt{threshold}\f$ ). @param min_rho Minimum value for \f$\rho\f$ for the accumulator (Note: \f$\rho\f$ can be negative. The absolute value \f$|\rho|\f$ is the distance of a line to the origin.). @param max_rho Maximum value for \f$\rho\f$ for the accumulator. @param rho_step Distance resolution of the accumulator. @param min_theta Minimum angle value of the accumulator in radians. -@param max_theta Maximum angle value of the accumulator in radians. +@param max_theta Upper bound for the angle value of the accumulator in radians. The actual maximum +angle may be slightly less than max_theta, depending on the parameters min_theta and theta_step. @param theta_step Angle resolution of the accumulator in radians. */ CV_EXPORTS_W void HoughLinesPointSet( InputArray point, OutputArray lines, int lines_max, int threshold, diff --git a/modules/imgproc/perf/perf_blur.cpp b/modules/imgproc/perf/perf_blur.cpp index e4092ccb16..d1f5a6b1ca 100644 --- a/modules/imgproc/perf/perf_blur.cpp +++ b/modules/imgproc/perf/perf_blur.cpp @@ -253,4 +253,52 @@ PERF_TEST_P(Size_MatType, BlendLinear, SANITY_CHECK_NOTHING(); } +///////////// Stackblur //////////////////////// +PERF_TEST_P(Size_MatType, stackblur3x3, + testing::Combine( + testing::Values(sz720p, sz1080p, sz2160p), + testing::Values(CV_8UC1, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1) + ) +) +{ + Size size = get<0>(GetParam()); + int type = get<1>(GetParam()); + double eps = 1e-3; + + eps = CV_MAT_DEPTH(type) <= CV_32S ? 1 : eps; + + Mat src(size, type); + Mat dst(size, type); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE() stackBlur(src, dst, Size(3,3)); + + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P(Size_MatType, stackblur101x101, + testing::Combine( + testing::Values(sz720p, sz1080p, sz2160p), + testing::Values(CV_8UC1, CV_8UC4, CV_16UC1, CV_16SC1, CV_32FC1) + ) +) +{ + Size size = get<0>(GetParam()); + int type = get<1>(GetParam()); + double eps = 1e-3; + + eps = CV_MAT_DEPTH(type) <= CV_32S ? 1 : eps; + + Mat src(size, type); + Mat dst(size, type); + + declare.in(src, WARMUP_RNG).out(dst); + + TEST_CYCLE() stackBlur(src, dst, Size(101,101)); + + SANITY_CHECK_NOTHING(); +} + + } // namespace diff --git a/modules/imgproc/src/color.hpp b/modules/imgproc/src/color.hpp index 3f3b634790..abbd65ec06 100644 --- a/modules/imgproc/src/color.hpp +++ b/modules/imgproc/src/color.hpp @@ -104,6 +104,10 @@ inline int dstChannels(int code) return 4; case COLOR_BGRA2BGR: case COLOR_RGBA2BGR: case COLOR_RGB2BGR: + case COLOR_HSV2RGB: case COLOR_HSV2BGR: case COLOR_RGB2HSV: case COLOR_BGR2HSV: + case COLOR_HLS2RGB: case COLOR_HLS2BGR: case COLOR_RGB2HLS: case COLOR_BGR2HLS: + case COLOR_HSV2RGB_FULL: case COLOR_HSV2BGR_FULL: case COLOR_RGB2HSV_FULL: case COLOR_BGR2HSV_FULL: + case COLOR_HLS2RGB_FULL: case COLOR_HLS2BGR_FULL: case COLOR_RGB2HLS_FULL: case COLOR_BGR2HLS_FULL: case COLOR_YUV2RGB: case COLOR_YUV2BGR: case COLOR_RGB2YUV: case COLOR_BGR2YUV: case COLOR_BGR5652BGR: case COLOR_BGR5552BGR: case COLOR_BGR5652RGB: case COLOR_BGR5552RGB: case COLOR_GRAY2BGR: @@ -111,6 +115,12 @@ inline int dstChannels(int code) case COLOR_YUV2BGR_YV12: case COLOR_YUV2RGB_YV12: case COLOR_YUV2BGR_IYUV: case COLOR_YUV2RGB_IYUV: case COLOR_YUV2RGB_UYVY: case COLOR_YUV2BGR_UYVY: case COLOR_YUV2RGB_YVYU: case COLOR_YUV2BGR_YVYU: case COLOR_YUV2RGB_YUY2: case COLOR_YUV2BGR_YUY2: + case COLOR_Lab2BGR: case COLOR_Lab2RGB: case COLOR_BGR2Lab: case COLOR_RGB2Lab: + case COLOR_Lab2LBGR: case COLOR_Lab2LRGB: + case COLOR_Luv2BGR: case COLOR_Luv2RGB: case COLOR_BGR2Luv: case COLOR_RGB2Luv: + case COLOR_Luv2LBGR: case COLOR_Luv2LRGB: + case COLOR_YCrCb2BGR: case COLOR_YCrCb2RGB: case COLOR_BGR2YCrCb: case COLOR_RGB2YCrCb: + case COLOR_XYZ2BGR: case COLOR_XYZ2RGB: case COLOR_BGR2XYZ: case COLOR_RGB2XYZ: return 3; diff --git a/modules/imgproc/src/color_hsv.simd.hpp b/modules/imgproc/src/color_hsv.simd.hpp index 9837af4acb..218103c4fe 100644 --- a/modules/imgproc/src/color_hsv.simd.hpp +++ b/modules/imgproc/src/color_hsv.simd.hpp @@ -5,6 +5,19 @@ #include "precomp.hpp" #include "opencv2/core/hal/intrin.hpp" +#if CV_SIMD_SCALABLE +/* FIX IT: +// std::swap(a, b) is not available for RVV vector types, +// and CV_SWAP needs another "t" as input, +// For compatibility, we swap RVV vector manually by using this macro. + +// If others scalable types (e.g. type in ARM SVE) can use std::swap, +// then replace CV_SIMD_SCALABLE with CV_RVV. +// If std::swap is available for RVV vector types in future, remove this macro. +*/ +#define swap(a, b) {auto t = a; a = b; b = t;} +#endif + namespace cv { namespace hal { CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN @@ -85,8 +98,8 @@ struct RGB2HSV_b int i = 0; -#if CV_SIMD - const int vsize = v_uint8::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + const int vsize = VTraits::vlanes(); for ( ; i <= n - vsize; i += vsize, src += scn*vsize, dst += 3*vsize) { @@ -110,108 +123,109 @@ struct RGB2HSV_b vmin = v_min(b, v_min(g, r)); v_uint8 diff, vr, vg; - diff = v - vmin; + diff = v_sub(v, vmin); v_uint8 v255 = vx_setall_u8(0xff), vz = vx_setzero_u8(); - vr = v_select(v == r, v255, vz); - vg = v_select(v == g, v255, vz); + vr = v_select(v_eq(v, r), v255, vz); + vg = v_select(v_eq(v, g), v255, vz); // sdiv = sdiv_table[v] - v_int32 sdiv[4]; - v_uint16 vd[2]; - v_expand(v, vd[0], vd[1]); - v_int32 vq[4]; - v_expand(v_reinterpret_as_s16(vd[0]), vq[0], vq[1]); - v_expand(v_reinterpret_as_s16(vd[1]), vq[2], vq[3]); + v_int32 sdiv0, sdiv1, sdiv2, sdiv3;; + v_uint16 vd0, vd1, vd2; + v_expand(v, vd0, vd1); + v_int32 vq0, vq1, vq2, vq3; + v_expand(v_reinterpret_as_s16(vd0), vq0, vq1); + v_expand(v_reinterpret_as_s16(vd1), vq2, vq3); { - int32_t CV_DECL_ALIGNED(CV_SIMD_WIDTH) storevq[vsize]; - for (int k = 0; k < 4; k++) - { - v_store_aligned(storevq + k*vsize/4, vq[k]); - } + int32_t CV_DECL_ALIGNED(CV_SIMD_WIDTH) storevq[VTraits::max_nlanes]; + v_store_aligned(storevq, vq0); + v_store_aligned(storevq + vsize/4, vq1); + v_store_aligned(storevq + 2*vsize/4, vq2); + v_store_aligned(storevq + 3*vsize/4, vq3); - for(int k = 0; k < 4; k++) - { - sdiv[k] = vx_lut(sdiv_table, storevq + k*vsize/4); - } + sdiv0 = vx_lut(sdiv_table, storevq); + sdiv1 = vx_lut(sdiv_table, storevq + vsize/4); + sdiv2 = vx_lut(sdiv_table, storevq + 2*vsize/4); + sdiv3 = vx_lut(sdiv_table, storevq + 3*vsize/4); } // hdiv = hdiv_table[diff] - v_int32 hdiv[4]; - v_uint16 diffd[2]; - v_expand(diff, diffd[0], diffd[1]); - v_int32 diffq[4]; - v_expand(v_reinterpret_as_s16(diffd[0]), diffq[0], diffq[1]); - v_expand(v_reinterpret_as_s16(diffd[1]), diffq[2], diffq[3]); + v_int32 hdiv0, hdiv1, hdiv2, hdiv3; + v_uint16 diffd0, diffd1, diffd2; + v_expand(diff, diffd0, diffd1); + v_int32 diffq0, diffq1, diffq2, diffq3; + v_expand(v_reinterpret_as_s16(diffd0), diffq0, diffq1); + v_expand(v_reinterpret_as_s16(diffd1), diffq2, diffq3); { - int32_t CV_DECL_ALIGNED(CV_SIMD_WIDTH) storediffq[vsize]; - for (int k = 0; k < 4; k++) - { - v_store_aligned(storediffq + k*vsize/4, diffq[k]); - } - - for (int k = 0; k < 4; k++) - { - hdiv[k] = vx_lut((int32_t*)hdiv_table, storediffq + k*vsize/4); - } + int32_t CV_DECL_ALIGNED(CV_SIMD_WIDTH) storediffq[VTraits::max_nlanes]; + v_store_aligned(storediffq, diffq0); + v_store_aligned(storediffq + vsize/4, diffq1); + v_store_aligned(storediffq + 2*vsize/4, diffq2); + v_store_aligned(storediffq + 3*vsize/4, diffq3); + hdiv0 = vx_lut((int32_t*)hdiv_table, storediffq + 0*vsize/4); + hdiv1 = vx_lut((int32_t*)hdiv_table, storediffq + 1*vsize/4); + hdiv2 = vx_lut((int32_t*)hdiv_table, storediffq + 2*vsize/4); + hdiv3 = vx_lut((int32_t*)hdiv_table, storediffq + 3*vsize/4); } // s = (diff * sdiv + (1 << (hsv_shift-1))) >> hsv_shift; - v_int32 sq[4]; + v_int32 sq0, sq1, sq2, sq3; v_int32 vdescale = vx_setall_s32(1 << (hsv_shift-1)); - for (int k = 0; k < 4; k++) - { - sq[k] = (diffq[k]*sdiv[k] + vdescale) >> hsv_shift; - } - v_int16 sd[2]; - sd[0] = v_pack(sq[0], sq[1]); - sd[1] = v_pack(sq[2], sq[3]); - s = v_pack_u(sd[0], sd[1]); + sq0 = v_shr(v_add(v_mul(diffq0, sdiv0), vdescale)); + sq1 = v_shr(v_add(v_mul(diffq1, sdiv1), vdescale)); + sq2 = v_shr(v_add(v_mul(diffq2, sdiv2), vdescale)); + sq3 = v_shr(v_add(v_mul(diffq3, sdiv3), vdescale)); + v_int16 sd0, sd1; + sd0 = v_pack(sq0, sq1); + sd1 = v_pack(sq2, sq3); + s = v_pack_u(sd0, sd1); // expand all to 16 bits - v_uint16 bdu[2], gdu[2], rdu[2]; - v_expand(b, bdu[0], bdu[1]); - v_expand(g, gdu[0], gdu[1]); - v_expand(r, rdu[0], rdu[1]); - v_int16 bd[2], gd[2], rd[2]; - bd[0] = v_reinterpret_as_s16(bdu[0]); - bd[1] = v_reinterpret_as_s16(bdu[1]); - gd[0] = v_reinterpret_as_s16(gdu[0]); - gd[1] = v_reinterpret_as_s16(gdu[1]); - rd[0] = v_reinterpret_as_s16(rdu[0]); - rd[1] = v_reinterpret_as_s16(rdu[1]); + v_uint16 bdu0, bdu1, gdu0, gdu1, rdu0, rdu1; + v_expand(b, bdu0, bdu1); + v_expand(g, gdu0, gdu1); + v_expand(r, rdu0, rdu1); + v_int16 bd0, bd1, gd0, gd1, rd0, rd1; + bd0 = v_reinterpret_as_s16(bdu0); + bd1 = v_reinterpret_as_s16(bdu1); + gd0 = v_reinterpret_as_s16(gdu0); + gd1 = v_reinterpret_as_s16(gdu1); + rd0 = v_reinterpret_as_s16(rdu0); + rd1 = v_reinterpret_as_s16(rdu1); - v_int16 vrd[2], vgd[2]; - v_expand(v_reinterpret_as_s8(vr), vrd[0], vrd[1]); - v_expand(v_reinterpret_as_s8(vg), vgd[0], vgd[1]); - v_int16 diffsd[2]; - diffsd[0] = v_reinterpret_as_s16(diffd[0]); - diffsd[1] = v_reinterpret_as_s16(diffd[1]); + v_int16 vrd0, vrd1, vgd0, vgd1; + v_expand(v_reinterpret_as_s8(vr), vrd0, vrd1); + v_expand(v_reinterpret_as_s8(vg), vgd0, vgd1); + v_int16 diffsd0, diffsd1; + diffsd0 = v_reinterpret_as_s16(diffd0); + diffsd1 = v_reinterpret_as_s16(diffd1); - v_int16 hd[2]; + v_int16 hd0, hd1; // h before division - for (int k = 0; k < 2; k++) - { - v_int16 gb = gd[k] - bd[k]; - v_int16 br = bd[k] - rd[k] + (diffsd[k] << 1); - v_int16 rg = rd[k] - gd[k] + (diffsd[k] << 2); - hd[k] = (vrd[k] & gb) + ((~vrd[k]) & ((vgd[k] & br) + ((~vgd[k]) & rg))); - } + v_int16 gb = v_sub(gd0 ,bd0); + v_int16 br = v_add(v_sub(bd0 ,rd0), v_shl<1>(diffsd0)); + v_int16 rg = v_add(v_sub(rd0 ,gd0), v_shl<2>(diffsd0)); + hd0 = v_add(v_and(vrd0, gb), v_and(v_not(vrd0), v_add(v_and(vgd0, br), v_and(v_not(vgd0), rg)))); + gb = v_sub(gd1, bd1); + br = v_add(v_sub(bd1, rd1), v_shl<1>(diffsd1)); + rg = v_add(v_sub(rd1, gd1), v_shl<2>(diffsd1)); + hd1 = v_add(v_and(vrd1, gb), v_and(v_not(vrd1), v_add(v_and(vgd1, br), v_and(v_not(vgd1), rg)))); // h div and fix - v_int32 hq[4]; - v_expand(hd[0], hq[0], hq[1]); - v_expand(hd[1], hq[2], hq[3]); - for(int k = 0; k < 4; k++) - { - hq[k] = (hq[k]*hdiv[k] + vdescale) >> hsv_shift; - } - hd[0] = v_pack(hq[0], hq[1]); - hd[1] = v_pack(hq[2], hq[3]); + v_int32 hq0, hq1, hq2, hq3; + v_expand(hd0, hq0, hq1); + v_expand(hd1, hq2, hq3); + hq0 = v_shr(v_add(v_mul(hq0, hdiv0), vdescale)); + hq1 = v_shr(v_add(v_mul(hq1, hdiv1), vdescale)); + hq2 = v_shr(v_add(v_mul(hq2, hdiv2), vdescale)); + hq3 = v_shr(v_add(v_mul(hq3, hdiv3), vdescale)); + + hd0 = v_pack(hq0, hq1); + hd1 = v_pack(hq2, hq3); v_int16 vhr = vx_setall_s16((short)hr); v_int16 vzd = vx_setzero_s16(); - hd[0] += v_select(hd[0] < vzd, vhr, vzd); - hd[1] += v_select(hd[1] < vzd, vhr, vzd); - h = v_pack_u(hd[0], hd[1]); + hd0 = v_add(hd0 ,v_select(v_lt(hd0, vzd), vhr, vzd)); + hd1 = v_add(hd1 ,v_select(v_lt(hd1, vzd), vhr, vzd)); + h = v_pack_u(hd0, hd1); v_store_interleave(dst, h, s, v); } @@ -260,7 +274,7 @@ struct RGB2HSV_f : srccn(_srccn), blueIdx(_blueIdx), hrange(_hrange) { } - #if CV_SIMD + #if CV_SIMD || CV_SIMD_SCALABLE inline void process(const v_float32& v_r, const v_float32& v_g, const v_float32& v_b, v_float32& v_h, v_float32& v_s, v_float32& v_v, float hscale) const @@ -269,17 +283,18 @@ struct RGB2HSV_f v_float32 v_max_rgb = v_max(v_max(v_r, v_g), v_b); v_float32 v_eps = vx_setall_f32(FLT_EPSILON); - v_float32 v_diff = v_max_rgb - v_min_rgb; - v_s = v_diff / (v_abs(v_max_rgb) + v_eps); + v_float32 v_diff = v_sub(v_max_rgb, v_min_rgb); + v_s = v_div(v_diff, v_add(v_abs(v_max_rgb), v_eps)); - v_float32 v_r_eq_max = v_r == v_max_rgb; - v_float32 v_g_eq_max = v_g == v_max_rgb; - v_h = v_select(v_r_eq_max, v_g - v_b, - v_select(v_g_eq_max, v_b - v_r, v_r - v_g)); - v_float32 v_res = v_select(v_r_eq_max, (v_g < v_b) & vx_setall_f32(360.0f), - v_select(v_g_eq_max, vx_setall_f32(120.0f), vx_setall_f32(240.0f))); - v_float32 v_rev_diff = vx_setall_f32(60.0f) / (v_diff + v_eps); - v_h = v_muladd(v_h, v_rev_diff, v_res) * vx_setall_f32(hscale); + v_float32 v_r_eq_max = v_eq(v_r, v_max_rgb); + v_float32 v_g_eq_max = v_eq(v_g, v_max_rgb); + v_h = v_select(v_r_eq_max, v_sub(v_g, v_b), + v_select(v_g_eq_max, v_sub(v_b, v_r), v_sub(v_r, v_g))); + v_float32 v_res = v_select(v_r_eq_max, + v_select(v_lt(v_g, v_b), vx_setall_f32(360.0f), vx_setall_f32(0.0f)), + v_select(v_g_eq_max, vx_setall_f32(120.0f), vx_setall_f32(240.0f))); + v_float32 v_rev_diff = v_div(vx_setall_f32(60.0f), v_add(v_diff, v_eps)); + v_h = v_mul(v_muladd(v_h, v_rev_diff, v_res), vx_setall_f32(hscale)); v_v = v_max_rgb; } @@ -293,8 +308,8 @@ struct RGB2HSV_f float hscale = hrange*(1.f/360.f); n *= 3; -#if CV_SIMD - const int vsize = v_float32::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + const int vsize = VTraits::vlanes(); for ( ; i <= n - 3*vsize; i += 3*vsize, src += scn * vsize) { v_float32 r, g, b, a; @@ -353,7 +368,7 @@ struct RGB2HSV_f }; -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE inline void HSV2RGB_simd(const v_float32& h, const v_float32& s, const v_float32& v, v_float32& b, v_float32& g, v_float32& r, float hscale) { @@ -361,43 +376,43 @@ inline void HSV2RGB_simd(const v_float32& h, const v_float32& s, const v_float32 v_float32 v_s = s; v_float32 v_v = v; - v_h = v_h * vx_setall_f32(hscale); + v_h = v_mul(v_h, vx_setall_f32(hscale)); v_float32 v_pre_sector = v_cvt_f32(v_trunc(v_h)); - v_h = v_h - v_pre_sector; + v_h = v_sub(v_h, v_pre_sector); v_float32 v_tab0 = v_v; v_float32 v_one = vx_setall_f32(1.0f); - v_float32 v_tab1 = v_v * (v_one - v_s); - v_float32 v_tab2 = v_v * (v_one - (v_s * v_h)); - v_float32 v_tab3 = v_v * (v_one - (v_s * (v_one - v_h))); + v_float32 v_tab1 = v_mul(v_v, v_sub(v_one, v_s)); + v_float32 v_tab2 = v_mul(v_v, v_sub(v_one, v_mul(v_s, v_h))); + v_float32 v_tab3 = v_mul(v_v, v_sub(v_one, v_mul(v_s, v_sub(v_one, v_h)))); v_float32 v_one_sixth = vx_setall_f32(1.0f / 6.0f); - v_float32 v_sector = v_pre_sector * v_one_sixth; + v_float32 v_sector = v_mul(v_pre_sector, v_one_sixth); v_sector = v_cvt_f32(v_trunc(v_sector)); v_float32 v_six = vx_setall_f32(6.0f); - v_sector = v_pre_sector - (v_sector * v_six); + v_sector = v_sub(v_pre_sector, v_mul(v_sector, v_six)); v_float32 v_two = vx_setall_f32(2.0f); - v_h = v_tab1 & (v_sector < v_two); - v_h = v_h | (v_tab3 & (v_sector == v_two)); + v_h = v_select(v_lt(v_sector, v_two), v_tab1, vx_setall_f32(0.0f)); + v_h = v_select(v_eq(v_sector, v_two), v_tab3, v_h); v_float32 v_three = vx_setall_f32(3.0f); - v_h = v_h | (v_tab0 & (v_sector == v_three)); + v_h = v_select(v_eq(v_sector, v_three), v_tab0, v_h); v_float32 v_four = vx_setall_f32(4.0f); - v_h = v_h | (v_tab0 & (v_sector == v_four)); - v_h = v_h | (v_tab2 & (v_sector > v_four)); + v_h = v_select(v_eq(v_sector, v_four), v_tab0, v_h); + v_h = v_select(v_gt(v_sector, v_four), v_tab2, v_h); - v_s = v_tab3 & (v_sector < v_one); - v_s = v_s | (v_tab0 & (v_sector == v_one)); - v_s = v_s | (v_tab0 & (v_sector == v_two)); - v_s = v_s | (v_tab2 & (v_sector == v_three)); - v_s = v_s | (v_tab1 & (v_sector > v_three)); + v_s = v_select(v_lt(v_sector, v_one), v_tab3, v_s); + v_s = v_select(v_eq(v_sector, v_one), v_tab0, v_s); + v_s = v_select(v_eq(v_sector, v_two), v_tab0, v_s); + v_s = v_select(v_eq(v_sector, v_three), v_tab2, v_s); + v_s = v_select(v_gt(v_sector, v_three), v_tab1, v_s); - v_v = v_tab0 & (v_sector < v_one); - v_v = v_v | (v_tab2 & (v_sector == v_one)); - v_v = v_v | (v_tab1 & (v_sector == v_two)); - v_v = v_v | (v_tab1 & (v_sector == v_three)); - v_v = v_v | (v_tab3 & (v_sector == v_four)); - v_v = v_v | (v_tab0 & (v_sector > v_four)); + v_v = v_select(v_lt(v_sector, v_one), v_tab0, v_v); + v_v = v_select(v_eq(v_sector, v_one), v_tab2, v_v); + v_v = v_select(v_eq(v_sector, v_two), v_tab1, v_v); + v_v = v_select(v_eq(v_sector, v_three), v_tab1, v_v); + v_v = v_select(v_eq(v_sector, v_four), v_tab3, v_v); + v_v = v_select(v_gt(v_sector, v_four), v_tab0, v_v); b = v_h; g = v_s; @@ -457,8 +472,8 @@ struct HSV2RGB_f float hs = hscale; n *= 3; -#if CV_SIMD - const int vsize = v_float32::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + const int vsize = VTraits::vlanes(); v_float32 valpha = vx_setall_f32(alpha); for (; i <= n - vsize*3; i += vsize*3, dst += dcn * vsize) { @@ -514,60 +529,102 @@ struct HSV2RGB_b int j = 0, dcn = dstcn; uchar alpha = ColorChannel::max(); -#if CV_SIMD - const int vsize = v_float32::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + const int vsize = VTraits::vlanes(); for (j = 0; j <= (n - vsize*4) * 3; j += 3 * 4 * vsize, dst += dcn * 4 * vsize) { v_uint8 h_b, s_b, v_b; - v_uint16 h_w[2], s_w[2], v_w[2]; - v_uint32 h_u[4], s_u[4], v_u[4]; + v_uint16 h_w0, h_w1, s_w0, s_w1, v_w0, v_w1; + v_uint32 h_u0, h_u1, h_u2, h_u3, s_u0, s_u1, s_u2, s_u3, v_u0, v_u1, v_u2, v_u3; v_load_deinterleave(src + j, h_b, s_b, v_b); - v_expand(h_b, h_w[0], h_w[1]); - v_expand(s_b, s_w[0], s_w[1]); - v_expand(v_b, v_w[0], v_w[1]); - v_expand(h_w[0], h_u[0], h_u[1]); - v_expand(h_w[1], h_u[2], h_u[3]); - v_expand(s_w[0], s_u[0], s_u[1]); - v_expand(s_w[1], s_u[2], s_u[3]); - v_expand(v_w[0], v_u[0], v_u[1]); - v_expand(v_w[1], v_u[2], v_u[3]); + v_expand(h_b, h_w0, h_w1); + v_expand(s_b, s_w0, s_w1); + v_expand(v_b, v_w0, v_w1); + v_expand(h_w0, h_u0, h_u1); + v_expand(h_w1, h_u2, h_u3); + v_expand(s_w0, s_u0, s_u1); + v_expand(s_w1, s_u2, s_u3); + v_expand(v_w0, v_u0, v_u1); + v_expand(v_w1, v_u2, v_u3); - v_int32 b_i[4], g_i[4], r_i[4]; + v_int32 b_i0, b_i1, b_i2, b_i3, g_i0, g_i1, g_i2, g_i3, r_i0, r_i1, r_i2, r_i3; v_float32 v_coeff0 = vx_setall_f32(1.0f / 255.0f); v_float32 v_coeff1 = vx_setall_f32(255.0f); - for( int k = 0; k < 4; k++ ) - { - v_float32 h = v_cvt_f32(v_reinterpret_as_s32(h_u[k])); - v_float32 s = v_cvt_f32(v_reinterpret_as_s32(s_u[k])); - v_float32 v = v_cvt_f32(v_reinterpret_as_s32(v_u[k])); + v_float32 h = v_cvt_f32(v_reinterpret_as_s32(h_u0)); + v_float32 s = v_cvt_f32(v_reinterpret_as_s32(s_u0)); + v_float32 v = v_cvt_f32(v_reinterpret_as_s32(v_u0)); - s *= v_coeff0; - v *= v_coeff0; - v_float32 b, g, r; - HSV2RGB_simd(h, s, v, b, g, r, hscale); + s = v_mul(s, v_coeff0); + v = v_mul(v, v_coeff0); + v_float32 b, g, r; + HSV2RGB_simd(h, s, v, b, g, r, hscale); - b *= v_coeff1; - g *= v_coeff1; - r *= v_coeff1; - b_i[k] = v_trunc(b); - g_i[k] = v_trunc(g); - r_i[k] = v_trunc(r); - } + b = v_mul(b, v_coeff1); + g = v_mul(g, v_coeff1); + r = v_mul(r, v_coeff1); + b_i0 = v_trunc(b); + g_i0 = v_trunc(g); + r_i0 = v_trunc(r); - v_uint16 r_w[2], g_w[2], b_w[2]; + h = v_cvt_f32(v_reinterpret_as_s32(h_u1)); + s = v_cvt_f32(v_reinterpret_as_s32(s_u1)); + v = v_cvt_f32(v_reinterpret_as_s32(v_u1)); + + s = v_mul(s, v_coeff0); + v = v_mul(v, v_coeff0); + HSV2RGB_simd(h, s, v, b, g, r, hscale); + + b = v_mul(b, v_coeff1); + g = v_mul(g, v_coeff1); + r = v_mul(r, v_coeff1); + b_i1 = v_trunc(b); + g_i1 = v_trunc(g); + r_i1 = v_trunc(r); + + h = v_cvt_f32(v_reinterpret_as_s32(h_u2)); + s = v_cvt_f32(v_reinterpret_as_s32(s_u2)); + v = v_cvt_f32(v_reinterpret_as_s32(v_u2)); + + s = v_mul(s, v_coeff0); + v = v_mul(v, v_coeff0); + HSV2RGB_simd(h, s, v, b, g, r, hscale); + + b = v_mul(b, v_coeff1); + g = v_mul(g, v_coeff1); + r = v_mul(r, v_coeff1); + b_i2 = v_trunc(b); + g_i2 = v_trunc(g); + r_i2 = v_trunc(r); + + h = v_cvt_f32(v_reinterpret_as_s32(h_u3)); + s = v_cvt_f32(v_reinterpret_as_s32(s_u3)); + v = v_cvt_f32(v_reinterpret_as_s32(v_u3)); + + s = v_mul(s, v_coeff0); + v = v_mul(v, v_coeff0); + HSV2RGB_simd(h, s, v, b, g, r, hscale); + + b = v_mul(b, v_coeff1); + g = v_mul(g, v_coeff1); + r = v_mul(r, v_coeff1); + b_i3 = v_trunc(b); + g_i3 = v_trunc(g); + r_i3 = v_trunc(r); + + v_uint16 r_w0, r_w1, g_w0, g_w1, b_w0, b_w1; v_uint8 r_b, g_b, b_b; - r_w[0] = v_pack_u(r_i[0], r_i[1]); - r_w[1] = v_pack_u(r_i[2], r_i[3]); - r_b = v_pack(r_w[0], r_w[1]); - g_w[0] = v_pack_u(g_i[0], g_i[1]); - g_w[1] = v_pack_u(g_i[2], g_i[3]); - g_b = v_pack(g_w[0], g_w[1]); - b_w[0] = v_pack_u(b_i[0], b_i[1]); - b_w[1] = v_pack_u(b_i[2], b_i[3]); - b_b = v_pack(b_w[0], b_w[1]); + r_w0 = v_pack_u(r_i0, r_i1); + r_w1 = v_pack_u(r_i2, r_i3); + r_b = v_pack(r_w0, r_w1); + g_w0 = v_pack_u(g_i0, g_i1); + g_w1 = v_pack_u(g_i2, g_i3); + g_b = v_pack(g_w0, g_w1); + b_w0 = v_pack_u(b_i0, b_i1); + b_w1 = v_pack_u(b_i2, b_i3); + b_b = v_pack(b_w0, b_w1); if( dcn == 3 ) { @@ -621,7 +678,7 @@ struct RGB2HLS_f { } -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE inline void process(const v_float32& r, const v_float32& g, const v_float32& b, const v_float32& vhscale, v_float32& h, v_float32& l, v_float32& s) const @@ -629,28 +686,28 @@ struct RGB2HLS_f v_float32 maxRgb = v_max(v_max(r, g), b); v_float32 minRgb = v_min(v_min(r, g), b); - v_float32 diff = maxRgb - minRgb; - v_float32 msum = maxRgb + minRgb; + v_float32 diff = v_sub(maxRgb, minRgb); + v_float32 msum = v_add(maxRgb, minRgb); v_float32 vhalf = vx_setall_f32(0.5f); - l = msum * vhalf; + l = v_mul(msum, vhalf); - s = diff / v_select(l < vhalf, msum, vx_setall_f32(2.0f) - msum); + s = v_div(diff, v_select(v_lt(l, vhalf), msum, v_sub(vx_setall_f32(2.0f), msum))); - v_float32 rMaxMask = maxRgb == r; - v_float32 gMaxMask = maxRgb == g; + v_float32 rMaxMask = v_eq(maxRgb, r); + v_float32 gMaxMask = v_eq(maxRgb, g); - h = v_select(rMaxMask, g - b, v_select(gMaxMask, b - r, r - g)); - v_float32 hpart = v_select(rMaxMask, (g < b) & vx_setall_f32(360.0f), + h = v_select(rMaxMask, v_sub(g, b), v_select(gMaxMask, v_sub(b, r), v_sub(r, g))); + v_float32 hpart = v_select(rMaxMask, v_select(v_lt(g, b), vx_setall_f32(360.0f), vx_setall_f32(0.0f)), v_select(gMaxMask, vx_setall_f32(120.0f), vx_setall_f32(240.0f))); - v_float32 invDiff = vx_setall_f32(60.0f) / diff; - h = v_muladd(h, invDiff, hpart) * vhscale; + v_float32 invDiff = v_div(vx_setall_f32(60.0f), diff); + h = v_mul(v_muladd(h, invDiff, hpart), vhscale); - v_float32 diffEpsMask = diff > vx_setall_f32(FLT_EPSILON); + v_float32 diffEpsMask = v_gt(diff, vx_setall_f32(FLT_EPSILON)); - h = diffEpsMask & h; + h = v_select(diffEpsMask, h, vx_setall_f32(0.0f)); // l = l; - s = diffEpsMask & s; + s = v_select(diffEpsMask, s, vx_setall_f32(0.0f)); } #endif @@ -660,8 +717,8 @@ struct RGB2HLS_f int i = 0, bidx = blueIdx, scn = srccn; -#if CV_SIMD - const int vsize = v_float32::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + const int vsize = VTraits::vlanes(); v_float32 vhscale = vx_setall_f32(hscale); for ( ; i <= n - vsize; @@ -744,29 +801,29 @@ struct RGB2HLS_b int scn = srccn; -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE float CV_DECL_ALIGNED(CV_SIMD_WIDTH) buf[bufChannels*BLOCK_SIZE]; #else float CV_DECL_ALIGNED(16) buf[bufChannels*BLOCK_SIZE]; #endif -#if CV_SIMD - static const int fsize = v_float32::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + static const int fsize = VTraits::vlanes(); //TODO: fix that when v_interleave is available - float CV_DECL_ALIGNED(CV_SIMD_WIDTH) interTmpM[fsize*3]; + float CV_DECL_ALIGNED(CV_SIMD_WIDTH) interTmpM[VTraits::max_nlanes*3]; v_store_interleave(interTmpM, vx_setall_f32(1.f), vx_setall_f32(255.f), vx_setall_f32(255.f)); - v_float32 mhls[3]; - for(int k = 0; k < 3; k++) - { - mhls[k] = vx_load_aligned(interTmpM + k*fsize); - } + v_float32 mhls0, mhls1, mhls2, mhls3; + mhls0 = vx_load_aligned(interTmpM); + mhls1 = vx_load_aligned(interTmpM + fsize); + mhls2 = vx_load_aligned(interTmpM + 2*fsize); + mhls3 = vx_load_aligned(interTmpM + 3*fsize); #endif for(int i = 0; i < n; i += BLOCK_SIZE, dst += BLOCK_SIZE*3 ) { int dn = std::min(n - i, (int)BLOCK_SIZE); -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE v_float32 v255inv = vx_setall_f32(1.f/255.f); if (scn == 3) { @@ -778,8 +835,8 @@ struct RGB2HLS_b v_uint16 drgb = vx_load_expand(src); v_int32 qrgb0, qrgb1; v_expand(v_reinterpret_as_s16(drgb), qrgb0, qrgb1); - v_store_aligned(buf + j + 0*fsize, v_cvt_f32(qrgb0)*v255inv); - v_store_aligned(buf + j + 1*fsize, v_cvt_f32(qrgb1)*v255inv); + v_store_aligned(buf + j + 0*fsize, v_mul(v_cvt_f32(qrgb0),v255inv)); + v_store_aligned(buf + j + 1*fsize, v_mul(v_cvt_f32(qrgb1),v255inv)); } for( ; j < dn*3; j++, src++ ) { @@ -793,30 +850,39 @@ struct RGB2HLS_b for ( ; j <= dn*bufChannels - nBlock*bufChannels; j += nBlock*bufChannels, src += nBlock*4) { - v_uint8 rgb[3], dummy; - v_load_deinterleave(src, rgb[0], rgb[1], rgb[2], dummy); + v_uint8 rgb0, rgb1, rgb2, rgb3, dummy; + v_load_deinterleave(src, rgb0, rgb1, rgb2, dummy); - v_uint16 d[3*2]; - for(int k = 0; k < 3; k++) - { - v_expand(rgb[k], d[k*2+0], d[k*2+1]); - } - v_int32 q[3*4]; - for(int k = 0; k < 3*2; k++) - { - v_expand(v_reinterpret_as_s16(d[k]), q[k*2+0], q[k*2+1]); - } + v_uint16 d0,d1,d2,d3,d4,d5; + v_expand(rgb0, d0, d1); + v_expand(rgb1, d2, d3); + v_expand(rgb2, d4, d5); + v_int32 q0,q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11; - v_float32 f[3*4]; - for(int k = 0; k < 3*4; k++) - { - f[k] = v_cvt_f32(q[k])*v255inv; - } + v_expand(v_reinterpret_as_s16(d0), q0, q1); + v_expand(v_reinterpret_as_s16(d1), q2, q3); + v_expand(v_reinterpret_as_s16(d2), q4, q5); + v_expand(v_reinterpret_as_s16(d3), q6, q7); + v_expand(v_reinterpret_as_s16(d4), q8, q9); + v_expand(v_reinterpret_as_s16(d5), q10, q11); + v_float32 f0,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11; + f0 = v_mul(v_cvt_f32(q0), v255inv); + f1 = v_mul(v_cvt_f32(q1), v255inv); + f2 = v_mul(v_cvt_f32(q2), v255inv); + f3 = v_mul(v_cvt_f32(q3), v255inv); + f4 = v_mul(v_cvt_f32(q4), v255inv); + f5 = v_mul(v_cvt_f32(q5), v255inv); + f6 = v_mul(v_cvt_f32(q6), v255inv); + f7 = v_mul(v_cvt_f32(q7), v255inv); + f8 = v_mul(v_cvt_f32(q8), v255inv); + f9 = v_mul(v_cvt_f32(q9), v255inv); + f10 = v_mul(v_cvt_f32(q10), v255inv); + f11 = v_mul(v_cvt_f32(q11), v255inv); - for(int k = 0; k < 4; k++) - { - v_store_interleave(buf + j + k*bufChannels*fsize, f[0*4+k], f[1*4+k], f[2*4+k]); - } + v_store_interleave(buf + j, f0, f4, f8); + v_store_interleave(buf + j + bufChannels*fsize, f1, f5, f9); + v_store_interleave(buf + j + 2*bufChannels*fsize, f2, f6, f10); + v_store_interleave(buf + j + 3*bufChannels*fsize, f3, f7, f11); } for( ; j < dn*3; j += 3, src += 4 ) { @@ -836,34 +902,53 @@ struct RGB2HLS_b cvt(buf, buf, dn); int j = 0; -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE for( ; j <= dn*3 - fsize*3*4; j += fsize*3*4) { - v_float32 f[3*4]; - for(int k = 0; k < 3*4; k++) - { - f[k] = vx_load_aligned(buf + j + k*fsize); - } + v_float32 f0,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11; + f0 = vx_load_aligned(buf + j + 0*fsize); + f1 = vx_load_aligned(buf + j + 1*fsize); + f2 = vx_load_aligned(buf + j + 2*fsize); + f3 = vx_load_aligned(buf + j + 3*fsize); + f4 = vx_load_aligned(buf + j + 4*fsize); + f5 = vx_load_aligned(buf + j + 5*fsize); + f6 = vx_load_aligned(buf + j + 6*fsize); + f7 = vx_load_aligned(buf + j + 7*fsize); + f8 = vx_load_aligned(buf + j + 8*fsize); + f9 = vx_load_aligned(buf + j + 9*fsize); + f10 = vx_load_aligned(buf + j + 10*fsize); + f11 = vx_load_aligned(buf + j + 11*fsize); - for(int k = 0; k < 4; k++) - { - for(int l = 0; l < 3; l++) - { - f[k*3+l] = f[k*3+l] * mhls[l]; - } - } + f0 = v_mul(f0, mhls0); + f1 = v_mul(f1, mhls1); + f2 = v_mul(f2, mhls2); + f3 = v_mul(f3, mhls0); + f4 = v_mul(f4, mhls1); + f5 = v_mul(f5, mhls2); + f6 = v_mul(f6, mhls0); + f7 = v_mul(f7, mhls1); + f8 = v_mul(f8, mhls2); + f9 = v_mul(f9, mhls0); + f10 = v_mul(f10, mhls1); + f11 = v_mul(f11, mhls2); - v_int32 q[3*4]; - for(int k = 0; k < 3*4; k++) - { - q[k] = v_round(f[k]); - } + v_int32 q0,q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11; + q0 = v_round(f0); + q1 = v_round(f1); + q2 = v_round(f2); + q3 = v_round(f3); + q4 = v_round(f4); + q5 = v_round(f5); + q6 = v_round(f6); + q7 = v_round(f7); + q8 = v_round(f8); + q9 = v_round(f9); + q10 = v_round(f10); + q11 = v_round(f11); - for(int k = 0; k < 3; k++) - { - v_store(dst + j + k*fsize*4, v_pack_u(v_pack(q[k*4+0], q[k*4+1]), - v_pack(q[k*4+2], q[k*4+3]))); - } + v_store(dst + j + 0*fsize*4, v_pack_u(v_pack(q0, q1),v_pack(q2, q3))); + v_store(dst + j + 1*fsize*4, v_pack_u(v_pack(q4, q5),v_pack(q6, q7))); + v_store(dst + j + 2*fsize*4, v_pack_u(v_pack(q8, q9),v_pack(q10, q11))); } #endif for( ; j < dn*3; j += 3 ) @@ -888,39 +973,39 @@ struct HLS2RGB_f : dstcn(_dstcn), blueIdx(_blueIdx), hscale(6.f/_hrange) { } -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE inline void process(const v_float32& h, const v_float32& l, const v_float32& s, v_float32& b, v_float32& g, v_float32& r) const { v_float32 v1 = vx_setall_f32(1.0f), v2 = vx_setall_f32(2.0f), v4 = vx_setall_f32(4.0f); - v_float32 lBelowHalfMask = l <= vx_setall_f32(0.5f); - v_float32 ls = l * s; - v_float32 elem0 = v_select(lBelowHalfMask, ls, s - ls); + v_float32 lBelowHalfMask = v_le(l, vx_setall_f32(0.5f)); + v_float32 ls = v_mul(l, s); + v_float32 elem0 = v_select(lBelowHalfMask, ls, v_sub(s, ls)); - v_float32 hsRaw = h * vx_setall_f32(hscale); + v_float32 hsRaw = v_mul(h, vx_setall_f32(hscale)); v_float32 preHs = v_cvt_f32(v_trunc(hsRaw)); - v_float32 hs = hsRaw - preHs; - v_float32 sector = preHs - vx_setall_f32(6.0f) * v_cvt_f32(v_trunc(hsRaw * vx_setall_f32(1.0f / 6.0f))); - v_float32 elem1 = hs + hs; + v_float32 hs = v_sub(hsRaw, preHs); + v_float32 sector = v_sub(preHs, v_mul(vx_setall_f32(6.0f), v_cvt_f32(v_trunc(v_mul(hsRaw, vx_setall_f32(1.0f / 6.0f)))))); + v_float32 elem1 = v_add(hs, hs); - v_float32 tab0 = l + elem0; - v_float32 tab1 = l - elem0; - v_float32 tab2 = l + elem0 - elem0 * elem1; - v_float32 tab3 = l - elem0 + elem0 * elem1; + v_float32 tab0 = v_add(l, elem0); + v_float32 tab1 = v_sub(l, elem0); + v_float32 tab2 = v_sub(v_add(l, elem0), v_mul(elem0, elem1)); + v_float32 tab3 = v_add(v_sub(l, elem0), v_mul(elem0, elem1)); - b = v_select(sector < v2, tab1, - v_select(sector <= v2, tab3, - v_select(sector <= v4, tab0, tab2))); + b = v_select(v_lt(sector, v2), tab1, + v_select(v_le(sector, v2), tab3, + v_select(v_le(sector, v4), tab0, tab2))); - g = v_select(sector < v1, tab3, - v_select(sector <= v2, tab0, - v_select(sector < v4, tab2, tab1))); + g = v_select(v_lt(sector, v1), tab3, + v_select(v_le(sector, v2), tab0, + v_select(v_lt(sector, v4), tab2, tab1))); - r = v_select(sector < v1, tab0, - v_select(sector < v2, tab2, - v_select(sector < v4, tab1, - v_select(sector <= v4, tab3, tab0)))); + r = v_select(v_lt(sector, v1), tab0, + v_select(v_lt(sector, v2), tab2, + v_select(v_lt(sector, v4), tab1, + v_select(v_le(sector, v4), tab3, tab0)))); } #endif @@ -931,8 +1016,8 @@ struct HLS2RGB_f int i = 0, bidx = blueIdx, dcn = dstcn; float alpha = ColorChannel::max(); -#if CV_SIMD - static const int vsize = v_float32::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + static const int vsize = VTraits::vlanes(); for (; i <= n - vsize; i += vsize, src += 3*vsize, dst += dcn*vsize) { v_float32 h, l, s, r, g, b; @@ -1020,23 +1105,22 @@ struct HLS2RGB_b int i, j, dcn = dstcn; uchar alpha = ColorChannel::max(); -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE float CV_DECL_ALIGNED(CV_SIMD_WIDTH) buf[bufChannels*BLOCK_SIZE]; #else float CV_DECL_ALIGNED(16) buf[bufChannels*BLOCK_SIZE]; #endif -#if CV_SIMD - static const int fsize = v_float32::nlanes; +#if CV_SIMD || CV_SIMD_SCALABLE + static const int fsize = VTraits::vlanes(); //TODO: fix that when v_interleave is available - float CV_DECL_ALIGNED(CV_SIMD_WIDTH) interTmpM[fsize*3]; + float CV_DECL_ALIGNED(CV_SIMD_WIDTH) interTmpM[VTraits::max_nlanes*3]; v_float32 v255inv = vx_setall_f32(1.f/255.f); v_store_interleave(interTmpM, vx_setall_f32(1.f), v255inv, v255inv); - v_float32 mhls[3]; - for(int k = 0; k < 3; k++) - { - mhls[k] = vx_load_aligned(interTmpM + k*fsize); - } + v_float32 mhls0, mhls1, mhls2; + mhls0 = vx_load_aligned(interTmpM + 0*fsize); + mhls1 = vx_load_aligned(interTmpM + 1*fsize); + mhls2 = vx_load_aligned(interTmpM + 2*fsize); #endif for( i = 0; i < n; i += BLOCK_SIZE, src += BLOCK_SIZE*3 ) @@ -1044,39 +1128,53 @@ struct HLS2RGB_b int dn = std::min(n - i, (int)BLOCK_SIZE); j = 0; -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE for( ; j <= dn*3 - 3*4*fsize; j += 3*4*fsize) { // 3x uchar -> 3*4 float - v_uint8 u[3]; - for(int k = 0; k < 3; k++) - { - u[k] = vx_load(src + j + k*4*fsize); - } - v_uint16 d[3*2]; - for(int k = 0; k < 3; k++) - { - v_expand(u[k], d[k*2+0], d[k*2+1]); - } - v_int32 q[3*4]; - for(int k = 0; k < 3*2; k++) - { - v_expand(v_reinterpret_as_s16(d[k]), q[k*2+0], q[k*2+1]); - } + v_uint8 u0, u1, u2; + u0 = vx_load(src + j + 0*4*fsize); + u1 = vx_load(src + j + 1*4*fsize); + u2 = vx_load(src + j + 2*4*fsize); + v_uint16 d0, d1, d2, d3, d4, d5; + v_expand(u0, d0, d1); + v_expand(u1, d2, d3); + v_expand(u2, d4, d5); - v_float32 f[3*4]; - for(int k = 0; k < 4; k++) - { - for(int l = 0; l < 3; l++) - { - f[k*3+l] = v_cvt_f32(q[k*3+l])*mhls[l]; - } - } + v_int32 q0,q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11; + v_expand(v_reinterpret_as_s16(d0), q0, q1); + v_expand(v_reinterpret_as_s16(d1), q2, q3); + v_expand(v_reinterpret_as_s16(d2), q4, q5); + v_expand(v_reinterpret_as_s16(d3), q6, q7); + v_expand(v_reinterpret_as_s16(d4), q8, q9); + v_expand(v_reinterpret_as_s16(d5), q10, q11); - for (int k = 0; k < 4*3; k++) - { - v_store_aligned(buf + j + k*fsize, f[k]); - } + v_float32 f0,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11; + f0 = v_mul(v_cvt_f32(q0),mhls0); + f1 = v_mul(v_cvt_f32(q1),mhls1); + f2 = v_mul(v_cvt_f32(q2),mhls2); + f3 = v_mul(v_cvt_f32(q3),mhls0); + f4 = v_mul(v_cvt_f32(q4),mhls1); + f5 = v_mul(v_cvt_f32(q5),mhls2); + f6 = v_mul(v_cvt_f32(q6),mhls0); + f7 = v_mul(v_cvt_f32(q7),mhls1); + f8 = v_mul(v_cvt_f32(q8),mhls2); + f9 = v_mul(v_cvt_f32(q9),mhls0); + f10 = v_mul(v_cvt_f32(q10),mhls1); + f11 = v_mul(v_cvt_f32(q11),mhls2); + + v_store_aligned(buf + j + 0*fsize, f0); + v_store_aligned(buf + j + 1*fsize, f1); + v_store_aligned(buf + j + 2*fsize, f2); + v_store_aligned(buf + j + 3*fsize, f3); + v_store_aligned(buf + j + 4*fsize, f4); + v_store_aligned(buf + j + 5*fsize, f5); + v_store_aligned(buf + j + 6*fsize, f6); + v_store_aligned(buf + j + 7*fsize, f7); + v_store_aligned(buf + j + 8*fsize, f8); + v_store_aligned(buf + j + 9*fsize, f9); + v_store_aligned(buf + j + 10*fsize, f10); + v_store_aligned(buf + j + 11*fsize, f11); } #endif for( ; j < dn*3; j += 3 ) @@ -1087,7 +1185,7 @@ struct HLS2RGB_b } cvt(buf, buf, dn); -#if CV_SIMD +#if CV_SIMD || CV_SIMD_SCALABLE v_float32 v255 = vx_setall_f32(255.f); if(dcn == 3) { @@ -1095,18 +1193,18 @@ struct HLS2RGB_b float* pbuf = buf; for( ; x <= dn - 4*fsize; x += 4*fsize, dst += 4*fsize, pbuf += 4*fsize) { - v_float32 vf[4]; - vf[0] = vx_load_aligned(pbuf + 0*fsize); - vf[1] = vx_load_aligned(pbuf + 1*fsize); - vf[2] = vx_load_aligned(pbuf + 2*fsize); - vf[3] = vx_load_aligned(pbuf + 3*fsize); - v_int32 vi[4]; - vi[0] = v_round(vf[0]*v255); - vi[1] = v_round(vf[1]*v255); - vi[2] = v_round(vf[2]*v255); - vi[3] = v_round(vf[3]*v255); - v_store(dst, v_pack_u(v_pack(vi[0], vi[1]), - v_pack(vi[2], vi[3]))); + v_float32 vf0, vf1, vf2, vf3; + vf0 = vx_load_aligned(pbuf + 0*fsize); + vf1 = vx_load_aligned(pbuf + 1*fsize); + vf2 = vx_load_aligned(pbuf + 2*fsize); + vf3 = vx_load_aligned(pbuf + 3*fsize); + v_int32 vi0, vi1, vi2, vi3; + vi0 = v_round(v_mul(vf0,v255)); + vi1 = v_round(v_mul(vf1,v255)); + vi2 = v_round(v_mul(vf2,v255)); + vi3 = v_round(v_mul(vf3,v255)); + v_store(dst, v_pack_u(v_pack(vi0, vi1), + v_pack(vi2, vi3))); } for( ; x < dn*3; x++, dst++, pbuf++) { @@ -1119,19 +1217,28 @@ struct HLS2RGB_b float* pbuf = buf; for ( ; x <= dn - 4*fsize; x += fsize, dst += 4*fsize, pbuf += bufChannels*fsize) { - v_float32 r[4], g[4], b[4]; - v_int32 ir[4], ig[4], ib[4]; - for(int k = 0; k < 4; k++) - { - v_load_deinterleave(pbuf, r[k], g[k], b[k]); - ir[k] = v_round(r[k]*v255); - ig[k] = v_round(g[k]*v255); - ib[k] = v_round(b[k]*v255); - } + v_float32 r0, r1, r2, r3, g0, g1, g2, g3, b0, b1, b2, b3; + v_int32 ir0, ir1, ir2, ir3, ig0, ig1, ig2, ig3, ib0, ib1, ib2, ib3; + v_load_deinterleave(pbuf, r0, g0, b0); + ir0 = v_round(v_mul(r0, v255)); + ig0 = v_round(v_mul(g0, v255)); + ib0 = v_round(v_mul(b0, v255)); + v_load_deinterleave(pbuf, r1, g1, b1); + ir1 = v_round(v_mul(r1, v255)); + ig1 = v_round(v_mul(g1, v255)); + ib1 = v_round(v_mul(b1, v255)); + v_load_deinterleave(pbuf, r2, g2, b2); + ir2 = v_round(v_mul(r2, v255)); + ig2 = v_round(v_mul(g2, v255)); + ib2 = v_round(v_mul(b2, v255)); + v_load_deinterleave(pbuf, r3, g3, b3); + ir3 = v_round(v_mul(r3, v255)); + ig3 = v_round(v_mul(g3, v255)); + ib3 = v_round(v_mul(b3, v255)); v_uint8 ur, ug, ub; - ur = v_pack_u(v_pack(ir[0], ir[1]), v_pack(ir[2], ir[3])); - ug = v_pack_u(v_pack(ig[0], ig[1]), v_pack(ig[2], ig[3])); - ub = v_pack_u(v_pack(ib[0], ib[1]), v_pack(ib[2], ib[3])); + ur = v_pack_u(v_pack(ir0, ir1), v_pack(ir2, ir3)); + ug = v_pack_u(v_pack(ig0, ig1), v_pack(ig2, ig3)); + ub = v_pack_u(v_pack(ib0, ib1), v_pack(ib2, ib3)); v_uint8 valpha = vx_setall_u8(alpha); v_store_interleave(dst, ur, ug, ub, valpha); diff --git a/modules/imgproc/src/filter.dispatch.cpp b/modules/imgproc/src/filter.dispatch.cpp index 83daf02c48..8279e91763 100644 --- a/modules/imgproc/src/filter.dispatch.cpp +++ b/modules/imgproc/src/filter.dispatch.cpp @@ -1401,7 +1401,7 @@ static void ocvSepFilter(int stype, int dtype, int ktype, Mat src(Size(width, height), stype, src_data, src_step); Mat dst(Size(width, height), dtype, dst_data, dst_step); f->apply(src, dst, Size(full_width, full_height), Point(offset_x, offset_y)); -}; +} //=================================================================== // HAL functions diff --git a/modules/imgproc/src/floodfill.cpp b/modules/imgproc/src/floodfill.cpp index 0d1886591b..8877cae9b6 100644 --- a/modules/imgproc/src/floodfill.cpp +++ b/modules/imgproc/src/floodfill.cpp @@ -283,7 +283,7 @@ floodFillGrad_CnIR( Mat& image, Mat& msk, Diff diff, ConnectedComp* region, int flags, std::vector* buffer ) { - int step = (int)image.step, maskStep = (int)msk.step; + size_t step = image.step, maskStep = msk.step; uchar* pImage = image.ptr(); _Tp* img = (_Tp*)(pImage + step*seed.y); uchar* pMask = msk.ptr() + maskStep + sizeof(_MTp); diff --git a/modules/imgproc/src/hough.cpp b/modules/imgproc/src/hough.cpp index f848cd3a39..44fe25bdd9 100644 --- a/modules/imgproc/src/hough.cpp +++ b/modules/imgproc/src/hough.cpp @@ -68,6 +68,18 @@ struct hough_cmp_gt const int* aux; }; +static inline int +computeNumangle( double min_theta, double max_theta, double theta_step ) +{ + int numangle = cvFloor((max_theta - min_theta) / theta_step) + 1; + // If the distance between the first angle and the last angle is + // approximately equal to pi, then the last angle will be removed + // in order to prevent a line to be detected twice. + if ( numangle > 1 && fabs(CV_PI - (numangle-1)*theta_step) < theta_step/2 ) + --numangle; + return numangle; +} + static void createTrigTable( int numangle, double min_theta, double theta_step, float irho, float *tabSin, float *tabCos ) @@ -130,7 +142,7 @@ HoughLinesStandard( InputArray src, OutputArray lines, int type, CV_CheckGE(max_theta, min_theta, "max_theta must be greater than min_theta"); - int numangle = cvRound((max_theta - min_theta) / theta); + int numangle = computeNumangle(min_theta, max_theta, theta); int numrho = cvRound(((max_rho - min_rho) + 1) / rho); #if defined HAVE_IPP && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_HOUGH @@ -475,7 +487,7 @@ HoughLinesProbabilistic( Mat& image, int width = image.cols; int height = image.rows; - int numangle = cvRound(CV_PI / theta); + int numangle = computeNumangle(0.0, CV_PI, theta); int numrho = cvRound(((width + height) * 2 + 1) / rho); #if defined HAVE_IPP && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_HOUGH @@ -792,7 +804,7 @@ static bool ocl_HoughLines(InputArray _src, OutputArray _lines, double rho, doub } UMat src = _src.getUMat(); - int numangle = cvRound((max_theta - min_theta) / theta); + int numangle = computeNumangle(min_theta, max_theta, theta); int numrho = cvRound(((src.cols + src.rows) * 2 + 1) / rho); UMat pointsList; @@ -846,7 +858,7 @@ static bool ocl_HoughLinesP(InputArray _src, OutputArray _lines, double rho, dou } UMat src = _src.getUMat(); - int numangle = cvRound(CV_PI / theta); + int numangle = computeNumangle(0.0, CV_PI, theta); int numrho = cvRound(((src.cols + src.rows) * 2 + 1) / rho); UMat pointsList; @@ -956,7 +968,7 @@ void HoughLinesPointSet( InputArray _point, OutputArray _lines, int lines_max, i int i; float irho = 1 / (float)rho_step; float irho_min = ((float)min_rho * irho); - int numangle = cvRound((max_theta - min_theta) / theta_step); + int numangle = computeNumangle(min_theta, max_theta, theta_step); int numrho = cvRound((max_rho - min_rho + 1) / rho_step); Mat _accum = Mat::zeros( (numangle+2), (numrho+2), CV_32SC1 ); @@ -2293,6 +2305,9 @@ static void HoughCircles( InputArray _image, OutputArray _circles, break; case HOUGH_GRADIENT_ALT: { + if( param2 >= 1 ) + CV_Error( Error::StsOutOfRange, "when using HOUGH_GRADIENT_ALT method, param2 parameter must be smaller than 1.0" ); + std::vector circles; Mat image = _image.getMat(); HoughCirclesAlt(image, circles, dp, minDist, minRadius, maxRadius, param1, param2); @@ -2320,7 +2335,7 @@ static void HoughCircles( InputArray _image, OutputArray _circles, } break; default: - CV_Error( Error::StsBadArg, "Unrecognized method id. Actually only cv::HOUGH_GRADIENT is supported." ); + CV_Error( Error::StsBadArg, "Unrecognized method id. Actually supported methods are HOUGH_GRADIENT and HOUGH_GRADIENT_ALT" ); } } diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 25107328d7..0542c47e59 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -2178,6 +2178,9 @@ public: #if CV_TRY_SSE4_1 bool useSSE4_1 = CV_CPU_HAS_SUPPORT_SSE4_1; #endif + #if CV_TRY_LASX + bool useLASX = CV_CPU_HAS_SUPPORT_LASX; + #endif int bh0 = std::min(BLOCK_SZ/2, dst.rows); int bw0 = std::min(BLOCK_SZ*BLOCK_SZ/bh0, dst.cols); @@ -2241,6 +2244,10 @@ public: if ( useAVX2 ) x1 = opt_AVX2::warpAffineBlockline(adelta + x, bdelta + x, xy, alpha, X0, Y0, bw); #endif + #if CV_TRY_LASX + if ( useLASX ) + x1 = opt_LASX::warpAffineBlockline(adelta + x, bdelta + x, xy, alpha, X0, Y0, bw); + #endif #if CV_SIMD128 { v_int32x4 v__X0 = v_setall_s32(X0), v__Y0 = v_setall_s32(Y0); @@ -2535,6 +2542,127 @@ static bool ocl_warpTransform(InputArray _src, OutputArray _dst, InputArray _M0, #endif +#ifdef HAVE_IPP +#define IPP_WARPAFFINE_PARALLEL 1 + +#ifdef HAVE_IPP_IW + +class ipp_warpAffineParallel: public ParallelLoopBody +{ +public: + ipp_warpAffineParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, IppiInterpolationType _inter, double (&_coeffs)[2][3], ::ipp::IwiBorderType _borderType, IwTransDirection _iwTransDirection, bool *_ok):m_src(src), m_dst(dst) + { + pOk = _ok; + + inter = _inter; + borderType = _borderType; + iwTransDirection = _iwTransDirection; + + for( int i = 0; i < 2; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = _coeffs[i][j]; + + *pOk = true; + } + ~ipp_warpAffineParallel() {} + + virtual void operator() (const Range& range) const CV_OVERRIDE + { + CV_INSTRUMENT_REGION_IPP(); + + if(*pOk == false) + return; + + try + { + ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); + CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, m_src, m_dst, coeffs, iwTransDirection, inter, ::ipp::IwiWarpAffineParams(), borderType, tile); + } + catch(const ::ipp::IwException &) + { + *pOk = false; + return; + } + } +private: + ::ipp::IwiImage &m_src; + ::ipp::IwiImage &m_dst; + + IppiInterpolationType inter; + double coeffs[2][3]; + ::ipp::IwiBorderType borderType; + IwTransDirection iwTransDirection; + + bool *pOk; + const ipp_warpAffineParallel& operator= (const ipp_warpAffineParallel&); +}; + +#endif + +static bool ipp_warpAffine( InputArray _src, OutputArray _dst, int interpolation, int borderType, InputArray _M, int flags ) +{ +#ifdef HAVE_IPP_IW + CV_INSTRUMENT_REGION_IPP(); + + if (!cv::ipp::useIPP_NotExact()) + return false; + + IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); + if((int)ippInter < 0) + return false; + + // Acquire data and begin processing + try + { + Mat src = _src.getMat(); + Mat dst = _dst.getMat(); + ::ipp::IwiImage iwSrc = ippiGetImage(src); + ::ipp::IwiImage iwDst = ippiGetImage(dst); + ::ipp::IwiBorderType ippBorder(ippiGetBorderType(borderType)); + IwTransDirection iwTransDirection; + if(!ippBorder) + return false; + + if( !(flags & WARP_INVERSE_MAP) ) + iwTransDirection = iwTransForward; + else + iwTransDirection = iwTransInverse; + + Mat M = _M.getMat(); + double coeffs[2][3]; + for( int i = 0; i < 2; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = M.at(i, j); + + const int threads = ippiSuggestThreadsNum(iwDst, 2); + + if(IPP_WARPAFFINE_PARALLEL && threads > 1) + { + bool ok = true; + Range range(0, (int)iwDst.m_size.height); + ipp_warpAffineParallel invoker(iwSrc, iwDst, ippInter, coeffs, ippBorder, iwTransDirection, &ok); + if(!ok) + return false; + + parallel_for_(range, invoker, threads*4); + + if(!ok) + return false; + } else { + CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, iwSrc, iwDst, coeffs, iwTransDirection, ippInter, ::ipp::IwiWarpAffineParams(), ippBorder); + } + + } + catch (const ::ipp::IwException &) + { + return false; + } + + return true; +#endif +} +#endif + namespace hal { void warpAffine(int src_type, @@ -2604,6 +2732,8 @@ void cv::warpAffine( InputArray _src, OutputArray _dst, CV_Assert( (M0.type() == CV_32F || M0.type() == CV_64F) && M0.rows == 2 && M0.cols == 3 ); M0.convertTo(matM, matM.type()); + CV_IPP_RUN_FAST(ipp_warpAffine(src, dst, interpolation, borderType, matM, flags)); + if( !(flags & WARP_INVERSE_MAP) ) { double D = M[0]*M[4] - M[1]*M[3]; diff --git a/modules/imgproc/src/imgwarp.hpp b/modules/imgproc/src/imgwarp.hpp index 1f8f1c5d17..35e9f7bb02 100644 --- a/modules/imgproc/src/imgwarp.hpp +++ b/modules/imgproc/src/imgwarp.hpp @@ -61,6 +61,13 @@ int warpAffineBlockline(int *adelta, int *bdelta, short* xy, short* alpha, int X #endif } +namespace opt_LASX +{ +#if CV_TRY_LASX +int warpAffineBlockline(int *adelta, int *bdelta, short* xy, short* alpha, int X0, int Y0, int bw); +#endif +} + namespace opt_SSE4_1 { #if CV_TRY_SSE4_1 @@ -75,7 +82,7 @@ public: static Ptr getImpl(const double *M); virtual void processNN(const double *M, short* xy, double X0, double Y0, double W0, int bw) = 0; virtual void process(const double *M, short* xy, short* alpha, double X0, double Y0, double W0, int bw) = 0; - virtual ~WarpPerspectiveLine_SSE4() {}; + virtual ~WarpPerspectiveLine_SSE4() {} }; #endif } diff --git a/modules/imgproc/src/imgwarp.lasx.cpp b/modules/imgproc/src/imgwarp.lasx.cpp new file mode 100644 index 0000000000..f6bf2a13cb --- /dev/null +++ b/modules/imgproc/src/imgwarp.lasx.cpp @@ -0,0 +1,98 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Copyright (C) 2014-2015, Itseez Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's 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. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and 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 the Intel Corporation or 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. +// +//M*/ + +/* //////////////////////////////////////////////////////////////////// +// +// Geometrical transforms on images and matrices: rotation, zoom etc. +// +// */ + +#include "precomp.hpp" +#include "imgwarp.hpp" +#include "opencv2/core/hal/intrin.hpp" + +namespace cv +{ +namespace opt_LASX +{ + +int warpAffineBlockline(int *adelta, int *bdelta, short* xy, short* alpha, int X0, int Y0, int bw) +{ + const int AB_BITS = MAX(10, (int)INTER_BITS); + int x1 = 0; + __m256i fxy_mask = _v256_setall_w(INTER_TAB_SIZE - 1); + __m256i XX = _v256_setall_w(X0), YY = _v256_setall_w(Y0); + for (; x1 <= bw - 16; x1 += 16) + { + __m256i tx0, tx1, ty0, ty1; + tx0 = __lasx_xvadd_w(__lasx_xvld((const __m256i*)(adelta + x1), 0), XX); + ty0 = __lasx_xvadd_w(__lasx_xvld((const __m256i*)(bdelta + x1), 0), YY); + tx1 = __lasx_xvadd_w(__lasx_xvld((const __m256i*)(adelta + x1), 8*4), XX); + ty1 = __lasx_xvadd_w(__lasx_xvld((const __m256i*)(bdelta + x1), 8*4), YY); + + tx0 = __lasx_xvsrai_w(tx0, AB_BITS - INTER_BITS); + ty0 = __lasx_xvsrai_w(ty0, AB_BITS - INTER_BITS); + tx1 = __lasx_xvsrai_w(tx1, AB_BITS - INTER_BITS); + ty1 = __lasx_xvsrai_w(ty1, AB_BITS - INTER_BITS); + + __m256i fx_ = _lasx_packs_w(__lasx_xvand_v(tx0, fxy_mask), + __lasx_xvand_v(tx1, fxy_mask)); + __m256i fy_ = _lasx_packs_w(__lasx_xvand_v(ty0, fxy_mask), + __lasx_xvand_v(ty1, fxy_mask)); + tx0 = _lasx_packs_w(__lasx_xvsrai_w(tx0, INTER_BITS), + __lasx_xvsrai_w(tx1, INTER_BITS)); + ty0 = _lasx_packs_w(__lasx_xvsrai_w(ty0, INTER_BITS), + __lasx_xvsrai_w(ty1, INTER_BITS)); + fx_ = __lasx_xvsadd_h(fx_, __lasx_xvslli_h(fy_, INTER_BITS)); + fx_ = __lasx_xvpermi_d(fx_, (3 << 6) + (1 << 4) + (2 << 2) + 0); + + __lasx_xvst(__lasx_xvilvl_h(ty0, tx0), (__m256i*)(xy + x1 * 2), 0); + __lasx_xvst(__lasx_xvilvh_h(ty0, tx0), (__m256i*)(xy + x1 * 2), 16*2); + __lasx_xvst(fx_, (__m256i*)(alpha + x1), 0); + } + return x1; +} + +} +} +/* End of file. */ diff --git a/modules/imgproc/src/imgwarp.sse4_1.cpp b/modules/imgproc/src/imgwarp.sse4_1.cpp index 5625b45f7b..a2ec9396da 100644 --- a/modules/imgproc/src/imgwarp.sse4_1.cpp +++ b/modules/imgproc/src/imgwarp.sse4_1.cpp @@ -492,7 +492,7 @@ public: (X & (INTER_TAB_SIZE - 1))); } } - virtual ~WarpPerspectiveLine_SSE4_Impl() CV_OVERRIDE {}; + virtual ~WarpPerspectiveLine_SSE4_Impl() CV_OVERRIDE {} }; Ptr WarpPerspectiveLine_SSE4::getImpl(const double *M) diff --git a/modules/imgproc/src/pyramids.cpp b/modules/imgproc/src/pyramids.cpp index c31c26c38f..fdbca92c78 100644 --- a/modules/imgproc/src/pyramids.cpp +++ b/modules/imgproc/src/pyramids.cpp @@ -82,6 +82,8 @@ template int PyrDownVecV(T1**, T2*, int) { return 0; } template int PyrUpVecV(T1**, T2**, int) { return 0; } +template int PyrUpVecVOneRow(T1**, T2*, int) { return 0; } + #if CV_SIMD template<> int PyrDownVecH(const uchar* src, int* row, int width) @@ -717,6 +719,120 @@ template <> int PyrUpVecV(float** src, float** dst, int width) return x; } +template <> int PyrUpVecVOneRow(int** src, uchar* dst, int width) +{ + int x = 0; + const int *row0 = src[0], *row1 = src[1], *row2 = src[2]; + + for( ; x <= width - v_uint8::nlanes; x += v_uint8::nlanes) + { + v_int16 v_r00 = v_pack(vx_load(row0 + x), vx_load(row0 + x + v_int32::nlanes)), + v_r01 = v_pack(vx_load(row0 + x + 2 * v_int32::nlanes), vx_load(row0 + x + 3 * v_int32::nlanes)), + v_r10 = v_pack(vx_load(row1 + x), vx_load(row1 + x + v_int32::nlanes)), + v_r11 = v_pack(vx_load(row1 + x + 2 * v_int32::nlanes), vx_load(row1 + x + 3 * v_int32::nlanes)), + v_r20 = v_pack(vx_load(row2 + x), vx_load(row2 + x + v_int32::nlanes)), + v_r21 = v_pack(vx_load(row2 + x + 2 * v_int32::nlanes), vx_load(row2 + x + 3 * v_int32::nlanes)); + v_int16 v_2r10 = v_r10 + v_r10, v_2r11 = (v_r11 + v_r11); + v_store(dst + x, v_rshr_pack_u<6>(v_r00 + v_r20 + (v_2r10 + v_2r10 + v_2r10), v_r01 + v_r21 + (v_2r11 + v_2r11 + v_2r11))); + } + if(x <= width - v_uint16::nlanes) + { + v_int16 v_r00 = v_pack(vx_load(row0 + x), vx_load(row0 + x + v_int32::nlanes)), + v_r10 = v_pack(vx_load(row1 + x), vx_load(row1 + x + v_int32::nlanes)), + v_r20 = v_pack(vx_load(row2 + x), vx_load(row2 + x + v_int32::nlanes)); + v_int16 v_2r10 = v_r10 + v_r10; + v_rshr_pack_u_store<6>(dst + x, v_r00 + v_r20 + (v_2r10 + v_2r10 + v_2r10)); + x += v_uint16::nlanes; + } + typedef int CV_DECL_ALIGNED(1) unaligned_int; + for (; x <= width - v_int32x4::nlanes; x += v_int32x4::nlanes) + { + v_int32 v_r00 = vx_load(row0 + x), + v_r10 = vx_load(row1 + x), + v_r20 = vx_load(row2 + x); + v_int32 v_2r10 = v_r10 + v_r10; + v_int16 d = v_pack(v_r00 + v_r20 + (v_2r10 + v_2r10 + v_2r10), (v_r10 + v_r20) << 2); + *(unaligned_int*)(dst + x) = v_reinterpret_as_s32(v_rshr_pack_u<6>(d, vx_setzero_s16())).get0(); + } + vx_cleanup(); + + return x; +} + +template <> int PyrUpVecVOneRow(int** src, short* dst, int width) +{ + int x = 0; + const int *row0 = src[0], *row1 = src[1], *row2 = src[2]; + + for( ; x <= width - v_int16::nlanes; x += v_int16::nlanes) + { + v_int32 v_r00 = vx_load(row0 + x), + v_r01 = vx_load(row0 + x + v_int32::nlanes), + v_r10 = vx_load(row1 + x), + v_r11 = vx_load(row1 + x + v_int32::nlanes), + v_r20 = vx_load(row2 + x), + v_r21 = vx_load(row2 + x + v_int32::nlanes); + v_store(dst + x, v_rshr_pack<6>(v_r00 + v_r20 + ((v_r10 << 1) + (v_r10 << 2)), v_r01 + v_r21 + ((v_r11 << 1) + (v_r11 << 2)))); + } + if(x <= width - v_int32::nlanes) + { + v_int32 v_r00 = vx_load(row0 + x), + v_r10 = vx_load(row1 + x), + v_r20 = vx_load(row2 + x); + v_rshr_pack_store<6>(dst + x, v_r00 + v_r20 + ((v_r10 << 1) + (v_r10 << 2))); + x += v_int32::nlanes; + } + vx_cleanup(); + + return x; +} + +template <> int PyrUpVecVOneRow(int** src, ushort* dst, int width) +{ + int x = 0; + const int *row0 = src[0], *row1 = src[1], *row2 = src[2]; + + for( ; x <= width - v_uint16::nlanes; x += v_uint16::nlanes) + { + v_int32 v_r00 = vx_load(row0 + x), + v_r01 = vx_load(row0 + x + v_int32::nlanes), + v_r10 = vx_load(row1 + x), + v_r11 = vx_load(row1 + x + v_int32::nlanes), + v_r20 = vx_load(row2 + x), + v_r21 = vx_load(row2 + x + v_int32::nlanes); + v_store(dst + x, v_rshr_pack_u<6>(v_r00 + v_r20 + ((v_r10 << 1) + (v_r10 << 2)), v_r01 + v_r21 + ((v_r11 << 1) + (v_r11 << 2)))); + } + if(x <= width - v_int32::nlanes) + { + v_int32 v_r00 = vx_load(row0 + x), + v_r10 = vx_load(row1 + x), + v_r20 = vx_load(row2 + x); + v_rshr_pack_u_store<6>(dst + x, v_r00 + v_r20 + ((v_r10 << 1) + (v_r10 << 2))); + x += v_int32::nlanes; + } + vx_cleanup(); + + return x; +} + +template <> int PyrUpVecVOneRow(float** src, float* dst, int width) +{ + int x = 0; + const float *row0 = src[0], *row1 = src[1], *row2 = src[2]; + + v_float32 v_6 = vx_setall_f32(6.0f), v_scale = vx_setall_f32(1.f/64.f); + for( ; x <= width - v_float32::nlanes; x += v_float32::nlanes) + { + v_float32 v_r0 = vx_load(row0 + x), + v_r1 = vx_load(row1 + x), + v_r2 = vx_load(row2 + x); + v_store(dst + x, v_scale * (v_muladd(v_6, v_r1, v_r0) + v_r2)); + } + vx_cleanup(); + + return x; +} + #endif template @@ -983,13 +1099,26 @@ pyrUp_( const Mat& _src, Mat& _dst, int) row0 = rows[0]; row1 = rows[1]; row2 = rows[2]; dsts[0] = dst0; dsts[1] = dst1; - x = PyrUpVecV(rows, dsts, dsize.width); - for( ; x < dsize.width; x++ ) + if (dst0 != dst1) { - T t1 = castOp((row1[x] + row2[x])*4); - T t0 = castOp(row0[x] + row1[x]*6 + row2[x]); - dst1[x] = t1; dst0[x] = t0; + x = PyrUpVecV(rows, dsts, dsize.width); + for( ; x < dsize.width; x++ ) + { + T t1 = castOp((row1[x] + row2[x])*4); + T t0 = castOp(row0[x] + row1[x]*6 + row2[x]); + dst1[x] = t1; dst0[x] = t0; + } } + else + { + x = PyrUpVecVOneRow(rows, dst0, dsize.width); + for( ; x < dsize.width; x++ ) + { + T t0 = castOp(row0[x] + row1[x]*6 + row2[x]); + dst0[x] = t0; + } + } + } if (dsize.height > ssize.height*2) diff --git a/modules/imgproc/src/resize.cpp b/modules/imgproc/src/resize.cpp index d634f1cc70..3ef2d5f839 100644 --- a/modules/imgproc/src/resize.cpp +++ b/modules/imgproc/src/resize.cpp @@ -339,7 +339,7 @@ template static void hlineResizeCn(ET* src, int cn, int *ofst, FT* m, FT* dst, int dst_min, int dst_max, int dst_width) { hline::ResizeCn(src, cn, ofst, m, dst, dst_min, dst_max, dst_width); -}; +} template <> void hlineResizeCn(uint8_t* src, int, int *ofst, ufixedpoint16* m, ufixedpoint16* dst, int dst_min, int dst_max, int dst_width) @@ -1098,6 +1098,16 @@ resizeNN( const Mat& src, Mat& dst, double fx, double fy ) opt_SSE4_1::resizeNN4_SSE4_1(range, src, dst, x_ofs, ify); } else +#endif +#if CV_TRY_LASX + if(CV_CPU_HAS_SUPPORT_LASX && ((pix_size == 2) || (pix_size == 4))) + { + if(pix_size == 2) + opt_LASX::resizeNN2_LASX(range, src, dst, x_ofs, ify); + else + opt_LASX::resizeNN4_LASX(range, src, dst, x_ofs, ify); + } + else #endif { resizeNNInvoker invoker(src, dst, x_ofs, ify); diff --git a/modules/imgproc/src/resize.hpp b/modules/imgproc/src/resize.hpp index 67cf5184af..1636e7585e 100644 --- a/modules/imgproc/src/resize.hpp +++ b/modules/imgproc/src/resize.hpp @@ -70,6 +70,15 @@ void resizeNN4_SSE4_1(const Range&, const Mat&, Mat&, int*, double); int VResizeLanczos4Vec_32f16u_SSE41(const float** src, ushort* dst, const float* beta, int width); #endif } + +namespace opt_LASX +{ +#if CV_TRY_LASX +void resizeNN2_LASX(const Range&, const Mat&, Mat&, int*, double); +void resizeNN4_LASX(const Range&, const Mat&, Mat&, int*, double); +#endif +} + } #endif /* End of file. */ diff --git a/modules/imgproc/src/resize.lasx.cpp b/modules/imgproc/src/resize.lasx.cpp new file mode 100644 index 0000000000..fece47087d --- /dev/null +++ b/modules/imgproc/src/resize.lasx.cpp @@ -0,0 +1,249 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Copyright (C) 2014-2015, Itseez Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's 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. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and 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 the Intel Corporation or 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. +// +//M*/ + +/* //////////////////////////////////////////////////////////////////// +// +// Geometrical transforms on images and matrices: rotation, zoom etc. +// +// */ + +#include "precomp.hpp" +#include "resize.hpp" +#include "opencv2/core/hal/intrin.hpp" + +namespace cv +{ +namespace opt_LASX +{ + +class resizeNNInvokerLASX4 CV_FINAL : + public ParallelLoopBody +{ +public: + resizeNNInvokerLASX4(const Mat& _src, Mat &_dst, int *_x_ofs, double _ify) : + ParallelLoopBody(), src(_src), dst(_dst), x_ofs(_x_ofs), + ify(_ify) + { + } + + virtual void operator() (const Range& range) const CV_OVERRIDE + { + Size ssize = src.size(), dsize = dst.size(); + int y, x; + int width = dsize.width; + int avxWidth = width - (width & 0x7); + if(((int64)(dst.data + dst.step) & 0x1f) == 0) + { + for(y = range.start; y < range.end; y++) + { + uchar* D = dst.data + dst.step*y; + uchar* Dstart = D; + int sy = std::min(cvFloor(y*ify), ssize.height-1); + const uchar* S = src.data + sy*src.step; +#ifdef CV_ICC +#pragma unroll(4) +#endif + for(x = 0; x < avxWidth; x += 8) + { + const __m256i CV_DECL_ALIGNED(64) *addr = (__m256i*)(x_ofs + x); + __m256i CV_DECL_ALIGNED(64) pixels = v256_lut_quads((schar *)S, (int *)addr).val; + __lasx_xvst(pixels, (int*)D, 0); + D += 32; + } + for(; x < width; x++) + { + *(int*)(Dstart + x*4) = *(int*)(S + x_ofs[x]); + } + } + } + else + { + for(y = range.start; y < range.end; y++) + { + uchar* D = dst.data + dst.step*y; + uchar* Dstart = D; + int sy = std::min(cvFloor(y*ify), ssize.height-1); + const uchar* S = src.data + sy*src.step; +#ifdef CV_ICC +#pragma unroll(4) +#endif + for(x = 0; x < avxWidth; x += 8) + { + const __m256i CV_DECL_ALIGNED(64) *addr = (__m256i*)(x_ofs + x); + __m256i CV_DECL_ALIGNED(64) pixels = v256_lut_quads((schar *)S, (int *)addr).val; + __lasx_xvst(pixels, (int*)D, 0); + D += 32; + } + for(; x < width; x++) + { + *(int*)(Dstart + x*4) = *(int*)(S + x_ofs[x]); + } + } + } + } + +private: + const Mat& src; + Mat& dst; + int* x_ofs; + double ify; + + resizeNNInvokerLASX4(const resizeNNInvokerLASX4&); + resizeNNInvokerLASX4& operator=(const resizeNNInvokerLASX4&); +}; + +class resizeNNInvokerLASX2 CV_FINAL : + public ParallelLoopBody +{ +public: + resizeNNInvokerLASX2(const Mat& _src, Mat &_dst, int *_x_ofs, double _ify) : + ParallelLoopBody(), src(_src), dst(_dst), x_ofs(_x_ofs), + ify(_ify) + { + } + + virtual void operator() (const Range& range) const CV_OVERRIDE + { + Size ssize = src.size(), dsize = dst.size(); + int y, x; + int width = dsize.width; + int avxWidth = width - (width & 0xf); + const __m256i CV_DECL_ALIGNED(64) shuffle_mask = _v256_set_b(15,14,11,10,13,12,9,8,7,6,3,2,5,4,1,0, + 15,14,11,10,13,12,9,8,7,6,3,2,5,4,1,0); + const __m256i CV_DECL_ALIGNED(64) permute_mask = _v256_set_w(7, 5, 3, 1, 6, 4, 2, 0); + if(((int64)(dst.data + dst.step) & 0x1f) == 0) + { + for(y = range.start; y < range.end; y++) + { + uchar* D = dst.data + dst.step*y; + uchar* Dstart = D; + int sy = std::min(cvFloor(y*ify), ssize.height-1); + const uchar* S = src.data + sy*src.step; + const uchar* S2 = S - 2; +#ifdef CV_ICC +#pragma unroll(4) +#endif + for(x = 0; x < avxWidth; x += 16) + { + const __m256i CV_DECL_ALIGNED(64) *addr = (__m256i*)(x_ofs + x); + __m256i CV_DECL_ALIGNED(64) pixels1 = v256_lut_quads((schar *)S, (int *)addr).val; + + const __m256i CV_DECL_ALIGNED(64) *addr2 = (__m256i*)(x_ofs + x + 8); + __m256i CV_DECL_ALIGNED(64) pixels2 = v256_lut_quads((schar *)S2, (int *)addr2).val; + + const __m256i h_mask = __lasx_xvreplgr2vr_w(0xFFFF0000); + __m256i CV_DECL_ALIGNED(64) unpacked = __lasx_xvbitsel_v(pixels1, pixels2, h_mask); + + __m256i CV_DECL_ALIGNED(64) bytes_shuffled = __lasx_xvshuf_b(unpacked, unpacked, shuffle_mask); + __m256i CV_DECL_ALIGNED(64) ints_permuted = __lasx_xvperm_w(bytes_shuffled, permute_mask); + __lasx_xvst(ints_permuted, (int*)D, 0); + D += 32; + } + for(; x < width; x++) + { + *(ushort*)(Dstart + x*2) = *(ushort*)(S + x_ofs[x]); + } + + } + } + else + { + for(y = range.start; y < range.end; y++) + { + uchar* D = dst.data + dst.step*y; + uchar* Dstart = D; + int sy = std::min(cvFloor(y*ify), ssize.height-1); + const uchar* S = src.data + sy*src.step; + const uchar* S2 = S - 2; +#ifdef CV_ICC +#pragma unroll(4) +#endif + for(x = 0; x < avxWidth; x += 16) + { + const __m256i CV_DECL_ALIGNED(64) *addr = (__m256i*)(x_ofs + x); + __m256i CV_DECL_ALIGNED(64) pixels1 = v256_lut_quads((schar *)S, (int *)addr).val; + + const __m256i CV_DECL_ALIGNED(64) *addr2 = (__m256i*)(x_ofs + x + 8); + __m256i CV_DECL_ALIGNED(64) pixels2 = v256_lut_quads((schar *)S2, (int *)addr2).val; + + const __m256i h_mask = __lasx_xvreplgr2vr_w(0xFFFF0000); + __m256i CV_DECL_ALIGNED(64) unpacked = __lasx_xvbitsel_v(pixels1, pixels2, h_mask); + + __m256i CV_DECL_ALIGNED(64) bytes_shuffled = __lasx_xvshuf_b(unpacked, unpacked, shuffle_mask); + __m256i CV_DECL_ALIGNED(64) ints_permuted = __lasx_xvperm_w(bytes_shuffled, permute_mask); + __lasx_xvst(ints_permuted, (int*)D, 0); + D += 32; + } + for(; x < width; x++) + { + *(ushort*)(Dstart + x*2) = *(ushort*)(S + x_ofs[x]); + } + } + } + } + +private: + const Mat& src; + Mat& dst; + int* x_ofs; + double ify; + + resizeNNInvokerLASX2(const resizeNNInvokerLASX2&); + resizeNNInvokerLASX2& operator=(const resizeNNInvokerLASX2&); +}; + +void resizeNN2_LASX(const Range& range, const Mat& src, Mat &dst, int *x_ofs, double ify) +{ + resizeNNInvokerLASX2 invoker(src, dst, x_ofs, ify); + parallel_for_(range, invoker, dst.total() / (double)(1 << 16)); +} + +void resizeNN4_LASX(const Range& range, const Mat& src, Mat &dst, int *x_ofs, double ify) +{ + resizeNNInvokerLASX4 invoker(src, dst, x_ofs, ify); + parallel_for_(range, invoker, dst.total() / (double)(1 << 16)); +} + +} +} +/* End of file. */ diff --git a/modules/imgproc/src/smooth.dispatch.cpp b/modules/imgproc/src/smooth.dispatch.cpp index 95a0437812..3ab8501601 100644 --- a/modules/imgproc/src/smooth.dispatch.cpp +++ b/modules/imgproc/src/smooth.dispatch.cpp @@ -635,6 +635,9 @@ void GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, return; } + if (sigma2 <= 0) + sigma2 = sigma1; + bool useOpenCL = ocl::isOpenCLActivated() && _dst.isUMat() && _src.dims() <= 2 && _src.rows() >= ksize.height && _src.cols() >= ksize.width && ksize.width > 1 && ksize.height > 1; diff --git a/modules/imgproc/src/stackblur.cpp b/modules/imgproc/src/stackblur.cpp new file mode 100644 index 0000000000..5d60a1d365 --- /dev/null +++ b/modules/imgproc/src/stackblur.cpp @@ -0,0 +1,1259 @@ +// 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. + +/* +StackBlur - a fast almost Gaussian Blur +Theory: http://underdestruction.com/2004/02/25/stackblur-2004 +The code has been borrowed from (https://github.com/flozz/StackBlur) +and adapted for OpenCV by Zihao Mu. + +Below is the original copyright +*/ + +/* +Copyright (c) 2010 Mario Klingemann + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include "precomp.hpp" +#include "opencv2/core/hal/intrin.hpp" + +#include + +using namespace std; + +#define STACKBLUR_MAX_RADIUS 254 + +static unsigned short const stackblurMul[255] = + { + 512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, + 454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, + 482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, + 437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, + 497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, + 320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, + 446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, + 329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, + 505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, + 399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, + 324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, + 268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, + 451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, + 385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, + 332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, + 289,287,285,282,280,278,275,273,271,269,267,265,263,261,259 + }; + +static unsigned char const stackblurShr[255] = + { + 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 + }; + +namespace cv{ + +#if CV_SIMD +template +inline int opRow(const T* , T* , const std::vector& , const float , const int radius, const int CN, const int ) +{ + return radius * CN; +} + +template<> +inline int opRow(const uchar* srcPtr, uchar* dstPtr, const std::vector& kVec, const float , const int radius, const int CN, const int widthCN) +{ + int kernelSize = (int)kVec.size(); + + int i = radius * CN; + if (radius > STACKBLUR_MAX_RADIUS) + return i; + + const int mulValTab= stackblurMul[radius]; + const int shrValTab= stackblurShr[radius]; + + const int VEC_LINE = v_uint8::nlanes; + + if (kernelSize == 3) + { + v_uint32 v_mulVal = vx_setall_u32(mulValTab); + for (; i <= widthCN - VEC_LINE; i += VEC_LINE) + { + v_uint16 x0l, x0h, x1l, x1h, x2l, x2h; + v_expand(vx_load(srcPtr + i - CN), x0l, x0h); + v_expand(vx_load(srcPtr + i), x1l, x1h); + v_expand(vx_load(srcPtr + i + CN), x2l, x2h); + + x1l = v_add_wrap(v_add_wrap(x1l, x1l), v_add_wrap(x0l, x2l)); + x1h = v_add_wrap(v_add_wrap(x1h, x1h), v_add_wrap(x0h, x2h)); + + v_uint32 y00, y01, y10, y11; + v_expand(x1l, y00, y01); + v_expand(x1h, y10, y11); + + y00 = (y00 * v_mulVal)>>shrValTab; + y01 = (y01 * v_mulVal)>>shrValTab; + y10 = (y10 * v_mulVal)>>shrValTab; + y11 = (y11 * v_mulVal)>>shrValTab; + + v_store(dstPtr + i, v_pack(v_pack(y00, y01), v_pack(y10, y11))); + } + } + else + { + const ushort * kx = kVec.data() + kernelSize/2; + v_int32 v_mulVal = vx_setall_s32(mulValTab); + v_int16 k0 = vx_setall_s16((short)(kx[0])); + + srcPtr += i; + for( ; i <= widthCN - VEC_LINE; i += VEC_LINE, srcPtr += VEC_LINE) + { + v_uint8 v_src = vx_load(srcPtr); + v_int32 s0, s1, s2, s3; + v_mul_expand(v_reinterpret_as_s16(v_expand_low(v_src)), k0, s0, s1); + v_mul_expand(v_reinterpret_as_s16(v_expand_high(v_src)), k0, s2, s3); + + int k = 1, j = CN; + for (; k <= kernelSize / 2 - 1; k += 2, j += 2 * CN) + { + v_int16 k12 = v_reinterpret_as_s16(vx_setall_s32(((int)kx[k] & 0xFFFF) | ((int)kx[k + 1] << 16))); + + v_uint8 v_src0 = vx_load(srcPtr - j); + v_uint8 v_src1 = vx_load(srcPtr - j - CN); + v_uint8 v_src2 = vx_load(srcPtr + j); + v_uint8 v_src3 = vx_load(srcPtr + j + CN); + + v_int16 xl, xh; + v_zip(v_reinterpret_as_s16(v_expand_low(v_src0) + v_expand_low(v_src2)), v_reinterpret_as_s16(v_expand_low(v_src1) + v_expand_low(v_src3)), xl, xh); + s0 += v_dotprod(xl, k12); + s1 += v_dotprod(xh, k12); + v_zip(v_reinterpret_as_s16(v_expand_high(v_src0) + v_expand_high(v_src2)), v_reinterpret_as_s16(v_expand_high(v_src1) + v_expand_high(v_src3)), xl, xh); + s2 += v_dotprod(xl, k12); + s3 += v_dotprod(xh, k12); + } + if( k < kernelSize / 2 + 1 ) + { + v_int16 k1 = vx_setall_s16((short)(kx[k])); + + v_uint8 v_src0 = vx_load(srcPtr - j); + v_uint8 v_src1 = vx_load(srcPtr + j); + + v_int16 xl, xh; + v_zip(v_reinterpret_as_s16(v_expand_low(v_src0)), v_reinterpret_as_s16(v_expand_low(v_src1)), xl, xh); + s0 += v_dotprod(xl, k1); + s1 += v_dotprod(xh, k1); + v_zip(v_reinterpret_as_s16(v_expand_high(v_src0)), v_reinterpret_as_s16(v_expand_high(v_src1)), xl, xh); + s2 += v_dotprod(xl, k1); + s3 += v_dotprod(xh, k1); + } + + s0 = (s0 * v_mulVal)>>shrValTab; + s1 = (s1 * v_mulVal)>>shrValTab; + s2 = (s2 * v_mulVal)>>shrValTab; + s3 = (s3 * v_mulVal)>>shrValTab; + + v_store(dstPtr + i, v_pack(v_reinterpret_as_u16(v_pack(s0, s1)), v_reinterpret_as_u16(v_pack(s2, s3)))); + } + } + return i; +} + +template<> +inline int opRow(const ushort* srcPtr, ushort* dstPtr, const std::vector& kVec, const float , const int radius, const int CN, const int widthCN) +{ + int kernelSize = (int)kVec.size(); + + int i = radius * CN; + if (radius > STACKBLUR_MAX_RADIUS) + return i; + + const int mulValTab= stackblurMul[radius]; + const int shrValTab= stackblurShr[radius]; + + const int VEC_LINE = v_uint16::nlanes; + + v_uint32 v_mulVal = vx_setall_u32(mulValTab); + if (kernelSize == 3) + { + for (; i <= widthCN - VEC_LINE; i += VEC_LINE) + { + v_uint32 x0l, x0h, x1l, x1h, x2l, x2h; + v_expand(vx_load(srcPtr + i - CN), x0l, x0h); + v_expand(vx_load(srcPtr + i), x1l, x1h); + v_expand(vx_load(srcPtr + i + CN), x2l, x2h); + + x1l = v_add(v_add(x1l, x1l), v_add(x0l, x2l)); + x1h = v_add(v_add(x1h, x1h), v_add(x0h, x2h)); + + v_store(dstPtr + i, v_pack((x1l * v_mulVal)>>shrValTab, (x1h * v_mulVal)>>shrValTab)); + } + } + else + { + const ushort * kx = kVec.data() + kernelSize/2; + v_uint16 k0 = vx_setall_u16(kx[0]); + + srcPtr += i; + for( ; i <= widthCN - VEC_LINE; i += VEC_LINE, srcPtr += VEC_LINE) + { + v_uint16 v_src = vx_load(srcPtr); + v_uint32 s0, s1; + + v_mul_expand(v_src, k0, s0, s1); + + int k = 1, j = CN; + for (; k <= kernelSize / 2 - 1; k += 2, j += 2*CN) + { + v_uint16 k1 = vx_setall_u16(kx[k]); + v_uint16 k2 = vx_setall_u16(kx[k + 1]); + + v_uint32 y0, y1; + v_mul_expand(vx_load(srcPtr - j) + vx_load(srcPtr + j), k1, y0, y1); + s0 += y0; + s1 += y1; + v_mul_expand(vx_load(srcPtr - j - CN) + vx_load(srcPtr + j + CN), k2, y0, y1); + s0 += y0; + s1 += y1; + } + if( k < kernelSize / 2 + 1 ) + { + v_uint16 k1 = vx_setall_u16(kx[k]); + + v_uint32 y0, y1; + v_mul_expand(vx_load(srcPtr - j) + vx_load(srcPtr + j), k1, y0, y1); + s0 += y0; + s1 += y1; + } + + s0 = (s0 * v_mulVal)>>shrValTab; + s1 = (s1 * v_mulVal)>>shrValTab; + + v_store(dstPtr + i, v_pack(s0, s1)); + } + } + + return i; +} + +template<> +inline int opRow(const short* srcPtr, short* dstPtr, const std::vector& kVec, const float , const int radius, const int CN, const int widthCN) +{ + int kernelSize = (int)kVec.size(); + int i = radius * CN; + + if (radius > STACKBLUR_MAX_RADIUS) + return i; + + const int mulValTab= stackblurMul[radius]; + const int shrValTab= stackblurShr[radius]; + + const int VEC_LINE = v_int16::nlanes; + v_int32 v_mulVal = vx_setall_s32(mulValTab); + + if (kernelSize == 3) + { + for (; i <= widthCN - VEC_LINE; i += VEC_LINE) + { + v_int32 x0l, x0h, x1l, x1h, x2l, x2h; + v_expand(vx_load(srcPtr + i - CN), x0l, x0h); + v_expand(vx_load(srcPtr + i), x1l, x1h); + v_expand(vx_load(srcPtr + i + CN), x2l, x2h); + + x1l = v_add(v_add(x1l, x1l), v_add(x0l, x2l)); + x1h = v_add(v_add(x1h, x1h), v_add(x0h, x2h)); + + v_store(dstPtr + i, v_pack((x1l * v_mulVal)>>shrValTab, (x1h * v_mulVal)>>shrValTab)); + } + } + else + { + const ushort * kx = kVec.data() + kernelSize/2; + v_int16 k0 = vx_setall_s16((short)(kx[0])); + + srcPtr += i; + for( ; i <= widthCN - VEC_LINE; i += VEC_LINE, srcPtr += VEC_LINE) + { + v_int16 v_src = vx_load(srcPtr); + v_int32 s0, s1; + v_mul_expand(v_src, k0, s0, s1); + + int k = 1, j = CN; + for (; k <= kernelSize / 2 - 1; k += 2, j += 2 * CN) + { + v_int16 k1 = vx_setall_s16((short)kx[k]); + v_int16 k2 = vx_setall_s16((short)kx[k + 1]); + + v_int32 y0, y1; + + v_mul_expand(vx_load(srcPtr - j) + vx_load(srcPtr + j), k1, y0, y1); + s0 += y0; + s1 += y1; + v_mul_expand(vx_load(srcPtr - j - CN) + vx_load(srcPtr + j + CN), k2, y0, y1); + s0 += y0; + s1 += y1; + } + if( k < kernelSize / 2 + 1 ) + { + v_int16 k1 = vx_setall_s16((short)kx[k]); + v_int32 y0, y1; + v_mul_expand(vx_load(srcPtr - j) + vx_load(srcPtr + j), k1, y0, y1); + s0 += y0; + s1 += y1; + } + + s0 = (s0 * v_mulVal)>>shrValTab; + s1 = (s1 * v_mulVal)>>shrValTab; + + v_store(dstPtr + i, v_pack(s0, s1)); + } + } + return i; +} + +template<> +inline int opRow(const float* srcPtr, float* dstPtr, const std::vector& kVec, const float mulVal, const int radius, const int CN, const int widthCN) +{ + int kernelSize = (int)kVec.size(); + int i = radius * CN; + + v_float32 v_mulVal = vx_setall_f32(mulVal); + const int VEC_LINE = v_float32::nlanes; + const int VEC_LINE4 = VEC_LINE * 4; + + if (kernelSize == 3) + { + for (; i <= widthCN - VEC_LINE4; i += VEC_LINE4) + { + v_float32 v_srcPtr0 = vx_load(srcPtr + i); + v_float32 v_srcPtr1 = vx_load(srcPtr + VEC_LINE + i) ; + v_float32 v_srcPtr2 = vx_load(srcPtr + VEC_LINE * 2 + i); + v_float32 v_srcPtr3 = vx_load(srcPtr + VEC_LINE * 3 + i); + + v_float32 v_sumVal0 = v_srcPtr0 + v_srcPtr0 + vx_load(srcPtr + i - CN) + vx_load(srcPtr + i + CN); + v_float32 v_sumVal1 = v_srcPtr1 + v_srcPtr1 + vx_load(srcPtr + VEC_LINE + i - CN) + vx_load(srcPtr + VEC_LINE + i + CN); + v_float32 v_sumVal2 = v_srcPtr2 + v_srcPtr2 + vx_load(srcPtr + VEC_LINE * 2 + i - CN) + vx_load(srcPtr + VEC_LINE * 2 + i + CN); + v_float32 v_sumVal3 = v_srcPtr3 + v_srcPtr3 + vx_load(srcPtr + VEC_LINE * 3 + i - CN) + vx_load(srcPtr + VEC_LINE * 3 + i + CN); + + v_store(dstPtr + i, v_sumVal0 * v_mulVal); + v_store(dstPtr + i + VEC_LINE, v_sumVal1 * v_mulVal); + v_store(dstPtr + i + VEC_LINE * 2, v_sumVal2 * v_mulVal); + v_store(dstPtr + i + VEC_LINE * 3, v_sumVal3 * v_mulVal); + } + + for (; i <= widthCN - VEC_LINE; i += VEC_LINE) + { + v_float32 v_srcPtr = vx_load(srcPtr + i); + v_float32 v_sumVal = v_srcPtr + v_srcPtr + vx_load(srcPtr + i - CN) + vx_load(srcPtr + i + CN); + v_store(dstPtr + i, v_sumVal * v_mulVal); + } + } + else + { + const ushort * kx = kVec.data() + kernelSize/2; + v_float32 k0 = vx_setall_f32((float)(kx[0])); + + srcPtr += i; + for( ; i <= widthCN - VEC_LINE; i += VEC_LINE, srcPtr += VEC_LINE) + { + v_float32 v_src = vx_load(srcPtr); + v_float32 s0; + s0 = v_src * k0; + + int k = 1, j = CN; + for (; k <= kernelSize / 2 - 1; k += 2, j += 2 * CN) + { + v_float32 k1 = vx_setall_f32((float)kx[k]); + v_float32 k2 = vx_setall_f32((float)kx[k + 1]); + + s0 += (vx_load(srcPtr - j) + vx_load(srcPtr + j)) * k1; + s0 += (vx_load(srcPtr - j - CN) + vx_load(srcPtr + j + CN)) * k2; + } + if( k < kernelSize / 2 + 1 ) + { + v_float32 k1 = vx_setall_f32((float)kx[k]); + + s0 += (vx_load(srcPtr - j) + vx_load(srcPtr + j)) * k1; + } + + v_store(dstPtr + i, s0 * v_mulVal); + } + } + return i; +} + +template +inline int opComputeDiff(const T*& , TBuf*& , const int , const int) +{ + return 0; +} + +template<> +inline int opComputeDiff(const uchar*& srcPtr, int*& diff0, const int w, const int CNR1) +{ + int index = 0; + const int VEC_LINE_8 = v_uint8::nlanes; + const int VEC_LINE_32 = v_int32::nlanes; + for (; index <= w - VEC_LINE_8; index += VEC_LINE_8, diff0+=VEC_LINE_8, srcPtr+=VEC_LINE_8) + { + v_uint16 x0l, x0h, x1l, x1h; + v_expand(vx_load(srcPtr + CNR1), x0l, x0h); + v_expand(vx_load(srcPtr), x1l, x1h); + + v_int32 y0, y1, y2, y3; + v_expand(v_reinterpret_as_s16(x0l) - v_reinterpret_as_s16(x1l), y0, y1); + v_expand(v_reinterpret_as_s16(x0h) - v_reinterpret_as_s16(x1h), y2, y3); + + v_store(diff0, y0); + v_store(diff0 + VEC_LINE_32, y1); + v_store(diff0 + VEC_LINE_32 * 2, y2); + v_store(diff0 + VEC_LINE_32 * 3, y3); + } + return index; +} +#endif + +template +class ParallelStackBlurRow : public ParallelLoopBody +{ +public: + ParallelStackBlurRow (const Mat &_src, Mat &_dst, int _radius): src(_src), dst(_dst) ,radius(_radius) + { + width= dst.cols; + wm = width - 1; + mulVal = 1.0f / ((radius + 1) * (radius + 1)); + CN = src.channels(); + } + + ~ParallelStackBlurRow() {} + + /* + * The idea is as follows: + * The stack can be understood as a sliding window of length kernel size. + * The sliding window moves one element at a time from left to right. + * The sumIn stores the elements added to the stack each time, + * and sumOut stores the subtracted elements. Every time stack moves, stack, sumIn and sumOut are updated. + * The dst will be calculated using the following formula: + * dst[i] = (stack + sumIn - sumOut) / stack_num + * In the Row direction, in order to avoid redundant computation, + * we save the sumIn - sumOut as a diff vector. + * So the new formula is: + * dst[i] = (stack + diff[i]) / stack_num. + * In practice, we use multiplication and bit shift right to simulate integer division: + * dst[i] = ((stack + diff[i]) * mulVal) >> shrVal. + * */ + virtual void operator ()(const Range& range) const CV_OVERRIDE + { + const int kernelSize = 2 * radius + 1; + + if (kernelSize <= 9 && width > kernelSize) // Special branch for small kernel + { + std::vector kVec; + for (int i = 0; i < kernelSize; i++) + { + if (i <= radius) + kVec.push_back(ushort(i + 1)); + else + kVec.push_back(ushort(2 * radius - i + 1)); + } + + const ushort * kx = kVec.data() + kernelSize/2; + for (int row = range.start; row < range.end; row++) + { + const T* srcPtr = src.ptr(row); + T* dstPtr = dst.ptr(row); + TBuf sumVal; + + // init + for (int i = 0; i < radius; i++) + { + for (int ci = 0; ci < CN; ci++) + { + sumVal = 0; + for (int k = 0; k < kernelSize; k++) + { + int index = std::max(k - radius + i, 0); + sumVal += (TBuf)srcPtr[index * CN + ci] * (TBuf)kVec[k]; + } + dstPtr[i*CN + ci] = (T)(sumVal * mulVal); + } + } + + int widthCN = (width - radius) * CN; + + // middle + int wc = radius * CN; +#if CV_SIMD + wc = opRow(srcPtr, dstPtr, kVec, mulVal, radius, CN, widthCN); +#endif + for (; wc < widthCN; wc++) + { + sumVal = srcPtr[wc] * kx[0]; + for (int k = 1; k <= radius; k++) + sumVal += ((TBuf)(srcPtr[wc + k * CN])+(TBuf)(srcPtr[wc - k * CN])) * (TBuf)kx[k]; + dstPtr[wc] = (T)(sumVal * mulVal); + } + + // tail + for (int i = wc / CN; i < width; i++) + { + for (int ci = 0; ci < CN; ci++) + { + sumVal = 0; + for (int k = 0; k < kernelSize; k++) + { + int index = std::min(k - radius + i, wm); + sumVal += (TBuf)srcPtr[index * CN + ci] * (TBuf)kVec[k]; + } + dstPtr[i*CN + ci] = (T)(sumVal * mulVal); + } + } + + } + } + else + { + size_t bufSize = CN * (width + radius) * sizeof(TBuf) + 2 * CN * sizeof(TBuf); + AutoBuffer _buf(bufSize + 16); + uchar* bufptr = alignPtr(_buf.data(), 16); + TBuf* diffVal = (TBuf*)bufptr; + TBuf* sum = diffVal+CN; + TBuf* diff = sum + CN; + + const int CNR1 = CN * (radius + 1); + const int widthCN = (width - radius - 1) * CN; + + for (int row = range.start; row < range.end; row++) + { + memset(bufptr, 0, bufSize); + + const T* srcPtr = src.ptr(row); + T* dstPtr = dst.ptr(row); + + int radiusMul = (radius + 2) * (radius + 1) / 2; + for (int ci = 0; ci < CN; ci++) + sum[ci] += (TBuf)srcPtr[ci] * radiusMul; + + // compute diff + const T* srcPtr0 = srcPtr; + + // init + for (int i = 0; i < radius; i++) + { + if (i < wm) srcPtr0 += CN; + for (int ci = 0; ci < CN; ci++) + { + diff[i*CN + ci] = (TBuf)srcPtr0[ci] - (TBuf)srcPtr[ci]; + diffVal[ci] += diff[i*CN + ci]; + sum[ci] += srcPtr0[ci] * (radius - i); + } + } + + // middle + auto diff0 = diff + radius * CN; + int index = 0; +#if CV_SIMD + index = opComputeDiff(srcPtr, diff0, widthCN, CNR1); +#endif + + for (; index < widthCN; index++, diff0++, srcPtr++) + diff0[0] = (TBuf)(srcPtr[CNR1]) - (TBuf)(srcPtr[0]); + + // tails + srcPtr0 = src.ptr(row) + index; + const T* srcPtr1 = src.ptr(row) + (width - 1) * CN; + int dist = width - index/CN; + for (int r = 0; r < radius; r++, diff0 += CN) + { + for (int ci = 0; ci < CN; ci++) + diff0[ci] = (TBuf)(srcPtr1[ci]) - (TBuf)(srcPtr0[ci]); + + if (dist >= r) + { + srcPtr0 += CN; + dist--; + } + } + + srcPtr = src.ptr(row); + diff0 = diff + radius * CN; + for (int ci = 0; ci < CN; ci++) + diffVal[ci] += diff0[ci]; + diff0 += CN; + + if (CN == 1) + { + for (int i = 0; i < width; i++, diff0 ++, dstPtr ++, srcPtr ++) + { + *(dstPtr) = saturate_cast((sum[0] * mulVal)); + sum[0] += diffVal[0]; + diffVal[0] += (diff0[0] - diff0[-CNR1]); + } + } + else if (CN == 3) + { + for (int i = 0; i < width; i++, diff0 += CN, dstPtr += CN, srcPtr += CN) + { + *(dstPtr + 0) = saturate_cast((sum[0] * mulVal)); + *(dstPtr + 1) = saturate_cast((sum[1] * mulVal)); + *(dstPtr + 2) = saturate_cast((sum[2] * mulVal)); + + sum[0] += diffVal[0]; + sum[1] += diffVal[1]; + sum[2] += diffVal[2]; + + diffVal[0] += (diff0[0] - diff0[0 - CNR1]); + diffVal[1] += (diff0[1] - diff0[1 - CNR1]); + diffVal[2] += (diff0[2] - diff0[2 - CNR1]); + } + } + else if (CN == 4) + { + for (int i = 0; i < width; i++, diff0 += CN, dstPtr += CN, srcPtr += CN) + { + *(dstPtr + 0) = saturate_cast((sum[0] * mulVal)); + *(dstPtr + 1) = saturate_cast((sum[1] * mulVal)); + *(dstPtr + 2) = saturate_cast((sum[2] * mulVal)); + *(dstPtr + 3) = saturate_cast((sum[3] * mulVal)); + + sum[0] += diffVal[0]; + sum[1] += diffVal[1]; + sum[2] += diffVal[2]; + sum[3] += diffVal[3]; + + diffVal[0] += (diff0[0] - diff0[0 - CNR1]); + diffVal[1] += (diff0[1] - diff0[1 - CNR1]); + diffVal[2] += (diff0[2] - diff0[2 - CNR1]); + diffVal[3] += (diff0[3] - diff0[3 - CNR1]); + } + } + else + { + int i = 0; + for (; i < width; i++, diff0 += CN, dstPtr += CN, srcPtr += CN) + { + for (int ci = 0; ci < CN; ci++) + { + *(dstPtr + ci) = saturate_cast((sum[ci] * mulVal)); + sum[ci] += diffVal[ci]; + diffVal[ci] += (diff0[ci] - diff0[ci - CNR1]); + } + } + } + } + } + } + +private: + const Mat &src; + Mat &dst; + int radius; + int width; + int wm; + int CN; + float mulVal; +}; + +#if CV_SIMD +template +inline int opColumn(const T* , T* , T* , TBuf* , TBuf* , TBuf* , const float , + const int , const int , const int , const int , const int ) +{ + return 0; +} + +template<> +inline int opColumn(const float* srcPtr, float* dstPtr, float* stack, float* sum, float* sumIn, + float* sumOut, const float mulVal, const int , const int , + const int widthLen, const int ss, const int sp1) +{ + int k = 0; + v_float32 v_mulVal = vx_setall_f32(mulVal); + const int VEC_LINE = v_float32::nlanes; + const int VEC_LINE4 = 4 * VEC_LINE; + + auto stackStartPtr = stack + ss * widthLen; + auto stackSp1Ptr = stack + sp1 * widthLen; + + for (;k <= widthLen - VEC_LINE4; k += VEC_LINE4) + { + v_float32 v_sum0 = vx_load(sum + k); + v_float32 v_sum1 = vx_load(sum + VEC_LINE + k); + v_float32 v_sum2 = vx_load(sum + VEC_LINE * 2 + k); + v_float32 v_sum3 = vx_load(sum + VEC_LINE * 3 + k); + + v_float32 v_sumOut0 = vx_load(sumOut + k); + v_float32 v_sumOut1 = vx_load(sumOut + VEC_LINE + k); + v_float32 v_sumOut2 = vx_load(sumOut + VEC_LINE * 2 + k); + v_float32 v_sumOut3 = vx_load(sumOut + VEC_LINE * 3 + k); + + v_float32 v_sumIn0 = vx_load(sumIn + k); + v_float32 v_sumIn1 = vx_load(sumIn + VEC_LINE + k); + v_float32 v_sumIn2 = vx_load(sumIn + VEC_LINE * 2 + k); + v_float32 v_sumIn3 = vx_load(sumIn + VEC_LINE * 3+ k); + + v_store(dstPtr + k, v_sum0 * v_mulVal); + v_store(dstPtr + VEC_LINE + k, v_sum1 * v_mulVal); + v_store(dstPtr + VEC_LINE * 2 + k, v_sum2 * v_mulVal); + v_store(dstPtr + VEC_LINE * 3 + k, v_sum3 * v_mulVal); + + v_sum0 -= v_sumOut0; + v_sum1 -= v_sumOut1; + v_sum2 -= v_sumOut2; + v_sum3 -= v_sumOut3; + + v_sumOut0 -= vx_load(stackStartPtr + k); + v_sumOut1 -= vx_load(stackStartPtr + VEC_LINE + k); + v_sumOut2 -= vx_load(stackStartPtr + VEC_LINE * 2 + k); + v_sumOut3 -= vx_load(stackStartPtr + VEC_LINE * 3 + k); + + v_float32 v_srcPtr0 = vx_load(srcPtr + k); + v_float32 v_srcPtr1 = vx_load(srcPtr + VEC_LINE + k); + v_float32 v_srcPtr2 = vx_load(srcPtr + VEC_LINE * 2 + k); + v_float32 v_srcPtr3 = vx_load(srcPtr + VEC_LINE * 3 + k); + + v_store(stackStartPtr + k, v_srcPtr0); + v_store(stackStartPtr + VEC_LINE + k, v_srcPtr1); + v_store(stackStartPtr + VEC_LINE * 2 + k, v_srcPtr2); + v_store(stackStartPtr + VEC_LINE * 3 + k, v_srcPtr3); + + v_sumIn0 += v_srcPtr0; + v_sumIn1 += v_srcPtr1; + v_sumIn2 += v_srcPtr2; + v_sumIn3 += v_srcPtr3; + + v_store(sum + k, v_sum0 + v_sumIn0); + v_store(sum + VEC_LINE + k, v_sum1 + v_sumIn1); + v_store(sum + VEC_LINE * 2 + k, v_sum2 + v_sumIn2); + v_store(sum + VEC_LINE * 3 + k, v_sum3 + v_sumIn3); + + v_srcPtr0 = vx_load(stackSp1Ptr + k); + v_srcPtr1 = vx_load(stackSp1Ptr + VEC_LINE + k); + v_srcPtr2 = vx_load(stackSp1Ptr + VEC_LINE * 2 + k); + v_srcPtr3 = vx_load(stackSp1Ptr + VEC_LINE * 3 + k); + + v_sumOut0 += v_srcPtr0; + v_sumOut1 += v_srcPtr1; + v_sumOut2 += v_srcPtr2; + v_sumOut3 += v_srcPtr3; + + v_store(sumOut + k, v_sumOut0); + v_store(sumOut + VEC_LINE + k, v_sumOut1); + v_store(sumOut + VEC_LINE * 2 + k, v_sumOut2); + v_store(sumOut + VEC_LINE * 3 + k, v_sumOut3); + + v_sumIn0 -= v_srcPtr0; + v_sumIn1 -= v_srcPtr1; + v_sumIn2 -= v_srcPtr2; + v_sumIn3 -= v_srcPtr3; + + v_store(sumIn + k, v_sumIn0); + v_store(sumIn + VEC_LINE + k, v_sumIn1); + v_store(sumIn + VEC_LINE * 2 + k, v_sumIn2); + v_store(sumIn + VEC_LINE * 3 + k, v_sumIn3); + } + + for (;k <= widthLen - VEC_LINE; k += VEC_LINE) + { + v_float32 v_sum = vx_load(sum + k); + v_float32 v_sumOut = vx_load(sumOut + k); + v_float32 v_sumIn = vx_load(sumIn + k); + + v_store(dstPtr + k, v_sum * v_mulVal); + v_sum -= v_sumOut; + v_sumOut -= vx_load(stackStartPtr + k); + + v_float32 v_srcPtr = vx_load(srcPtr + k); + v_store(stackStartPtr + k, v_srcPtr); + + v_sumIn += v_srcPtr; + v_store(sum + k, v_sum + v_sumIn); + + v_srcPtr = vx_load(stackSp1Ptr + k); + v_sumOut += v_srcPtr; + v_store(sumOut + k, v_sumOut); + v_sumIn -= v_srcPtr; + v_store(sumIn + k, v_sumIn); + } + return k; +} + +template<> +inline int opColumn(const uchar* srcPtr, uchar* dstPtr, uchar* stack, int* sum, int* sumIn, + int* sumOut, const float , const int mulValTab, const int shrValTab, + const int widthLen, const int ss, const int sp1) +{ + int k = 0; + if (mulValTab != 0 && shrValTab != 0) + { + const int VEC_LINE_8 = v_uint8::nlanes; + const int VEC_LINE_32 = v_int32::nlanes; + v_int32 v_mulVal = vx_setall_s32(mulValTab); + + auto stackStartPtr = stack + ss * widthLen; + auto stackSp1Ptr = stack + sp1 * widthLen; + + for (;k <= widthLen - VEC_LINE_8; k += VEC_LINE_8) + { + v_int32 v_sum0, v_sum1, v_sum2, v_sum3; + v_int32 v_sumIn0, v_sumIn1, v_sumIn2, v_sumIn3; + v_int32 v_sumOut0, v_sumOut1, v_sumOut2, v_sumOut3; + + v_sum0 = vx_load(sum + k); + v_sum1 = vx_load(sum + k + VEC_LINE_32); + v_sum2 = vx_load(sum + k + VEC_LINE_32 * 2); + v_sum3 = vx_load(sum + k + VEC_LINE_32 * 3); + + v_sumIn0 = vx_load(sumIn + k); + v_sumIn1 = vx_load(sumIn + k + VEC_LINE_32); + v_sumIn2 = vx_load(sumIn + k + VEC_LINE_32 * 2); + v_sumIn3 = vx_load(sumIn + k + VEC_LINE_32 * 3); + + v_sumOut0 = vx_load(sumOut + k); + v_sumOut1 = vx_load(sumOut + k + VEC_LINE_32); + v_sumOut2 = vx_load(sumOut + k + VEC_LINE_32 * 2); + v_sumOut3 = vx_load(sumOut + k + VEC_LINE_32 * 3); + + v_store(dstPtr + k, + v_pack( + v_reinterpret_as_u16(v_pack((v_sum0 * v_mulVal)>>shrValTab, (v_sum1 * v_mulVal)>>shrValTab)), + v_reinterpret_as_u16(v_pack((v_sum2 * v_mulVal)>>shrValTab, (v_sum3 * v_mulVal)>>shrValTab)))); + + v_sum0 -= v_sumOut0; + v_sum1 -= v_sumOut1; + v_sum2 -= v_sumOut2; + v_sum3 -= v_sumOut3; + + v_uint16 x0l, x0h; + v_int32 v_ss0, v_ss1, v_ss2, v_ss3; + + v_expand(vx_load(stackStartPtr + k), x0l, x0h); + v_expand(v_reinterpret_as_s16(x0l), v_ss0, v_ss1); + v_expand(v_reinterpret_as_s16(x0h), v_ss2, v_ss3); + + v_sumOut0 -= v_ss0; + v_sumOut1 -= v_ss1; + v_sumOut2 -= v_ss2; + v_sumOut3 -= v_ss3; + + v_expand(vx_load(srcPtr + k), x0l, x0h); + v_expand(v_reinterpret_as_s16(x0l), v_ss0, v_ss1); + v_expand(v_reinterpret_as_s16(x0h), v_ss2, v_ss3); + + memcpy(stackStartPtr + k,srcPtr + k, VEC_LINE_8 * sizeof (uchar)); + + v_sumIn0 += v_ss0; + v_sumIn1 += v_ss1; + v_sumIn2 += v_ss2; + v_sumIn3 += v_ss3; + + v_store(sum + k, v_sum0 + v_sumIn0); + v_store(sum + VEC_LINE_32 + k, v_sum1 + v_sumIn1); + v_store(sum + VEC_LINE_32 * 2 + k, v_sum2 + v_sumIn2); + v_store(sum + VEC_LINE_32 * 3 + k, v_sum3 + v_sumIn3); + + v_expand(vx_load(stackSp1Ptr + k), x0l, x0h); + v_expand(v_reinterpret_as_s16(x0l), v_ss0, v_ss1); + v_expand(v_reinterpret_as_s16(x0h), v_ss2, v_ss3); + + v_sumOut0 += v_ss0; + v_sumOut1 += v_ss1; + v_sumOut2 += v_ss2; + v_sumOut3 += v_ss3; + + v_store(sumOut + k, v_sumOut0); + v_store(sumOut + VEC_LINE_32 + k, v_sumOut1); + v_store(sumOut + VEC_LINE_32 * 2 + k, v_sumOut2); + v_store(sumOut + VEC_LINE_32 * 3 + k, v_sumOut3); + + v_sumIn0 -= v_ss0; + v_sumIn1 -= v_ss1; + v_sumIn2 -= v_ss2; + v_sumIn3 -= v_ss3; + + v_store(sumIn + k, v_sumIn0); + v_store(sumIn + VEC_LINE_32 + k, v_sumIn1); + v_store(sumIn + VEC_LINE_32 * 2 + k, v_sumIn2); + v_store(sumIn + VEC_LINE_32 * 3 + k, v_sumIn3); + } + } + return k; +} + +template<> +inline int opColumn(const short* srcPtr, short* dstPtr, short* stack, int* sum, int* sumIn, + int* sumOut, const float , const int mulValTab, const int shrValTab, + const int widthLen, const int ss, const int sp1) +{ + int k = 0; + if (mulValTab != 0 && shrValTab != 0) + { + const int VEC_LINE_16 = v_int16::nlanes; + const int VEC_LINE_32 = v_int32::nlanes; + v_int32 v_mulVal = vx_setall_s32(mulValTab); + + auto stackStartPtr = stack + ss * widthLen; + auto stackSp1Ptr = stack + sp1 * widthLen; + for (;k <= widthLen - VEC_LINE_16; k += VEC_LINE_16) + { + v_int32 v_sum0, v_sum1; + v_int32 v_sumIn0, v_sumIn1; + v_int32 v_sumOut0, v_sumOut1; + + v_sum0 = vx_load(sum + k); + v_sum1 = vx_load(sum + k + VEC_LINE_32); + + v_sumIn0 = vx_load(sumIn + k); + v_sumIn1 = vx_load(sumIn + k + VEC_LINE_32); + + v_sumOut0 = vx_load(sumOut + k); + v_sumOut1 = vx_load(sumOut + k + VEC_LINE_32); + + v_store(dstPtr + k,v_pack((v_sum0 * v_mulVal)>>shrValTab, (v_sum1 * v_mulVal)>>shrValTab)); + + v_sum0 -= v_sumOut0; + v_sum1 -= v_sumOut1; + + v_int32 v_ss0, v_ss1; + v_expand(vx_load(stackStartPtr + k), v_ss0, v_ss1); + + v_sumOut0 -= v_ss0; + v_sumOut1 -= v_ss1; + + v_expand(vx_load(srcPtr + k), v_ss0, v_ss1); + memcpy(stackStartPtr + k,srcPtr + k, VEC_LINE_16 * sizeof (short)); + + v_sumIn0 += v_ss0; + v_sumIn1 += v_ss1; + + v_sum0 += v_sumIn0; + v_sum1 += v_sumIn1; + + v_store(sum + k, v_sum0); + v_store(sum + VEC_LINE_32 + k, v_sum1); + + v_expand(vx_load(stackSp1Ptr + k), v_ss0, v_ss1); + + v_sumOut0 += v_ss0; + v_sumOut1 += v_ss1; + + v_store(sumOut + k, v_sumOut0); + v_store(sumOut + VEC_LINE_32 + k, v_sumOut1); + + v_sumIn0 -= v_ss0; + v_sumIn1 -= v_ss1; + + v_store(sumIn + k, v_sumIn0); + v_store(sumIn + VEC_LINE_32 + k, v_sumIn1); + } + } + return k; +} + +template<> +inline int opColumn(const ushort* srcPtr, ushort* dstPtr, ushort* stack, int* sum, int* sumIn, + int* sumOut, const float , const int mulValTab, const int shrValTab, + const int widthLen, const int ss, const int sp1) +{ + int k = 0; + if (mulValTab != 0 && shrValTab != 0) + { + const int VEC_LINE_16 = v_uint16::nlanes; + const int VEC_LINE_32 = v_int32::nlanes; + v_uint32 v_mulVal = vx_setall_u32((uint32_t)mulValTab); + + auto stackStartPtr = stack + ss * widthLen; + auto stackSp1Ptr = stack + sp1 * widthLen; + for (;k <= widthLen - VEC_LINE_16; k += VEC_LINE_16) + { + v_int32 v_sum0, v_sum1; + v_int32 v_sumIn0, v_sumIn1; + v_int32 v_sumOut0, v_sumOut1; + + v_sum0 = vx_load(sum + k); + v_sum1 = vx_load(sum + k + VEC_LINE_32); + + v_sumIn0 = vx_load(sumIn + k); + v_sumIn1 = vx_load(sumIn + k + VEC_LINE_32); + + v_sumOut0 = vx_load(sumOut + k); + v_sumOut1 = vx_load(sumOut + k + VEC_LINE_32); + + v_store(dstPtr + k, v_pack((v_reinterpret_as_u32(v_sum0) * v_mulVal)>>shrValTab, (v_reinterpret_as_u32(v_sum1) * v_mulVal)>>shrValTab)); + + v_sum0 -= v_sumOut0; + v_sum1 -= v_sumOut1; + + v_uint32 v_ss0, v_ss1; + v_expand(vx_load(stackStartPtr + k), v_ss0, v_ss1); + + v_sumOut0 -= v_reinterpret_as_s32(v_ss0); + v_sumOut1 -= v_reinterpret_as_s32(v_ss1); + + v_expand(vx_load(srcPtr + k), v_ss0, v_ss1); + + memcpy(stackStartPtr + k,srcPtr + k, VEC_LINE_16 * sizeof (ushort)); + + v_sumIn0 += v_reinterpret_as_s32(v_ss0); + v_sumIn1 += v_reinterpret_as_s32(v_ss1); + + v_sum0 += v_sumIn0; + v_sum1 += v_sumIn1; + + v_store(sum + k, v_sum0); + v_store(sum + VEC_LINE_32 + k, v_sum1); + + v_expand(vx_load(stackSp1Ptr + k), v_ss0, v_ss1); + + v_sumOut0 += v_reinterpret_as_s32(v_ss0); + v_sumOut1 += v_reinterpret_as_s32(v_ss1); + + v_store(sumOut + k, v_sumOut0); + v_store(sumOut + VEC_LINE_32 + k, v_sumOut1); + + v_sumIn0 -= v_reinterpret_as_s32(v_ss0); + v_sumIn1 -= v_reinterpret_as_s32(v_ss1); + + v_store(sumIn + k, v_sumIn0); + v_store(sumIn + VEC_LINE_32 + k, v_sumIn1); + } + } + return k; +} +#endif + +template +class ParallelStackBlurColumn: + public ParallelLoopBody +{ +public: + ParallelStackBlurColumn (const Mat & _src, Mat &_dst, int _radius):src(_src), dst(_dst) ,radius(_radius) + { + CN = src.channels(); + widthElem = CN * src.cols; + height = src.rows; + hm = src.rows - 1; + mulVal = 1.0f / ((radius + 1)*(radius + 1)); + if (radius <= STACKBLUR_MAX_RADIUS) + { + shrValTab = stackblurShr[radius]; + mulValTab = stackblurMul[radius]; + } + else + { + shrValTab = 0; + mulValTab = 0; + } + } + + ~ParallelStackBlurColumn() {} + + virtual void operator ()(const Range& range) const CV_OVERRIDE + { + if (radius == 0) + return; + + const int kernelSize = 2 * radius + 1; + int widthImg = std::min(range.end, src.cols * CN); + int widthLen = widthImg - range.start; + + size_t bufSize = 3 * widthLen * sizeof(TBuf) + kernelSize * widthLen * sizeof(T); + + AutoBuffer _buf(bufSize + 16); + uchar* bufptr = alignPtr(_buf.data(), 16); + + TBuf* sum = (TBuf *)bufptr; + TBuf* sumIn = sum + widthLen; + TBuf* sumOut = sumIn + widthLen; + T* stack = (T* )(sumOut + widthLen); + + memset(bufptr, 0, bufSize); + + const T* srcPtr =dst.ptr() + range.start; + + for (int i = 0; i <= radius; i++) + { + for (int k = 0; k < widthLen; k++) + { + stack[i * widthLen + k] = *(srcPtr + k); + sum[k] += *(srcPtr + k) * (i + 1); + sumOut[k] += *(srcPtr + k); + } + } + + for (int i = 1; i <= radius; i++) + { + if (i <= hm) srcPtr += widthElem; + for (int k = 0; k < widthLen; k++) + { + T tmp = *(srcPtr + k); + stack[(i + radius) * widthLen + k] = tmp; + sum[k] += tmp * (radius - i + 1); + sumIn[k] += tmp; + } + } + + int sp = radius; + int yp = radius; + + if (yp > hm) yp = hm; + + T* dstPtr = dst.ptr() + range.start; + srcPtr = dst.ptr(yp) + range.start; + int stackStart = 0; + + for(int i = 0; i < height; i++) + { + stackStart = sp + kernelSize - radius; + if (stackStart >= kernelSize) stackStart -= kernelSize; + + int sp1 = sp + 1; + if (sp1 >= kernelSize) + sp1 = 0; + + if (yp < hm) + { + yp++; + srcPtr += widthElem; + } + + int k = 0; +#if CV_SIMD + k = opColumn(srcPtr, dstPtr, stack, sum, sumIn, sumOut, mulVal, mulValTab, shrValTab, + widthLen, stackStart, sp1); +#endif + + for (; k < widthLen; k++) + { + *(dstPtr + k) = static_cast(sum[k] * mulVal); + sum[k] -= sumOut[k]; + sumOut[k] -= stack[stackStart * widthLen + k]; + + stack[stackStart * widthLen + k] = *(srcPtr + k); + sumIn[k] += *(srcPtr + k); + sum[k] += sumIn[k]; + + sumOut[k] += stack[sp1 * widthLen + k]; + sumIn[k] -= stack[sp1 * widthLen + k]; + } + + dstPtr += widthElem; + ++sp; + if (sp >= kernelSize) + sp = 0; + } + } + +private: + const Mat &src; + Mat &dst; + int radius; + int CN; + int height; + int widthElem; + int hm; + float mulVal; + int mulValTab; + int shrValTab; +}; + +void stackBlur(InputArray _src, OutputArray _dst, Size ksize) +{ + CV_INSTRUMENT_REGION(); + CV_Assert(!_src.empty()); + + CV_Assert( ksize.width > 0 && ksize.width % 2 == 1 && + ksize.height > 0 && ksize.height % 2 == 1 ); + + int radiusH = ksize.height / 2; + int radiusW = ksize.width / 2; + + int stype = _src.type(), sdepth = _src.depth(); + Mat src = _src.getMat(); + + if (ksize.width == 1) + { + _src.copyTo(_dst); + + if (ksize.height == 1) + return; + } + else + { + _dst.create( src.size(), stype); + } + + Mat dst = _dst.getMat(); + int numOfThreads = getNumThreads(); + int widthElem = src.cols * src.channels(); + + if (dst.rows / numOfThreads < 3) + numOfThreads = std::max(1, dst.rows / 3); + + if (sdepth == CV_8U) + { + if (ksize.width != 1) + parallel_for_(Range(0, src.rows), ParallelStackBlurRow(src, dst, radiusW), numOfThreads); + if (ksize.height != 1) + parallel_for_(Range(0, widthElem), ParallelStackBlurColumn(dst, dst, radiusH), numOfThreads); + } + else if (sdepth == CV_16S) + { + if (ksize.width != 1) + parallel_for_(Range(0, src.rows), ParallelStackBlurRow(src, dst, radiusW), numOfThreads); + if (ksize.height != 1) + parallel_for_(Range(0, widthElem), ParallelStackBlurColumn(dst, dst, radiusH), numOfThreads); + } + else if (sdepth == CV_16U) + { + if (ksize.width != 1) + parallel_for_(Range(0, src.rows), ParallelStackBlurRow(src, dst, radiusW), numOfThreads); + if (ksize.height != 1) + parallel_for_(Range(0, widthElem), ParallelStackBlurColumn(dst, dst, radiusH), numOfThreads); + } + else if (sdepth == CV_32F) + { + if (ksize.width != 1) + parallel_for_(Range(0, src.rows), ParallelStackBlurRow(src, dst, radiusW), numOfThreads); + if (ksize.height != 1) + parallel_for_(Range(0, widthElem), ParallelStackBlurColumn(dst, dst, radiusH), numOfThreads); + } + else + CV_Error(Error::StsNotImplemented, + ("Unsupported input format in StackBlur, the supported formats are: CV_8U, CV_16U, CV_16S and CV_32F.")); +} +} //namespace diff --git a/modules/imgproc/src/sumpixels.avx512_skx.hpp b/modules/imgproc/src/sumpixels.avx512_skx.hpp index 3c9c90c658..81d9d1d846 100644 --- a/modules/imgproc/src/sumpixels.avx512_skx.hpp +++ b/modules/imgproc/src/sumpixels.avx512_skx.hpp @@ -6,6 +6,12 @@ #include "opencv2/core/hal/intrin.hpp" +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + namespace cv { namespace hal { CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN @@ -465,3 +471,7 @@ void calculate_integral_avx512(const uchar *src, size_t _srcstep, CV_CPU_OPTIMIZATION_NAMESPACE_END }} // end namespace cv::hal + +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic pop +#endif diff --git a/modules/imgproc/test/test_drawing.cpp b/modules/imgproc/test/test_drawing.cpp index cb120110e8..a24dff4a6b 100644 --- a/modules/imgproc/test/test_drawing.cpp +++ b/modules/imgproc/test/test_drawing.cpp @@ -71,7 +71,7 @@ void CV_DrawingTest::run( int ) { //imwrite( filename, testImg ); ts->printf( ts->LOG, "test image can not be read"); -#ifdef HAVE_PNG +#if defined(HAVE_PNG) || defined(HAVE_SPNG) ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); #else ts->printf( ts->LOG, "PNG image support is not available"); diff --git a/modules/imgproc/test/test_filter.cpp b/modules/imgproc/test/test_filter.cpp index 8de6a91216..fab4404df5 100644 --- a/modules/imgproc/test/test_filter.cpp +++ b/modules/imgproc/test/test_filter.cpp @@ -1090,5 +1090,18 @@ TEST(Imgproc_GaussianBlur, regression_11303) EXPECT_LE(cv::norm(src, dst, NORM_L2), 1e-3); } +TEST(Imgproc, morphologyEx_small_input_22893) +{ + char input_data[] = {1, 2, 3, 4}; + char gold_data[] = {2, 3, 4, 4}; + cv::Mat img(1, 4, CV_8UC1, input_data); + cv::Mat gold(1, 4, CV_8UC1, gold_data); + + cv::Mat kernel = getStructuringElement(cv::MORPH_RECT, cv::Size(4,4)); + cv::Mat result; + morphologyEx(img, result, cv::MORPH_DILATE, kernel); + + ASSERT_EQ(0, cvtest::norm(result, gold, NORM_INF)); +} }} // namespace diff --git a/modules/imgproc/test/test_houghlines.cpp b/modules/imgproc/test/test_houghlines.cpp index e90891274a..61b67d9873 100644 --- a/modules/imgproc/test/test_houghlines.cpp +++ b/modules/imgproc/test/test_houghlines.cpp @@ -329,6 +329,17 @@ TEST(HoughLinesPointSet, regression_21029) EXPECT_TRUE(lines.empty()); } +TEST(HoughLines, regression_21983) +{ + Mat img(200, 200, CV_8UC1, Scalar(0)); + line(img, Point(0, 100), Point(100, 100), Scalar(255)); + std::vector lines; + HoughLines(img, lines, 1, CV_PI/180, 90, 0, 0, 0.001, 1.58); + ASSERT_EQ(lines.size(), 1U); + EXPECT_EQ(lines[0][0], 100); + EXPECT_NEAR(lines[0][1], 1.57179642, 1e-4); +} + 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_pyramid.cpp b/modules/imgproc/test/test_pyramid.cpp index 343d7a2321..0556ce6fa6 100644 --- a/modules/imgproc/test/test_pyramid.cpp +++ b/modules/imgproc/test/test_pyramid.cpp @@ -8,12 +8,41 @@ namespace opencv_test { namespace { TEST(Imgproc_PyrUp, pyrUp_regression_22184) { - Mat src(100, 100, CV_16UC3, Scalar::all(255)); - Mat dst(100 * 2 + 1, 100 * 2 + 1, CV_16UC3, Scalar::all(0)); + Mat src(100,100,CV_16UC3,Scalar(255,255,255)); + Mat dst(100 * 2 + 1, 100 * 2 + 1, CV_16UC3, Scalar(0,0,0)); pyrUp(src, dst, Size(dst.cols, dst.rows)); double min_val = 0; minMaxLoc(dst, &min_val); ASSERT_GT(cvRound(min_val), 0); } -}} // namespace +TEST(Imgproc_PyrUp, pyrUp_regression_22194) +{ + Mat src(13, 13,CV_16UC3,Scalar(0,0,0)); + { + int swidth = src.cols; + int sheight = src.rows; + int cn = src.channels(); + int count = 0; + for (int y = 0; y < sheight; y++) + { + ushort *src_c = src.ptr(y); + for (int x = 0; x < swidth * cn; x++) + { + src_c[x] = (count++) % 10; + } + } + } + Mat dst(src.cols * 2 - 1, src.rows * 2 - 1, CV_16UC3, Scalar(0,0,0)); + pyrUp(src, dst, Size(dst.cols, dst.rows)); + + { + ushort *dst_c = dst.ptr(dst.rows - 1); + ASSERT_EQ(dst_c[0], 6); + ASSERT_EQ(dst_c[1], 6); + ASSERT_EQ(dst_c[2], 1); + } +} + +} +} diff --git a/modules/imgproc/test/test_stackblur.cpp b/modules/imgproc/test/test_stackblur.cpp new file mode 100644 index 0000000000..b7525467b3 --- /dev/null +++ b/modules/imgproc/test/test_stackblur.cpp @@ -0,0 +1,315 @@ +// 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. + +/* +StackBlur - a fast almost Gaussian Blur +Theory: http://underdestruction.com/2004/02/25/stackblur-2004 +The code has been borrowed from (https://github.com/flozz/StackBlur). + +Below is the original copyright +*/ + +/* +Copyright (c) 2010 Mario Klingemann + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + +template +void _stackblurRef(const Mat& src, Mat& dst, Size ksize) +{ + CV_Assert(!src.empty()); + CV_Assert(ksize.width > 0 && ksize.height > 0 && ksize.height % 2 == 1 && ksize.width % 2 == 1); + + dst.create(src.size(), src.type()); + const int CN = src.channels(); + + int rowsImg = src.rows; + int colsImg = src.cols; + int wm = colsImg - 1; + + int radiusW = ksize.width / 2; + int stackLenW = ksize.width; + const float mulW = 1.0f / (((float )radiusW + 1.0f) * ((float )radiusW + 1.0f)); + + // Horizontal direction + std::vector stack(stackLenW * CN); + for (int row = 0; row < rowsImg; row++) + { + std::vector sum(CN, 0); + std::vector sumIn(CN, 0); + std::vector sumOut(CN, 0); + + const T* srcPtr = src.ptr(row); + + for (int i = 0; i <= radiusW; i++) + { + for (int ci = 0; ci < CN; ci++) + { + T tmp = *(srcPtr + ci); + stack[i * CN + ci] = tmp; + sum[ci] += tmp * (i + 1); + sumOut[ci] += tmp; + } + } + + for (int i = 1; i <= radiusW; i++) + { + if (i <= wm) srcPtr += CN; + for(int ci = 0; ci < CN; ci++) + { + T tmp = *(srcPtr + ci); + stack[(i + radiusW) * CN + ci] = tmp; + sum[ci] += tmp * (radiusW + 1 - i); + sumIn[ci] += tmp; + } + } + + int sp = radiusW; + int xp = radiusW ; + if (xp > wm) xp = wm; + + T* dstPtr = dst.ptr(row); + srcPtr = src.ptr(row) + xp * CN; + + int stackStart= 0; + + for (int i = 0; i < colsImg; i++) + { + stackStart = sp + stackLenW - radiusW; + + if (stackStart >= stackLenW) stackStart -= stackLenW; + + for(int ci = 0; ci < CN; ci++) + { + *(dstPtr + ci) = cv::saturate_cast(sum[ci] * mulW); + sum[ci] -= sumOut[ci]; + sumOut[ci] -= stack[stackStart*CN + ci]; + } + + const T* srcNew = srcPtr; + + if(xp < wm) + srcNew += CN; + + for (int ci = 0; ci < CN; ci++) + { + stack[stackStart * CN + ci] = *(srcNew + ci); + sumIn[ci] += *(srcNew + ci); + sum[ci] += sumIn[ci]; + } + + int sp1 = sp + 1; + if (sp1 >= stackLenW) + sp1 = 0; + + for(int ci = 0; ci < CN; ci++) + { + T tmp = stack[sp1*CN + ci]; + sumOut[ci] += tmp; + sumIn[ci] -= tmp; + } + + dstPtr += CN; + + if (xp < wm) + { + xp++; + srcPtr += CN; + } + + ++sp; + if (sp >= stackLenW) + sp = 0; + } + } + + // Vertical direction + int hm = rowsImg - 1; + int widthElem = colsImg * CN; + int radiusH = ksize.height / 2; + int stackLenH = ksize.height; + const float mulH = 1.0f / (((float )radiusH + 1.0f) * ((float )radiusH + 1.0f)); + + stack.resize(stackLenH, 0); + for (int col = 0; col < widthElem; col++) + { + const T* srcPtr =dst.ptr() + col; + float sum0 = 0; + float sumIn0 = 0; + float sumOut0 = 0; + + for (int i = 0; i <= radiusH; i++) + { + T tmp = (T)(*srcPtr); + stack[i] = tmp; + sum0 += tmp * (i + 1); + sumOut0 += tmp; + } + + for (int i = 1; i <= radiusH; i++) + { + if (i <= hm) srcPtr += widthElem; + T tmp = (T)(*srcPtr); + stack[i + radiusH] = tmp; + sum0 += tmp * (radiusH - i + 1); + sumIn0 += tmp; + } + + int sp = radiusH; + int yp = radiusH; + + if (yp > hm) yp = hm; + + T* dstPtr = dst.ptr() + col; + srcPtr = dst.ptr(yp) + col; + + const T* srcNew; + + int stackStart = 0; + + for (int i = 0; i < rowsImg; i++) + { + stackStart = sp + stackLenH - radiusH; + if (stackStart >= stackLenH) stackStart -= stackLenH; + + *(dstPtr) = saturate_cast(sum0 * mulH); + sum0 -= sumOut0; + sumOut0 -= stack[stackStart]; + srcNew = srcPtr; + + if (yp < hm) + srcNew += widthElem; + + stack[stackStart] = *(srcNew); + sumIn0 += *(srcNew); + sum0 += sumIn0; + + int sp1 = sp + 1; + sp1 &= -(sp1 < stackLenH); + + sumOut0 += stack[sp1]; + sumIn0 -= stack[sp1]; + + dstPtr += widthElem; + + if (yp < hm) + { + yp++; + srcPtr += widthElem; + } + + ++sp; + if (sp >= stackLenH) sp = 0; + } + } +} + +void stackBlurRef(const Mat& img, Mat& dst, Size ksize) +{ + if(img.depth() == CV_8U) + _stackblurRef(img, dst, ksize); + else if (img.depth() == CV_16S) + _stackblurRef(img, dst, ksize); + else if (img.depth() == CV_16U) + _stackblurRef(img, dst, ksize); + else if (img.depth() == CV_32F) + _stackblurRef(img, dst, ksize); + else + CV_Error(Error::StsNotImplemented, + ("Unsupported Mat type in stackBlurRef, " + "the supported formats are: CV_8U, CV_16U, CV_16S and CV_32F.")); +} + +std::vector kernelSizeVec = { + Size(3, 3), + Size(5, 5), + Size(101, 101), + Size(3, 9) + }; + +typedef testing::TestWithParam > StackBlur; + +TEST_P (StackBlur, regression) +{ + Mat img_ = imread(findDataFile("shared/fruits.png"), 1); + const int cn = get<0>(GetParam()); + const int kIndex = get<1>(GetParam()); + const int dtype = get<2>(GetParam()); + + Size ksize = kernelSizeVec[kIndex]; + + Mat img, dstRef, dst; + convert(img_, img, dtype); + + vector channels; + split(img, channels); + channels.push_back(channels[0]); // channels size is 4. + + Mat imgCn; + if (cn == 1) + imgCn = channels[0]; + else if (cn == 4) + merge(channels, imgCn); + else + imgCn = img; + + stackBlurRef(imgCn, dstRef, ksize); + stackBlur(imgCn, dst, ksize); + EXPECT_LE(cvtest::norm(dstRef, dst, NORM_INF), 2.); +} + +INSTANTIATE_TEST_CASE_P(Imgproc, StackBlur, + testing::Combine( + testing::Values(1, 3, 4), + testing::Values(0, 1, 2, 3), + testing::Values(CV_8U, CV_16S, CV_16U, CV_32F) + ) +); + +typedef testing::TestWithParam > StackBlur_GaussianBlur; + +// StackBlur should produce similar results as GaussianBlur output. +TEST_P(StackBlur_GaussianBlur, compare) +{ + Mat img_ = imread(findDataFile("shared/fruits.png"), 1); + const int dtype = get<0>(GetParam()); + + Size ksize(3, 3); + Mat img, dstS, dstG; + convert(img_, img, dtype); + + stackBlur(img, dstS, ksize); + GaussianBlur(img, dstG, ksize, 0); + + EXPECT_LE(cvtest::norm(dstS, dstG, NORM_INF), 13.); +} + +INSTANTIATE_TEST_CASE_P(Imgproc, StackBlur_GaussianBlur, testing::Values(CV_8U, CV_16S, CV_16U, CV_32F)); +} +} diff --git a/modules/java/check-tests.py b/modules/java/check-tests.py index 29a9c31116..ce29fa3439 100755 --- a/modules/java/check-tests.py +++ b/modules/java/check-tests.py @@ -68,11 +68,11 @@ class JavaParser: if os.path.isfile(path): if path.endswith("FeatureDetector.java"): for prefix1 in ("", "Grid", "Pyramid", "Dynamic"): - for prefix2 in ("FAST", "STAR", "MSER", "ORB", "SIFT", "SURF", "GFTT", "HARRIS", "SIMPLEBLOB", "DENSE"): + for prefix2 in ("FAST", "STAR", "MSER", "ORB", "SIFT", "SURF", "GFTT", "HARRIS", "SIMPLEBLOB", "DENSE", "AKAZE", "KAZE", "BRISK", "AGAST"): parser.parse_file(path,prefix1+prefix2) elif path.endswith("DescriptorExtractor.java"): for prefix1 in ("", "Opponent"): - for prefix2 in ("BRIEF", "ORB", "SIFT", "SURF"): + for prefix2 in ("BRIEF", "ORB", "SIFT", "SURF", "AKAZE", "KAZE", "BEBLID", "DAISY", "FREAK", "LUCID", "LATCH"): parser.parse_file(path,prefix1+prefix2) elif path.endswith("GenericDescriptorMatcher.java"): for prefix in ("OneWay", "Fern"): diff --git a/modules/js/src/helpers.js b/modules/js/src/helpers.js index a2f3101b08..59e6e65b29 100644 --- a/modules/js/src/helpers.js +++ b/modules/js/src/helpers.js @@ -55,7 +55,7 @@ Module['imread'] = function(imageSource) { canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; - ctx = canvas.getContext('2d'); + ctx = canvas.getContext('2d', { willReadFrequently: true }); ctx.drawImage(img, 0, 0, img.width, img.height); } else if (img instanceof HTMLCanvasElement) { canvas = img; diff --git a/modules/ml/src/inner_functions.cpp b/modules/ml/src/inner_functions.cpp index b823c5ba22..6b3affcebc 100644 --- a/modules/ml/src/inner_functions.cpp +++ b/modules/ml/src/inner_functions.cpp @@ -126,10 +126,10 @@ public: errStrip[idxErr]=err ; - }; + } ParallelCalcError& operator=(const ParallelCalcError &) { return *this; - }; + } }; diff --git a/modules/objc/generator/gen_objc.py b/modules/objc/generator/gen_objc.py index 06d5341827..06a36820a1 100755 --- a/modules/objc/generator/gen_objc.py +++ b/modules/objc/generator/gen_objc.py @@ -208,6 +208,7 @@ class ConstInfo(GeneralInfo): def __init__(self, decl, addedManually=False, namespaces=[], enumType=None): GeneralInfo.__init__(self, "const", decl, namespaces) self.cname = get_cname(self.name) + self.swift_name = None self.value = decl[1] self.enumType = enumType self.addedManually = addedManually @@ -786,14 +787,27 @@ class ObjectiveCWrapperGenerator(object): logging.info('ignored: %s', constinfo) else: objc_type = enumType.rsplit(".", 1)[-1] if enumType else "" - if constinfo.classname in const_fix and objc_type in const_fix[constinfo.classname] and constinfo.name in const_fix[constinfo.classname][objc_type]: - fixed_const = const_fix[constinfo.classname][objc_type][constinfo.name] - constinfo.name = fixed_const - constinfo.cname = fixed_const + if constinfo.enumType and constinfo.classpath: + new_name = constinfo.classname + '_' + constinfo.name + const_fix.setdefault(constinfo.classpath, {}).setdefault(objc_type, {})[constinfo.name] = new_name + constinfo.swift_name = constinfo.name + constinfo.name = new_name + logging.info('use outer class prefix: %s', constinfo) + + if constinfo.classpath in const_fix and objc_type in const_fix[constinfo.classpath]: + fixed_consts = const_fix[constinfo.classpath][objc_type] + if constinfo.name in fixed_consts: + fixed_const = fixed_consts[constinfo.name] + constinfo.name = fixed_const + constinfo.cname = fixed_const + if constinfo.value in fixed_consts: + constinfo.value = fixed_consts[constinfo.value] if not self.isWrapped(constinfo.classname): logging.info('class not found: %s', constinfo) - constinfo.name = constinfo.classname + '_' + constinfo.name + if not constinfo.name.startswith(constinfo.classname + "_"): + constinfo.swift_name = constinfo.name + constinfo.name = constinfo.classname + '_' + constinfo.name constinfo.classname = '' ci = self.getClass(constinfo.classname) @@ -1302,7 +1316,9 @@ $unrefined_call$epilogue$ret ci.enum_declarations.write(""" // C++: enum {1} ({2}) typedef NS_ENUM(int, {1}) {{ - {0}\n}};\n\n""".format(",\n ".join(["%s = %s" % (c.name, c.value) for c in consts]), typeNameShort, typeName) + {0}\n}};\n\n""".format( + ",\n ".join(["%s = %s" % (c.name + (" NS_SWIFT_NAME(" + c.swift_name + ")" if c.swift_name else ""), c.value) for c in consts]), + typeNameShort, typeName) ) else: if not wrote_consts_pragma: diff --git a/modules/objdetect/doc/objdetect.bib b/modules/objdetect/doc/objdetect.bib new file mode 100644 index 0000000000..394eff8537 --- /dev/null +++ b/modules/objdetect/doc/objdetect.bib @@ -0,0 +1,20 @@ +@article{Aruco2014, + author = {S. Garrido-Jurado and R. Mu\~noz-Salinas and F.J. Madrid-Cuevas and M.J. Mar\'in-Jim\'enez} + title = {Automatic generation and detection of highly reliable fiducial markers under occlusion}, + year = {2014}, + pages = {2280 - 2292}, + journal = {Pattern Recognition}, + volume = {47}, + number = {6}, + issn = {0031-3203}, + doi = {http://dx.doi.org/10.1016/j.patcog.2014.01.005}, + url = {http://www.sciencedirect.com/science/article/pii/S0031320314000235} +} + +@inproceedings{wang2016iros, + author = {John Wang and Edwin Olson}, + title = {{AprilTag} 2: Efficient and robust fiducial detection}, + booktitle = {Proceedings of the {IEEE/RSJ} International Conference on Intelligent Robots and Systems {(IROS)}}, + year = {2016}, + month = {October} +} diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index 1956e03aa3..27a6921400 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -105,6 +105,26 @@ using a Boosted Cascade of Simple Features. IEEE CVPR, 2001. The paper is availa @defgroup objdetect_dnn_face DNN-based face detection and recognition Check @ref tutorial_dnn_face "the corresponding tutorial" for more details. @defgroup objdetect_common Common functions and classes + @defgroup objdetect_aruco ArUco markers and boards detection for robust camera pose estimation + @{ + ArUco Marker Detection + Square fiducial markers (also known as Augmented Reality Markers) are useful for easy, + fast and robust camera pose estimation. + + The main functionality of ArucoDetector class is detection of markers in an image. If the markers are grouped + as a board, then you can try to recover the missing markers with ArucoDetector::refineDetectedMarkers(). + ArUco markers can also be used for advanced chessboard corner finding. To do this, group the markers in the + CharucoBoard and find the corners of the chessboard with the CharucoDetector::detectBoard(). + + The implementation is based on the ArUco Library by R. Muñoz-Salinas and S. Garrido-Jurado @cite Aruco2014. + + Markers can also be detected based on the AprilTag 2 @cite wang2016iros fiducial detection method. + + @sa @cite Aruco2014 + This code has been originally developed by Sergio Garrido-Jurado as a project + for Google Summer of Code 2015 (GSoC 15). + @} + @} */ @@ -751,6 +771,12 @@ public: */ CV_WRAP void setEpsY(double epsY); + /** @brief use markers to improve the position of the corners of the QR code + * + * alignmentMarkers using by default + */ + CV_WRAP void setUseAlignmentMarkers(bool useAlignmentMarkers); + /** @brief Detects QR code in image and returns the quadrangle containing the code. @param img grayscale or color (BGR) image containing (or not) QR code. @param points Output vector of vertices of the minimum-area quadrangle containing the code. @@ -836,5 +862,7 @@ protected: #include "opencv2/objdetect/detection_based_tracker.hpp" #include "opencv2/objdetect/face.hpp" +#include "opencv2/objdetect/aruco_detector.hpp" +#include "opencv2/objdetect/charuco_detector.hpp" #endif diff --git a/modules/objdetect/include/opencv2/objdetect/aruco_board.hpp b/modules/objdetect/include/opencv2/objdetect/aruco_board.hpp new file mode 100644 index 0000000000..90ad2b8a7e --- /dev/null +++ b/modules/objdetect/include/opencv2/objdetect/aruco_board.hpp @@ -0,0 +1,179 @@ +// 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_OBJDETECT_ARUCO_BOARD_HPP +#define OPENCV_OBJDETECT_ARUCO_BOARD_HPP + +#include + +namespace cv { +namespace aruco { +//! @addtogroup objdetect_aruco +//! @{ + +class Dictionary; + +/** @brief Board of ArUco markers + * + * A board is a set of markers in the 3D space with a common coordinate system. + * The common form of a board of marker is a planar (2D) board, however any 3D layout can be used. + * A Board object is composed by: + * - The object points of the marker corners, i.e. their coordinates respect to the board system. + * - The dictionary which indicates the type of markers of the board + * - The identifier of all the markers in the board. + */ +class CV_EXPORTS_W_SIMPLE Board { +public: + /** @brief Common Board constructor + * + * @param objPoints array of object points of all the marker corners in the board + * @param dictionary the dictionary of markers employed for this board + * @param ids vector of the identifiers of the markers in the board + */ + CV_WRAP Board(InputArrayOfArrays objPoints, const Dictionary& dictionary, InputArray ids); + + /** @brief return the Dictionary of markers employed for this board + */ + CV_WRAP const Dictionary& getDictionary() const; + + /** @brief return array of object points of all the marker corners in the board. + * + * Each marker include its 4 corners in this order: + * - objPoints[i][0] - left-top point of i-th marker + * - objPoints[i][1] - right-top point of i-th marker + * - objPoints[i][2] - right-bottom point of i-th marker + * - objPoints[i][3] - left-bottom point of i-th marker + * + * Markers are placed in a certain order - row by row, left to right in every row. For M markers, the size is Mx4. + */ + CV_WRAP const std::vector >& getObjPoints() const; + + /** @brief vector of the identifiers of the markers in the board (should be the same size as objPoints) + * @return vector of the identifiers of the markers + */ + CV_WRAP const std::vector& getIds() const; + + /** @brief get coordinate of the bottom right corner of the board, is set when calling the function create() + */ + CV_WRAP const Point3f& getRightBottomCorner() const; + + /** @brief Given a board configuration and a set of detected markers, returns the corresponding + * image points and object points to call solvePnP() + * + * @param detectedCorners List of detected marker corners of the board. + * For CharucoBoard class you can set list of charuco corners. + * @param detectedIds List of identifiers for each marker or list of charuco identifiers for each corner. + * For CharucoBoard class you can set list of charuco identifiers for each corner. + * @param objPoints Vector of vectors of board marker points in the board coordinate space. + * @param imgPoints Vector of vectors of the projections of board marker corner points. + */ + CV_WRAP void matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds, + OutputArray objPoints, OutputArray imgPoints) const; + + /** @brief Draw a planar board + * + * @param outSize size of the output image in pixels. + * @param img output image with the board. The size of this image will be outSize + * and the board will be on the center, keeping the board proportions. + * @param marginSize minimum margins (in pixels) of the board in the output image + * @param borderBits width of the marker borders. + * + * This function return the image of the board, ready to be printed. + */ + CV_WRAP void generateImage(Size outSize, OutputArray img, int marginSize = 0, int borderBits = 1) const; + + CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first) + Board(); + + struct Impl; +protected: + Board(const Ptr& impl); + Ptr impl; +}; + +/** @brief Planar board with grid arrangement of markers + * + * More common type of board. All markers are placed in the same plane in a grid arrangement. + * The board image can be drawn using generateImage() method. + */ +class CV_EXPORTS_W_SIMPLE GridBoard : public Board { +public: + /** + * @brief GridBoard constructor + * + * @param size number of markers in x and y directions + * @param markerLength marker side length (normally in meters) + * @param markerSeparation separation between two markers (same unit as markerLength) + * @param dictionary dictionary of markers indicating the type of markers + * @param ids set of marker ids in dictionary to use on board. + */ + CV_WRAP GridBoard(const Size& size, float markerLength, float markerSeparation, + const Dictionary &dictionary, InputArray ids = noArray()); + + CV_WRAP Size getGridSize() const; + CV_WRAP float getMarkerLength() const; + CV_WRAP float getMarkerSeparation() const; + + CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first) + GridBoard(); +}; + +/** + * @brief ChArUco board is a planar chessboard where the markers are placed inside the white squares of a chessboard. + * + * The benefits of ChArUco boards is that they provide both, ArUco markers versatility and chessboard corner precision, + * which is important for calibration and pose estimation. The board image can be drawn using generateImage() method. + */ +class CV_EXPORTS_W_SIMPLE CharucoBoard : public Board { +public: + /** @brief CharucoBoard constructor + * + * @param size number of chessboard squares in x and y directions + * @param squareLength squareLength chessboard square side length (normally in meters) + * @param markerLength marker side length (same unit than squareLength) + * @param dictionary dictionary of markers indicating the type of markers + * @param ids array of id used markers + * The first markers in the dictionary are used to fill the white chessboard squares. + */ + CV_WRAP CharucoBoard(const Size& size, float squareLength, float markerLength, + const Dictionary &dictionary, InputArray ids = noArray()); + + CV_WRAP Size getChessboardSize() const; + CV_WRAP float getSquareLength() const; + CV_WRAP float getMarkerLength() const; + + /** @brief get CharucoBoard::chessboardCorners + */ + CV_WRAP std::vector getChessboardCorners() const; + + /** @brief get CharucoBoard::nearestMarkerIdx + */ + CV_PROP std::vector > getNearestMarkerIdx() const; + + /** @brief get CharucoBoard::nearestMarkerCorners + */ + CV_PROP std::vector > getNearestMarkerCorners() const; + + /** @brief check whether the ChArUco markers are collinear + * + * @param charucoIds list of identifiers for each corner in charucoCorners per frame. + * @return bool value, 1 (true) if detected corners form a line, 0 (false) if they do not. + * solvePnP, calibration functions will fail if the corners are collinear (true). + * + * The number of ids in charucoIDs should be <= the number of chessboard corners in the board. + * This functions checks whether the charuco corners are on a straight line (returns true, if so), or not (false). + * Axis parallel, as well as diagonal and other straight lines detected. Degenerate cases: + * for number of charucoIDs <= 2,the function returns true. + */ + CV_WRAP bool checkCharucoCornersCollinear(InputArray charucoIds) const; + + CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to “protected” (need to fix bindings first) + CharucoBoard(); +}; + +//! @} + +} +} + +#endif diff --git a/modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp b/modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp new file mode 100644 index 0000000000..c7338c39e0 --- /dev/null +++ b/modules/objdetect/include/opencv2/objdetect/aruco_detector.hpp @@ -0,0 +1,367 @@ +// 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_OBJDETECT_ARUCO_DETECTOR_HPP +#define OPENCV_OBJDETECT_ARUCO_DETECTOR_HPP + +#include +#include + +namespace cv { +namespace aruco { + +//! @addtogroup objdetect_aruco +//! @{ + +enum CornerRefineMethod{ + CORNER_REFINE_NONE, ///< Tag and corners detection based on the ArUco approach + CORNER_REFINE_SUBPIX, ///< ArUco approach and refine the corners locations using corner subpixel accuracy + CORNER_REFINE_CONTOUR, ///< ArUco approach and refine the corners locations using the contour-points line fitting + CORNER_REFINE_APRILTAG, ///< Tag and corners detection based on the AprilTag 2 approach @cite wang2016iros +}; + +/** @brief struct DetectorParameters is used by ArucoDetector + */ +struct CV_EXPORTS_W_SIMPLE DetectorParameters { + CV_WRAP DetectorParameters() { + adaptiveThreshWinSizeMin = 3; + adaptiveThreshWinSizeMax = 23; + adaptiveThreshWinSizeStep = 10; + adaptiveThreshConstant = 7; + minMarkerPerimeterRate = 0.03; + maxMarkerPerimeterRate = 4.; + polygonalApproxAccuracyRate = 0.03; + minCornerDistanceRate = 0.05; + minDistanceToBorder = 3; + minMarkerDistanceRate = 0.05; + cornerRefinementMethod = CORNER_REFINE_NONE; + cornerRefinementWinSize = 5; + cornerRefinementMaxIterations = 30; + cornerRefinementMinAccuracy = 0.1; + markerBorderBits = 1; + perspectiveRemovePixelPerCell = 4; + perspectiveRemoveIgnoredMarginPerCell = 0.13; + maxErroneousBitsInBorderRate = 0.35; + minOtsuStdDev = 5.0; + errorCorrectionRate = 0.6; + aprilTagQuadDecimate = 0.0; + aprilTagQuadSigma = 0.0; + aprilTagMinClusterPixels = 5; + aprilTagMaxNmaxima = 10; + aprilTagCriticalRad = (float)(10* CV_PI /180); + aprilTagMaxLineFitMse = 10.0; + aprilTagMinWhiteBlackDiff = 5; + aprilTagDeglitch = 0; + detectInvertedMarker = false; + useAruco3Detection = false; + minSideLengthCanonicalImg = 32; + minMarkerLengthRatioOriginalImg = 0.0; + }; + + /** @brief Read a new set of DetectorParameters from FileNode (use FileStorage.root()). + */ + CV_WRAP bool readDetectorParameters(const FileNode& fn); + + /** @brief Write a set of DetectorParameters to FileStorage + */ + CV_WRAP bool writeDetectorParameters(FileStorage& fs, const String& name = String()); + + /// minimum window size for adaptive thresholding before finding contours (default 3). + CV_PROP_RW int adaptiveThreshWinSizeMin; + + /// maximum window size for adaptive thresholding before finding contours (default 23). + CV_PROP_RW int adaptiveThreshWinSizeMax; + + /// increments from adaptiveThreshWinSizeMin to adaptiveThreshWinSizeMax during the thresholding (default 10). + CV_PROP_RW int adaptiveThreshWinSizeStep; + + /// constant for adaptive thresholding before finding contours (default 7) + CV_PROP_RW double adaptiveThreshConstant; + + /** @brief determine minimum perimeter for marker contour to be detected. + * + * This is defined as a rate respect to the maximum dimension of the input image (default 0.03). + */ + CV_PROP_RW double minMarkerPerimeterRate; + + /** @brief determine maximum perimeter for marker contour to be detected. + * + * This is defined as a rate respect to the maximum dimension of the input image (default 4.0). + */ + CV_PROP_RW double maxMarkerPerimeterRate; + + /// minimum accuracy during the polygonal approximation process to determine which contours are squares. (default 0.03) + CV_PROP_RW double polygonalApproxAccuracyRate; + + /// minimum distance between corners for detected markers relative to its perimeter (default 0.05) + CV_PROP_RW double minCornerDistanceRate; + + /// minimum distance of any corner to the image border for detected markers (in pixels) (default 3) + CV_PROP_RW int minDistanceToBorder; + + /** @brief minimum mean distance beetween two marker corners to be considered imilar, so that the smaller one is removed. + * + * The rate is relative to the smaller perimeter of the two markers (default 0.05). + */ + CV_PROP_RW double minMarkerDistanceRate; + + /** @brief default value CORNER_REFINE_NONE */ + CV_PROP_RW CornerRefineMethod cornerRefinementMethod; + + /// window size for the corner refinement process (in pixels) (default 5). + CV_PROP_RW int cornerRefinementWinSize; + + /// maximum number of iterations for stop criteria of the corner refinement process (default 30). + CV_PROP_RW int cornerRefinementMaxIterations; + + /// minimum error for the stop cristeria of the corner refinement process (default: 0.1) + CV_PROP_RW double cornerRefinementMinAccuracy; + + /// number of bits of the marker border, i.e. marker border width (default 1). + CV_PROP_RW int markerBorderBits; + + /// number of bits (per dimension) for each cell of the marker when removing the perspective (default 4). + CV_PROP_RW int perspectiveRemovePixelPerCell; + + /** @brief width of the margin of pixels on each cell not considered for the determination of the cell bit. + * + * Represents the rate respect to the total size of the cell, i.e. perspectiveRemovePixelPerCell (default 0.13) + */ + CV_PROP_RW double perspectiveRemoveIgnoredMarginPerCell; + + /** @brief maximum number of accepted erroneous bits in the border (i.e. number of allowed white bits in the border). + * + * Represented as a rate respect to the total number of bits per marker (default 0.35). + */ + CV_PROP_RW double maxErroneousBitsInBorderRate; + + /** @brief minimun standard deviation in pixels values during the decodification step to apply Otsu + * thresholding (otherwise, all the bits are set to 0 or 1 depending on mean higher than 128 or not) (default 5.0) + */ + CV_PROP_RW double minOtsuStdDev; + + /// error correction rate respect to the maximun error correction capability for each dictionary (default 0.6). + CV_PROP_RW double errorCorrectionRate; + + /** @brief April :: User-configurable parameters. + * + * Detection of quads can be done on a lower-resolution image, improving speed at a cost of + * pose accuracy and a slight decrease in detection rate. Decoding the binary payload is still + */ + CV_PROP_RW float aprilTagQuadDecimate; + + /// what Gaussian blur should be applied to the segmented image (used for quad detection?) + CV_PROP_RW float aprilTagQuadSigma; + + // April :: Internal variables + /// reject quads containing too few pixels (default 5). + CV_PROP_RW int aprilTagMinClusterPixels; + + /// how many corner candidates to consider when segmenting a group of pixels into a quad (default 10). + CV_PROP_RW int aprilTagMaxNmaxima; + + /** @brief reject quads where pairs of edges have angles that are close to straight or close to 180 degrees. + * + * Zero means that no quads are rejected. (In radians) (default 10*PI/180) + */ + CV_PROP_RW float aprilTagCriticalRad; + + /// when fitting lines to the contours, what is the maximum mean squared error + CV_PROP_RW float aprilTagMaxLineFitMse; + + /** @brief add an extra check that the white model must be (overall) brighter than the black model. + * + * When we build our model of black & white pixels, we add an extra check that the white model must be (overall) + * brighter than the black model. How much brighter? (in pixel values, [0,255]), (default 5) + */ + CV_PROP_RW int aprilTagMinWhiteBlackDiff; + + /// should the thresholded image be deglitched? Only useful for very noisy images (default 0). + CV_PROP_RW int aprilTagDeglitch; + + /** @brief to check if there is a white marker. + * + * In order to generate a "white" marker just invert a normal marker by using a tilde, ~markerImage. (default false) + */ + CV_PROP_RW bool detectInvertedMarker; + + /** @brief enable the new and faster Aruco detection strategy. + * + * Proposed in the paper: + * Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018) + * https://www.researchgate.net/publication/325787310_Speeded_Up_Detection_of_Squared_Fiducial_Markers + */ + CV_PROP_RW bool useAruco3Detection; + + /// minimum side length of a marker in the canonical image. Latter is the binarized image in which contours are searched. + CV_PROP_RW int minSideLengthCanonicalImg; + + /// range [0,1], eq (2) from paper. The parameter tau_i has a direct influence on the processing speed. + CV_PROP_RW float minMarkerLengthRatioOriginalImg; +}; + +/** @brief struct RefineParameters is used by ArucoDetector + */ +struct CV_EXPORTS_W_SIMPLE RefineParameters { + CV_WRAP RefineParameters(float minRepDistance = 10.f, float errorCorrectionRate = 3.f, bool checkAllOrders = true); + + + /** @brief Read a new set of RefineParameters from FileNode (use FileStorage.root()). + */ + CV_WRAP bool readRefineParameters(const FileNode& fn); + + /** @brief Write a set of RefineParameters to FileStorage + */ + CV_WRAP bool writeRefineParameters(FileStorage& fs, const String& name = String()); + + /** @brief minRepDistance minimum distance between the corners of the rejected candidate and the reprojected marker + in order to consider it as a correspondence. + */ + CV_PROP_RW float minRepDistance; + + /** @brief minRepDistance rate of allowed erroneous bits respect to the error correction capability of the used dictionary. + * + * -1 ignores the error correction step. + */ + CV_PROP_RW float errorCorrectionRate; + + /** @brief checkAllOrders consider the four posible corner orders in the rejectedCorners array. + * + * If it set to false, only the provided corner order is considered (default true). + */ + CV_PROP_RW bool checkAllOrders; +}; + +/** @brief The main functionality of ArucoDetector class is detection of markers in an image with detectMarkers() method. + * + * After detecting some markers in the image, you can try to find undetected markers from this dictionary with + * refineDetectedMarkers() method. + * + * @see DetectorParameters, RefineParameters + */ +class CV_EXPORTS_W ArucoDetector : public Algorithm +{ +public: + /** @brief Basic ArucoDetector constructor + * + * @param dictionary indicates the type of markers that will be searched + * @param detectorParams marker detection parameters + * @param refineParams marker refine detection parameters + */ + CV_WRAP ArucoDetector(const Dictionary &dictionary = getPredefinedDictionary(cv::aruco::DICT_4X4_50), + const DetectorParameters &detectorParams = DetectorParameters(), + const RefineParameters& refineParams = RefineParameters()); + + /** @brief Basic marker detection + * + * @param image input image + * @param corners vector of detected marker corners. For each marker, its four corners + * are provided, (e.g std::vector > ). For N detected markers, + * the dimensions of this array is Nx4. The order of the corners is clockwise. + * @param ids vector of identifiers of the detected markers. The identifier is of type int + * (e.g. std::vector). For N detected markers, the size of ids is also N. + * The identifiers have the same order than the markers in the imgPoints array. + * @param rejectedImgPoints contains the imgPoints of those squares whose inner code has not a + * correct codification. Useful for debugging purposes. + * + * Performs marker detection in the input image. Only markers included in the specific dictionary + * are searched. For each detected marker, it returns the 2D position of its corner in the image + * and its corresponding identifier. + * Note that this function does not perform pose estimation. + * @note The function does not correct lens distortion or takes it into account. It's recommended to undistort + * input image with corresponging camera model, if camera parameters are known + * @sa undistort, estimatePoseSingleMarkers, estimatePoseBoard + */ + CV_WRAP void detectMarkers(InputArray image, OutputArrayOfArrays corners, OutputArray ids, + OutputArrayOfArrays rejectedImgPoints = noArray()) const; + + /** @brief Refind not detected markers based on the already detected and the board layout + * + * @param image input image + * @param board layout of markers in the board. + * @param detectedCorners vector of already detected marker corners. + * @param detectedIds vector of already detected marker identifiers. + * @param rejectedCorners vector of rejected candidates during the marker detection process. + * @param cameraMatrix optional input 3x3 floating-point camera matrix + * \f$A = \vecthreethree{f_x}{0}{c_x}{0}{f_y}{c_y}{0}{0}{1}\f$ + * @param distCoeffs optional vector of distortion coefficients + * \f$(k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6],[s_1, s_2, s_3, s_4]])\f$ of 4, 5, 8 or 12 elements + * @param recoveredIdxs Optional array to returns the indexes of the recovered candidates in the + * original rejectedCorners array. + * + * This function tries to find markers that were not detected in the basic detecMarkers function. + * First, based on the current detected marker and the board layout, the function interpolates + * the position of the missing markers. Then it tries to find correspondence between the reprojected + * markers and the rejected candidates based on the minRepDistance and errorCorrectionRate parameters. + * If camera parameters and distortion coefficients are provided, missing markers are reprojected + * using projectPoint function. If not, missing marker projections are interpolated using global + * homography, and all the marker corners in the board must have the same Z coordinate. + */ + CV_WRAP void refineDetectedMarkers(InputArray image, const Board &board, + InputOutputArrayOfArrays detectedCorners, + InputOutputArray detectedIds, InputOutputArrayOfArrays rejectedCorners, + InputArray cameraMatrix = noArray(), InputArray distCoeffs = noArray(), + OutputArray recoveredIdxs = noArray()) const; + + CV_WRAP const Dictionary& getDictionary() const; + CV_WRAP void setDictionary(const Dictionary& dictionary); + + CV_WRAP const DetectorParameters& getDetectorParameters() const; + CV_WRAP void setDetectorParameters(const DetectorParameters& detectorParameters); + + CV_WRAP const RefineParameters& getRefineParameters() const; + CV_WRAP void setRefineParameters(const RefineParameters& refineParameters); + + /** @brief Stores algorithm parameters in a file storage + */ + virtual void write(FileStorage& fs) const override; + + /** @brief simplified API for language bindings + */ + CV_WRAP inline void write(FileStorage& fs, const String& name) { Algorithm::write(fs, name); } + + /** @brief Reads algorithm parameters from a file storage + */ + CV_WRAP virtual void read(const FileNode& fn) override; +protected: + struct ArucoDetectorImpl; + Ptr arucoDetectorImpl; +}; + +/** @brief Draw detected markers in image + * + * @param image input/output image. It must have 1 or 3 channels. The number of channels is not altered. + * @param corners positions of marker corners on input image. + * (e.g std::vector > ). For N detected markers, the dimensions of + * this array should be Nx4. The order of the corners should be clockwise. + * @param ids vector of identifiers for markers in markersCorners . + * Optional, if not provided, ids are not painted. + * @param borderColor color of marker borders. Rest of colors (text color and first corner color) + * are calculated based on this one to improve visualization. + * + * Given an array of detected marker corners and its corresponding ids, this functions draws + * the markers in the image. The marker borders are painted and the markers identifiers if provided. + * Useful for debugging purposes. + */ +CV_EXPORTS_W void drawDetectedMarkers(InputOutputArray image, InputArrayOfArrays corners, + InputArray ids = noArray(), Scalar borderColor = Scalar(0, 255, 0)); + +/** @brief Generate a canonical marker image + * + * @param dictionary dictionary of markers indicating the type of markers + * @param id identifier of the marker that will be returned. It has to be a valid id in the specified dictionary. + * @param sidePixels size of the image in pixels + * @param img output image with the marker + * @param borderBits width of the marker border. + * + * This function returns a marker image in its canonical form (i.e. ready to be printed) + */ +CV_EXPORTS_W void generateImageMarker(const Dictionary &dictionary, int id, int sidePixels, OutputArray img, + int borderBits = 1); + +//! @} + +} +} + +#endif diff --git a/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp new file mode 100644 index 0000000000..343d876b6c --- /dev/null +++ b/modules/objdetect/include/opencv2/objdetect/aruco_dictionary.hpp @@ -0,0 +1,147 @@ +// 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_OBJDETECT_DICTIONARY_HPP +#define OPENCV_OBJDETECT_DICTIONARY_HPP + +#include + +namespace cv { +namespace aruco { + +//! @addtogroup objdetect_aruco +//! @{ + + +/** @brief Dictionary/Set of markers, it contains the inner codification + * + * BytesList contains the marker codewords where: + * - bytesList.rows is the dictionary size + * - each marker is encoded using `nbytes = ceil(markerSize*markerSize/8.)` + * - each row contains all 4 rotations of the marker, so its length is `4*nbytes` + * + * `bytesList.ptr(i)[k*nbytes + j]` is then the j-th byte of i-th marker, in its k-th rotation. + */ +class CV_EXPORTS_W_SIMPLE Dictionary { + + public: + CV_PROP_RW Mat bytesList; // marker code information + CV_PROP_RW int markerSize; // number of bits per dimension + CV_PROP_RW int maxCorrectionBits; // maximum number of bits that can be corrected + + + CV_WRAP Dictionary(); + + CV_WRAP Dictionary(const Mat &bytesList, int _markerSize, int maxcorr = 0); + + + + /** @brief Read a new dictionary from FileNode. + * + * Dictionary format:\n + * nmarkers: 35\n + * markersize: 6\n + * maxCorrectionBits: 5\n + * marker_0: "101011111011111001001001101100000000"\n + * ...\n + * marker_34: "011111010000111011111110110101100101" + */ + CV_WRAP bool readDictionary(const cv::FileNode& fn); + + /** @brief Write a dictionary to FileStorage, format is the same as in readDictionary(). + */ + CV_WRAP void writeDictionary(FileStorage& fs, const String& name = String()); + + /** @brief Given a matrix of bits. Returns whether if marker is identified or not. + * + * It returns by reference the correct id (if any) and the correct rotation + */ + CV_WRAP bool identify(const Mat &onlyBits, CV_OUT int &idx, CV_OUT int &rotation, double maxCorrectionRate) const; + + /** @brief Returns the distance of the input bits to the specific id. + * + * If allRotations is true, the four posible bits rotation are considered + */ + CV_WRAP int getDistanceToId(InputArray bits, int id, bool allRotations = true) const; + + + /** @brief Generate a canonical marker image + */ + CV_WRAP void generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits = 1) const; + + + /** @brief Transform matrix of bits to list of bytes in the 4 rotations + */ + CV_WRAP static Mat getByteListFromBits(const Mat &bits); + + + /** @brief Transform list of bytes to matrix of bits + */ + CV_WRAP static Mat getBitsFromByteList(const Mat &byteList, int markerSize); +}; + + + + +/** @brief Predefined markers dictionaries/sets + * + * Each dictionary indicates the number of bits and the number of markers contained + * - DICT_ARUCO_ORIGINAL: standard ArUco Library Markers. 1024 markers, 5x5 bits, 0 minimum + distance + */ +enum PredefinedDictionaryType { + DICT_4X4_50 = 0, ///< 4x4 bits, minimum hamming distance between any two codes = 4, 50 codes + DICT_4X4_100, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 100 codes + DICT_4X4_250, ///< 4x4 bits, minimum hamming distance between any two codes = 3, 250 codes + DICT_4X4_1000, ///< 4x4 bits, minimum hamming distance between any two codes = 2, 1000 codes + DICT_5X5_50, ///< 5x5 bits, minimum hamming distance between any two codes = 8, 50 codes + DICT_5X5_100, ///< 5x5 bits, minimum hamming distance between any two codes = 7, 100 codes + DICT_5X5_250, ///< 5x5 bits, minimum hamming distance between any two codes = 6, 250 codes + DICT_5X5_1000, ///< 5x5 bits, minimum hamming distance between any two codes = 5, 1000 codes + DICT_6X6_50, ///< 6x6 bits, minimum hamming distance between any two codes = 13, 50 codes + DICT_6X6_100, ///< 6x6 bits, minimum hamming distance between any two codes = 12, 100 codes + DICT_6X6_250, ///< 6x6 bits, minimum hamming distance between any two codes = 11, 250 codes + DICT_6X6_1000, ///< 6x6 bits, minimum hamming distance between any two codes = 9, 1000 codes + DICT_7X7_50, ///< 7x7 bits, minimum hamming distance between any two codes = 19, 50 codes + DICT_7X7_100, ///< 7x7 bits, minimum hamming distance between any two codes = 18, 100 codes + DICT_7X7_250, ///< 7x7 bits, minimum hamming distance between any two codes = 17, 250 codes + DICT_7X7_1000, ///< 7x7 bits, minimum hamming distance between any two codes = 14, 1000 codes + DICT_ARUCO_ORIGINAL, ///< 6x6 bits, minimum hamming distance between any two codes = 3, 1024 codes + DICT_APRILTAG_16h5, ///< 4x4 bits, minimum hamming distance between any two codes = 5, 30 codes + DICT_APRILTAG_25h9, ///< 5x5 bits, minimum hamming distance between any two codes = 9, 35 codes + DICT_APRILTAG_36h10, ///< 6x6 bits, minimum hamming distance between any two codes = 10, 2320 codes + DICT_APRILTAG_36h11 ///< 6x6 bits, minimum hamming distance between any two codes = 11, 587 codes +}; + + +/** @brief Returns one of the predefined dictionaries defined in PredefinedDictionaryType + */ +CV_EXPORTS Dictionary getPredefinedDictionary(PredefinedDictionaryType name); + + +/** @brief Returns one of the predefined dictionaries referenced by DICT_*. + */ +CV_EXPORTS_W Dictionary getPredefinedDictionary(int dict); + +/** @brief Extend base dictionary by new nMarkers + * + * @param nMarkers number of markers in the dictionary + * @param markerSize number of bits per dimension of each markers + * @param baseDictionary Include the markers in this dictionary at the beginning (optional) + * @param randomSeed a user supplied seed for theRNG() + * + * This function creates a new dictionary composed by nMarkers markers and each markers composed + * by markerSize x markerSize bits. If baseDictionary is provided, its markers are directly + * included and the rest are generated based on them. If the size of baseDictionary is higher + * than nMarkers, only the first nMarkers in baseDictionary are taken and no new marker is added. + */ +CV_EXPORTS_W Dictionary extendDictionary(int nMarkers, int markerSize, const Dictionary &baseDictionary = Dictionary(), + int randomSeed=0); + + + +//! @} +} +} + +#endif diff --git a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp new file mode 100644 index 0000000000..f6ad677099 --- /dev/null +++ b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp @@ -0,0 +1,154 @@ +// 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_OBJDETECT_CHARUCO_DETECTOR_HPP +#define OPENCV_OBJDETECT_CHARUCO_DETECTOR_HPP + +#include "opencv2/objdetect/aruco_detector.hpp" + +namespace cv { +namespace aruco { + +//! @addtogroup objdetect_aruco +//! @{ + +struct CV_EXPORTS_W_SIMPLE CharucoParameters { + CharucoParameters() { + minMarkers = 2; + tryRefineMarkers = false; + } + /// cameraMatrix optional 3x3 floating-point camera matrix + CV_PROP_RW Mat cameraMatrix; + + /// distCoeffs optional vector of distortion coefficients + CV_PROP_RW Mat distCoeffs; + + /// minMarkers number of adjacent markers that must be detected to return a charuco corner, default = 2 + CV_PROP_RW int minMarkers; + + /// try to use refine board, default false + CV_PROP_RW bool tryRefineMarkers; +}; + +class CV_EXPORTS_W CharucoDetector : public Algorithm { +public: + /** @brief Basic CharucoDetector constructor + * + * @param board ChAruco board + * @param charucoParams charuco detection parameters + * @param detectorParams marker detection parameters + * @param refineParams marker refine detection parameters + */ + CV_WRAP CharucoDetector(const CharucoBoard& board, + const CharucoParameters& charucoParams = CharucoParameters(), + const DetectorParameters &detectorParams = DetectorParameters(), + const RefineParameters& refineParams = RefineParameters()); + + CV_WRAP const CharucoBoard& getBoard() const; + CV_WRAP void setBoard(const CharucoBoard& board); + + CV_WRAP const CharucoParameters& getCharucoParameters() const; + CV_WRAP void setCharucoParameters(CharucoParameters& charucoParameters); + + CV_WRAP const DetectorParameters& getDetectorParameters() const; + CV_WRAP void setDetectorParameters(const DetectorParameters& detectorParameters); + + CV_WRAP const RefineParameters& getRefineParameters() const; + CV_WRAP void setRefineParameters(const RefineParameters& refineParameters); + + /** + * @brief detect aruco markers and interpolate position of ChArUco board corners + * @param image input image necesary for corner refinement. Note that markers are not detected and + * should be sent in corners and ids parameters. + * @param charucoCorners interpolated chessboard corners. + * @param charucoIds interpolated chessboard corners identifiers. + * @param markerCorners vector of already detected markers corners. For each marker, its four + * corners are provided, (e.g std::vector > ). For N detected markers, the + * dimensions of this array should be Nx4. The order of the corners should be clockwise. + * If markerCorners and markerCorners are empty, the function detect aruco markers and ids. + * @param markerIds list of identifiers for each marker in corners. + * If markerCorners and markerCorners are empty, the function detect aruco markers and ids. + * + * This function receives the detected markers and returns the 2D position of the chessboard corners + * from a ChArUco board using the detected Aruco markers. + * + * If markerCorners and markerCorners are empty, the detectMarkers() will run and detect aruco markers and ids. + * + * If camera parameters are provided, the process is based in an approximated pose estimation, else it is based on local homography. + * Only visible corners are returned. For each corner, its corresponding identifier is also returned in charucoIds. + * @sa findChessboardCorners + */ + CV_WRAP void detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds, + InputOutputArrayOfArrays markerCorners = noArray(), + InputOutputArray markerIds = noArray()) const; + + /** + * @brief Detect ChArUco Diamond markers + * + * @param image input image necessary for corner subpixel. + * @param diamondCorners output list of detected diamond corners (4 corners per diamond). The order + * is the same than in marker corners: top left, top right, bottom right and bottom left. Similar + * format than the corners returned by detectMarkers (e.g std::vector > ). + * @param diamondIds ids of the diamonds in diamondCorners. The id of each diamond is in fact of + * type Vec4i, so each diamond has 4 ids, which are the ids of the aruco markers composing the + * diamond. + * @param markerCorners list of detected marker corners from detectMarkers function. + * If markerCorners and markerCorners are empty, the function detect aruco markers and ids. + * @param markerIds list of marker ids in markerCorners. + * If markerCorners and markerCorners are empty, the function detect aruco markers and ids. + * + * This function detects Diamond markers from the previous detected ArUco markers. The diamonds + * are returned in the diamondCorners and diamondIds parameters. If camera calibration parameters + * are provided, the diamond search is based on reprojection. If not, diamond search is based on + * homography. Homography is faster than reprojection, but less accurate. + */ + CV_WRAP void detectDiamonds(InputArray image, OutputArrayOfArrays diamondCorners, OutputArray diamondIds, + InputOutputArrayOfArrays markerCorners = noArray(), + InputOutputArrayOfArrays markerIds = noArray()) const; +protected: + struct CharucoDetectorImpl; + Ptr charucoDetectorImpl; +}; + +/** + * @brief Draws a set of Charuco corners + * @param image input/output image. It must have 1 or 3 channels. The number of channels is not + * altered. + * @param charucoCorners vector of detected charuco corners + * @param charucoIds list of identifiers for each corner in charucoCorners + * @param cornerColor color of the square surrounding each corner + * + * This function draws a set of detected Charuco corners. If identifiers vector is provided, it also + * draws the id of each corner. + */ +CV_EXPORTS_W void drawDetectedCornersCharuco(InputOutputArray image, InputArray charucoCorners, + InputArray charucoIds = noArray(), Scalar cornerColor = Scalar(255, 0, 0)); + +/** + * @brief Draw a set of detected ChArUco Diamond markers + * + * @param image input/output image. It must have 1 or 3 channels. The number of channels is not + * altered. + * @param diamondCorners positions of diamond corners in the same format returned by + * detectCharucoDiamond(). (e.g std::vector > ). For N detected markers, + * the dimensions of this array should be Nx4. The order of the corners should be clockwise. + * @param diamondIds vector of identifiers for diamonds in diamondCorners, in the same format + * returned by detectCharucoDiamond() (e.g. std::vector). + * Optional, if not provided, ids are not painted. + * @param borderColor color of marker borders. Rest of colors (text color and first corner color) + * are calculated based on this one. + * + * Given an array of detected diamonds, this functions draws them in the image. The marker borders + * are painted and the markers identifiers if provided. + * Useful for debugging purposes. + */ +CV_EXPORTS_W void drawDetectedDiamonds(InputOutputArray image, InputArrayOfArrays diamondCorners, + InputArray diamondIds = noArray(), + Scalar borderColor = Scalar(0, 0, 255)); + +//! @} + +} +} + +#endif diff --git a/modules/objdetect/misc/java/test/ArucoTest.java b/modules/objdetect/misc/java/test/ArucoTest.java new file mode 100644 index 0000000000..932141275c --- /dev/null +++ b/modules/objdetect/misc/java/test/ArucoTest.java @@ -0,0 +1,113 @@ +package org.opencv.test.aruco; + +import java.util.ArrayList; +import java.util.List; + +import org.opencv.test.OpenCVTestCase; +import org.junit.Assert; +import org.opencv.core.Scalar; +import org.opencv.core.Mat; +import org.opencv.core.MatOfInt; +import org.opencv.core.Size; +import org.opencv.core.CvType; +import org.opencv.objdetect.*; + + +public class ArucoTest extends OpenCVTestCase { + + public void testGenerateBoards() { + Dictionary dictionary = Objdetect.getPredefinedDictionary(Objdetect.DICT_4X4_50); + + Mat point1 = new Mat(4, 3, CvType.CV_32FC1); + int row = 0, col = 0; + double squareLength = 40.; + point1.put(row, col, 0, 0, 0, + 0, squareLength, 0, + squareLength, squareLength, 0, + 0, squareLength, 0); + ListobjPoints = new ArrayList(); + objPoints.add(point1); + + Mat ids = new Mat(1, 1, CvType.CV_32SC1); + ids.put(row, col, 0); + + Board board = new Board(objPoints, dictionary, ids); + + Mat image = new Mat(); + board.generateImage(new Size(80, 80), image, 2); + + assertTrue(image.total() > 0); + } + + public void testArucoIssue3133() { + byte[][] marker = {{0,1,1},{1,1,1},{0,1,1}}; + Dictionary dictionary = Objdetect.extendDictionary(1, 3); + dictionary.set_maxCorrectionBits(0); + Mat markerBits = new Mat(3, 3, CvType.CV_8UC1); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + markerBits.put(i, j, marker[i][j]); + } + } + + Mat markerCompressed = Dictionary.getByteListFromBits(markerBits); + assertMatNotEqual(markerCompressed, dictionary.get_bytesList()); + + dictionary.set_bytesList(markerCompressed); + assertMatEqual(markerCompressed, dictionary.get_bytesList()); + } + + public void testArucoDetector() { + Dictionary dictionary = Objdetect.getPredefinedDictionary(0); + DetectorParameters detectorParameters = new DetectorParameters(); + ArucoDetector detector = new ArucoDetector(dictionary, detectorParameters); + + Mat markerImage = new Mat(); + int id = 1, offset = 5, size = 40; + Objdetect.generateImageMarker(dictionary, id, size, markerImage, detectorParameters.get_markerBorderBits()); + + Mat image = new Mat(markerImage.rows() + 2*offset, markerImage.cols() + 2*offset, + CvType.CV_8UC1, new Scalar(255)); + Mat m = image.submat(offset, size+offset, offset, size+offset); + markerImage.copyTo(m); + + List corners = new ArrayList(); + Mat ids = new Mat(); + detector.detectMarkers(image, corners, ids); + + assertEquals(1, corners.size()); + Mat res = corners.get(0); + assertArrayEquals(new double[]{offset, offset}, res.get(0, 0), 0.0); + assertArrayEquals(new double[]{size + offset - 1, offset}, res.get(0, 1), 0.0); + assertArrayEquals(new double[]{size + offset - 1, size + offset - 1}, res.get(0, 2), 0.0); + assertArrayEquals(new double[]{offset, size + offset - 1}, res.get(0, 3), 0.0); + } + + public void testCharucoDetector() { + Dictionary dictionary = Objdetect.getPredefinedDictionary(0); + int boardSizeX = 3, boardSizeY = 3; + CharucoBoard board = new CharucoBoard(new Size(boardSizeX, boardSizeY), 1.f, 0.8f, dictionary); + CharucoDetector charucoDetector = new CharucoDetector(board); + + int cellSize = 80; + Mat boardImage = new Mat(); + board.generateImage(new Size(cellSize*boardSizeX, cellSize*boardSizeY), boardImage); + + assertTrue(boardImage.total() > 0); + + Mat charucoCorners = new Mat(); + Mat charucoIds = new Mat(); + charucoDetector.detectBoard(boardImage, charucoCorners, charucoIds); + + assertEquals(4, charucoIds.total()); + int[] intCharucoIds = (new MatOfInt(charucoIds)).toArray(); + Assert.assertArrayEquals(new int[]{0, 1, 2, 3}, intCharucoIds); + + double eps = 0.2; + assertArrayEquals(new double[]{cellSize, cellSize}, charucoCorners.get(0, 0), eps); + assertArrayEquals(new double[]{2*cellSize, cellSize}, charucoCorners.get(1, 0), eps); + assertArrayEquals(new double[]{cellSize, 2*cellSize}, charucoCorners.get(2, 0), eps); + assertArrayEquals(new double[]{2*cellSize, 2*cellSize}, charucoCorners.get(3, 0), eps); + } + +} diff --git a/modules/objdetect/misc/python/test/test_objdetect_aruco.py b/modules/objdetect/misc/python/test/test_objdetect_aruco.py new file mode 100644 index 0000000000..97d4fcb821 --- /dev/null +++ b/modules/objdetect/misc/python/test/test_objdetect_aruco.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +# Python 2/3 compatibility +from __future__ import print_function + +import os, tempfile, numpy as np + +import cv2 as cv + +from tests_common import NewOpenCVTests + +class aruco_objdetect_test(NewOpenCVTests): + + def test_idsAccessibility(self): + + ids = np.arange(17) + rev_ids = ids[::-1] + + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_250) + board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict) + + np.testing.assert_array_equal(board.getIds().squeeze(), ids) + + board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict, rev_ids) + np.testing.assert_array_equal(board.getIds().squeeze(), rev_ids) + + board = cv.aruco.CharucoBoard((7, 5), 1, 0.5, aruco_dict, ids) + np.testing.assert_array_equal(board.getIds().squeeze(), ids) + + def test_identify(self): + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50) + expected_idx = 9 + expected_rotation = 2 + bit_marker = np.array([[0, 1, 1, 0], [1, 0, 1, 0], [1, 1, 1, 1], [0, 0, 1, 1]], dtype=np.uint8) + + check, idx, rotation = aruco_dict.identify(bit_marker, 0) + + self.assertTrue(check, True) + self.assertEqual(idx, expected_idx) + self.assertEqual(rotation, expected_rotation) + + def test_getDistanceToId(self): + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_50) + idx = 7 + rotation = 3 + bit_marker = np.array([[0, 1, 0, 1], [0, 1, 1, 1], [1, 1, 0, 0], [0, 1, 0, 0]], dtype=np.uint8) + dist = aruco_dict.getDistanceToId(bit_marker, idx) + + self.assertEqual(dist, 0) + + def test_aruco_detector(self): + aruco_params = cv.aruco.DetectorParameters() + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250) + aruco_detector = cv.aruco.ArucoDetector(aruco_dict, aruco_params) + id = 2 + marker_size = 100 + offset = 10 + img_marker = cv.aruco.generateImageMarker(aruco_dict, id, marker_size, aruco_params.markerBorderBits) + img_marker = np.pad(img_marker, pad_width=offset, mode='constant', constant_values=255) + gold_corners = np.array([[offset, offset],[marker_size+offset-1.0,offset], + [marker_size+offset-1.0,marker_size+offset-1.0], + [offset, marker_size+offset-1.0]], dtype=np.float32) + corners, ids, rejected = aruco_detector.detectMarkers(img_marker) + + self.assertEqual(1, len(ids)) + self.assertEqual(id, ids[0]) + for i in range(0, len(corners)): + np.testing.assert_array_equal(gold_corners, corners[i].reshape(4, 2)) + + def test_aruco_detector_refine(self): + aruco_params = cv.aruco.DetectorParameters() + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250) + aruco_detector = cv.aruco.ArucoDetector(aruco_dict, aruco_params) + board_size = (3, 4) + board = cv.aruco.GridBoard(board_size, 5.0, 1.0, aruco_dict) + board_image = board.generateImage((board_size[0]*50, board_size[1]*50), marginSize=10) + + corners, ids, rejected = aruco_detector.detectMarkers(board_image) + self.assertEqual(board_size[0]*board_size[1], len(ids)) + + part_corners, part_ids, part_rejected = corners[:-1], ids[:-1], list(rejected) + part_rejected.append(corners[-1]) + + refine_corners, refine_ids, refine_rejected, recovered_ids = aruco_detector.refineDetectedMarkers(board_image, board, part_corners, part_ids, part_rejected) + + self.assertEqual(board_size[0] * board_size[1], len(refine_ids)) + self.assertEqual(1, len(recovered_ids)) + + self.assertEqual(ids[-1], refine_ids[-1]) + self.assertEqual((1, 4, 2), refine_corners[0].shape) + np.testing.assert_array_equal(corners, refine_corners) + + def test_write_read_dictionary(self): + try: + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_5X5_50) + markers_gold = aruco_dict.bytesList + + # write aruco_dict + fd, filename = tempfile.mkstemp(prefix="opencv_python_aruco_dict_", suffix=".yml") + os.close(fd) + + fs_write = cv.FileStorage(filename, cv.FileStorage_WRITE) + aruco_dict.writeDictionary(fs_write) + fs_write.release() + + # reset aruco_dict + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_6X6_250) + + # read aruco_dict + fs_read = cv.FileStorage(filename, cv.FileStorage_READ) + aruco_dict.readDictionary(fs_read.root()) + fs_read.release() + + # check equal + self.assertEqual(aruco_dict.markerSize, 5) + self.assertEqual(aruco_dict.maxCorrectionBits, 3) + np.testing.assert_array_equal(aruco_dict.bytesList, markers_gold) + + finally: + if os.path.exists(filename): + os.remove(filename) + + def test_charuco_detector(self): + aruco_dict = cv.aruco.getPredefinedDictionary(cv.aruco.DICT_4X4_250) + board_size = (3, 3) + board = cv.aruco.CharucoBoard(board_size, 1.0, .8, aruco_dict) + charuco_detector = cv.aruco.CharucoDetector(board) + cell_size = 100 + + image = board.generateImage((cell_size*board_size[0], cell_size*board_size[1])) + + list_gold_corners = [] + for i in range(1, board_size[0]): + for j in range(1, board_size[1]): + list_gold_corners.append((j*cell_size, i*cell_size)) + gold_corners = np.array(list_gold_corners, dtype=np.float32) + + charucoCorners, charucoIds, markerCorners, markerIds = charuco_detector.detectBoard(image) + + self.assertEqual(len(charucoIds), 4) + for i in range(0, 4): + self.assertEqual(charucoIds[i], i) + np.testing.assert_allclose(gold_corners, charucoCorners.reshape(-1, 2), 0.01, 0.1) + +if __name__ == '__main__': + NewOpenCVTests.bootstrap() diff --git a/modules/objdetect/misc/python/test/test_qrcode_detect.py b/modules/objdetect/misc/python/test/test_qrcode_detect.py index 8a95c8bce5..0237900572 100644 --- a/modules/objdetect/misc/python/test/test_qrcode_detect.py +++ b/modules/objdetect/misc/python/test/test_qrcode_detect.py @@ -43,10 +43,10 @@ class qrcode_detector_test(NewOpenCVTests): retval, decoded_data, points, straight_qrcode = detector.detectAndDecodeMulti(img) self.assertTrue(retval) self.assertEqual(len(decoded_data), 6) - self.assertEqual(decoded_data[0], "TWO STEPS FORWARD") - self.assertEqual(decoded_data[1], "EXTRA") - self.assertEqual(decoded_data[2], "SKIP") - self.assertEqual(decoded_data[3], "STEP FORWARD") - self.assertEqual(decoded_data[4], "STEP BACK") - self.assertEqual(decoded_data[5], "QUESTION") + self.assertTrue("TWO STEPS FORWARD" in decoded_data) + self.assertTrue("EXTRA" in decoded_data) + self.assertTrue("SKIP" in decoded_data) + self.assertTrue("STEP FORWARD" in decoded_data) + self.assertTrue("STEP BACK" in decoded_data) + self.assertTrue("QUESTION" in decoded_data) self.assertEqual(points.shape, (6, 4, 2)) diff --git a/modules/objdetect/perf/perf_aruco.cpp b/modules/objdetect/perf/perf_aruco.cpp new file mode 100644 index 0000000000..3a5a659482 --- /dev/null +++ b/modules/objdetect/perf/perf_aruco.cpp @@ -0,0 +1,285 @@ +// 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 "perf_precomp.hpp" +#include "opencv2/3d.hpp" + +namespace opencv_test { +using namespace perf; + +typedef tuple UseArucoParams; +typedef TestBaseWithParam EstimateAruco; +#define ESTIMATE_PARAMS Combine(Values(false, true), Values(-1)) + +static double deg2rad(double deg) { return deg * CV_PI / 180.; } + +class MarkerPainter +{ +private: + int imgMarkerSize = 0; + Mat cameraMatrix; +public: + MarkerPainter(const int size) { + setImgMarkerSize(size); + } + + void setImgMarkerSize(const int size) { + imgMarkerSize = size; + cameraMatrix = Mat::eye(3, 3, CV_64FC1); + cameraMatrix.at(0, 0) = cameraMatrix.at(1, 1) = imgMarkerSize; + cameraMatrix.at(0, 2) = imgMarkerSize / 2.0; + cameraMatrix.at(1, 2) = imgMarkerSize / 2.0; + } + + static std::pair getSyntheticRT(double yaw, double pitch, double distance) { + auto rvec_tvec = std::make_pair(Mat(3, 1, CV_64FC1), Mat(3, 1, CV_64FC1)); + Mat& rvec = rvec_tvec.first; + Mat& tvec = rvec_tvec.second; + + // Rvec + // first put the Z axis aiming to -X (like the camera axis system) + Mat rotZ(3, 1, CV_64FC1); + rotZ.ptr(0)[0] = 0; + rotZ.ptr(0)[1] = 0; + rotZ.ptr(0)[2] = -0.5 * CV_PI; + + Mat rotX(3, 1, CV_64FC1); + rotX.ptr(0)[0] = 0.5 * CV_PI; + rotX.ptr(0)[1] = 0; + rotX.ptr(0)[2] = 0; + + Mat camRvec, camTvec; + composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)), + camRvec, camTvec); + + // now pitch and yaw angles + Mat rotPitch(3, 1, CV_64FC1); + rotPitch.ptr(0)[0] = 0; + rotPitch.ptr(0)[1] = pitch; + rotPitch.ptr(0)[2] = 0; + + Mat rotYaw(3, 1, CV_64FC1); + rotYaw.ptr(0)[0] = yaw; + rotYaw.ptr(0)[1] = 0; + rotYaw.ptr(0)[2] = 0; + + composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw, + Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec); + + // compose both rotations + composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, + Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec); + + // Tvec, just move in z (camera) direction the specific distance + tvec.ptr(0)[0] = 0.; + tvec.ptr(0)[1] = 0.; + tvec.ptr(0)[2] = distance; + return rvec_tvec; + } + + std::pair > getProjectMarker(int id, double yaw, double pitch, + const aruco::DetectorParameters& parameters, + const aruco::Dictionary& dictionary) { + auto marker_corners = std::make_pair(Mat(imgMarkerSize, imgMarkerSize, CV_8UC1, Scalar::all(255)), vector()); + Mat& img = marker_corners.first; + vector& corners = marker_corners.second; + + // canonical image + const int markerSizePixels = static_cast(imgMarkerSize/sqrt(2.f)); + aruco::generateImageMarker(dictionary, id, markerSizePixels, img, parameters.markerBorderBits); + + // get rvec and tvec for the perspective + const double distance = 0.1; + auto rvec_tvec = MarkerPainter::getSyntheticRT(yaw, pitch, distance); + Mat& rvec = rvec_tvec.first; + Mat& tvec = rvec_tvec.second; + + const float markerLength = 0.05f; + vector markerObjPoints; + markerObjPoints.emplace_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0)); + markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, 0, 0)); + markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0)); + markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(0, -markerLength, 0)); + + // project markers and draw them + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners); + + vector originalCorners; + originalCorners.emplace_back(Point2f(0.f, 0.f)); + originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, 0)); + originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels)); + originalCorners.emplace_back(originalCorners[0]+Point2f(0, (float)markerSizePixels)); + + Mat transformation = getPerspectiveTransform(originalCorners, corners); + + warpPerspective(img, img, transformation, Size(imgMarkerSize, imgMarkerSize), INTER_NEAREST, BORDER_CONSTANT, + Scalar::all(255)); + return marker_corners; + } + + std::pair > > getProjectMarkersTile(const int numMarkers, + const aruco::DetectorParameters& params, + const aruco::Dictionary& dictionary) { + Mat tileImage(imgMarkerSize*numMarkers, imgMarkerSize*numMarkers, CV_8UC1, Scalar::all(255)); + map > idCorners; + + int iter = 0, pitch = 0, yaw = 0; + for (int i = 0; i < numMarkers; i++) { + for (int j = 0; j < numMarkers; j++) { + int currentId = iter; + auto marker_corners = getProjectMarker(currentId, deg2rad(70+yaw), deg2rad(pitch), params, dictionary); + Point2i startPoint(j*imgMarkerSize, i*imgMarkerSize); + Mat tmp_roi = tileImage(Rect(startPoint.x, startPoint.y, imgMarkerSize, imgMarkerSize)); + marker_corners.first.copyTo(tmp_roi); + + for (Point2f& point: marker_corners.second) + point += static_cast(startPoint); + idCorners[currentId] = marker_corners.second; + auto test = idCorners[currentId]; + yaw = (yaw + 10) % 51; // 70+yaw >= 70 && 70+yaw <= 120 + iter++; + } + pitch = (pitch + 60) % 360; + } + return std::make_pair(tileImage, idCorners); + } +}; + +static inline double getMaxDistance(map > &golds, const vector& ids, + const vector >& corners) { + std::map mapDist; + for (const auto& el : golds) + mapDist[el.first] = std::numeric_limits::max(); + for (size_t i = 0; i < ids.size(); i++) { + int id = ids[i]; + const auto gold_corners = golds.find(id); + if (gold_corners != golds.end()) { + double distance = 0.; + for (int c = 0; c < 4; c++) + distance = std::max(distance, cv::norm(gold_corners->second[c] - corners[i][c])); + mapDist[id] = distance; + } + } + return std::max_element(std::begin(mapDist), std::end(mapDist), + [](const pair& p1, const pair& p2){return p1.second < p2.second;})->second; +} + +PERF_TEST_P(EstimateAruco, ArucoFirst, ESTIMATE_PARAMS) { + UseArucoParams testParams = GetParam(); + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + aruco::DetectorParameters detectorParams; + detectorParams.minDistanceToBorder = 1; + detectorParams.markerBorderBits = 1; + detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX; + + const int markerSize = 100; + const int numMarkersInRow = 9; + //USE_ARUCO3 + detectorParams.useAruco3Detection = get<0>(testParams); + if (detectorParams.useAruco3Detection) { + detectorParams.minSideLengthCanonicalImg = 32; + detectorParams.minMarkerLengthRatioOriginalImg = 0.04f / numMarkersInRow; + } + aruco::ArucoDetector detector(dictionary, detectorParams); + MarkerPainter painter(markerSize); + auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary); + + // detect markers + vector > corners; + vector ids; + TEST_CYCLE() { + detector.detectMarkers(image_map.first, corners, ids); + } + ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast(ids.size())); + double maxDistance = getMaxDistance(image_map.second, ids, corners); + ASSERT_LT(maxDistance, 3.); + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P(EstimateAruco, ArucoSecond, ESTIMATE_PARAMS) { + UseArucoParams testParams = GetParam(); + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + aruco::DetectorParameters detectorParams; + detectorParams.minDistanceToBorder = 1; + detectorParams.markerBorderBits = 1; + detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX; + + //USE_ARUCO3 + detectorParams.useAruco3Detection = get<0>(testParams); + if (detectorParams.useAruco3Detection) { + detectorParams.minSideLengthCanonicalImg = 64; + detectorParams.minMarkerLengthRatioOriginalImg = 0.f; + } + aruco::ArucoDetector detector(dictionary, detectorParams); + const int markerSize = 200; + const int numMarkersInRow = 11; + MarkerPainter painter(markerSize); + auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary); + + // detect markers + vector > corners; + vector ids; + TEST_CYCLE() { + detector.detectMarkers(image_map.first, corners, ids); + } + ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast(ids.size())); + double maxDistance = getMaxDistance(image_map.second, ids, corners); + ASSERT_LT(maxDistance, 3.); + SANITY_CHECK_NOTHING(); +} + +struct Aruco3Params { + bool useAruco3Detection = false; + float minMarkerLengthRatioOriginalImg = 0.f; + int minSideLengthCanonicalImg = 0; + + Aruco3Params(bool useAruco3, float minMarkerLen, int minSideLen): useAruco3Detection(useAruco3), + minMarkerLengthRatioOriginalImg(minMarkerLen), + minSideLengthCanonicalImg(minSideLen) {} + friend std::ostream& operator<<(std::ostream& os, const Aruco3Params& d) { + os << d.useAruco3Detection << " " << d.minMarkerLengthRatioOriginalImg << " " << d.minSideLengthCanonicalImg; + return os; + } +}; +typedef tuple> ArucoTestParams; + +typedef TestBaseWithParam EstimateLargeAruco; +#define ESTIMATE_FHD_PARAMS Combine(Values(Aruco3Params(false, 0.f, 0), Aruco3Params(true, 0.f, 32), \ +Aruco3Params(true, 0.015f, 32), Aruco3Params(true, 0.f, 16), Aruco3Params(true, 0.0069f, 16)), \ +Values(std::make_pair(1440, 1), std::make_pair(480, 3), std::make_pair(144, 10))) + +PERF_TEST_P(EstimateLargeAruco, ArucoFHD, ESTIMATE_FHD_PARAMS) { + ArucoTestParams testParams = GetParam(); + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + aruco::DetectorParameters detectorParams; + detectorParams.minDistanceToBorder = 1; + detectorParams.markerBorderBits = 1; + detectorParams.cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX; + + //USE_ARUCO3 + detectorParams.useAruco3Detection = get<0>(testParams).useAruco3Detection; + if (detectorParams.useAruco3Detection) { + detectorParams.minSideLengthCanonicalImg = get<0>(testParams).minSideLengthCanonicalImg; + detectorParams.minMarkerLengthRatioOriginalImg = get<0>(testParams).minMarkerLengthRatioOriginalImg; + } + aruco::ArucoDetector detector(dictionary, detectorParams); + const int markerSize = get<1>(testParams).first; // 1440 or 480 or 144 + const int numMarkersInRow = get<1>(testParams).second; // 1 or 3 or 144 + MarkerPainter painter(markerSize); // num pixels is 1440x1440 as in FHD 1920x1080 + auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary); + + // detect markers + vector > corners; + vector ids; + TEST_CYCLE() + { + detector.detectMarkers(image_map.first, corners, ids); + } + ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast(ids.size())); + double maxDistance = getMaxDistance(image_map.second, ids, corners); + ASSERT_LT(maxDistance, 3.); + SANITY_CHECK_NOTHING(); +} + +} diff --git a/modules/objdetect/perf/perf_qrcode_pipeline.cpp b/modules/objdetect/perf/perf_qrcode_pipeline.cpp index 9e7960d819..6722978b9a 100644 --- a/modules/objdetect/perf/perf_qrcode_pipeline.cpp +++ b/modules/objdetect/perf/perf_qrcode_pipeline.cpp @@ -55,6 +55,10 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, decode) typedef ::perf::TestBaseWithParam< std::string > Perf_Objdetect_QRCode_Multi; +static inline bool compareCorners(const Point2f& corner1, const Point2f& corner2) { + return corner1.x == corner2.x ? corner1.y < corner2.y : corner1.x < corner2.x; +} + PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, detectMulti) { const std::string name_current_image = GetParam(); @@ -66,15 +70,19 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, detectMulti) std::vector corners; QRCodeDetector qrcode; TEST_CYCLE() ASSERT_TRUE(qrcode.detectMulti(src, corners)); + sort(corners.begin(), corners.end(), compareCorners); SANITY_CHECK(corners); } +static inline bool compareQR(const pair& v1, const pair& v2) { + return v1.first < v2.first; +} + #ifdef HAVE_QUIRC PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti) { const std::string name_current_image = GetParam(); const std::string root = "cv/qrcode/multiple/"; - std::string image_path = findDataFile(root + name_current_image); Mat src = imread(image_path); ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; @@ -91,15 +99,21 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti) ASSERT_FALSE(decoded_info[i].empty()); } } - std::vector < std::vector< uint8_t > > decoded_info_uint8_t; - for(size_t i = 0; i < decoded_info.size(); i++) - { - std::vector< uint8_t > tmp(decoded_info[i].begin(), decoded_info[i].end()); - decoded_info_uint8_t.push_back(tmp); + ASSERT_EQ(decoded_info.size(), straight_barcode.size()); + vector > result; + for (size_t i = 0ull; i < decoded_info.size(); i++) { + result.push_back(make_pair(decoded_info[i], straight_barcode[i])); } - SANITY_CHECK(decoded_info_uint8_t); - SANITY_CHECK(straight_barcode); + sort(result.begin(), result.end(), compareQR); + vector > decoded_info_sort; + vector straight_barcode_sort; + for (size_t i = 0ull; i < result.size(); i++) { + vector tmp(result[i].first.begin(), result[i].first.end()); + decoded_info_sort.push_back(tmp); + straight_barcode_sort.push_back(result[i].second); + } + SANITY_CHECK(decoded_info_sort); } #endif diff --git a/modules/objdetect/src/aruco/apriltag/apriltag_quad_thresh.cpp b/modules/objdetect/src/aruco/apriltag/apriltag_quad_thresh.cpp new file mode 100644 index 0000000000..38d1b2ffb8 --- /dev/null +++ b/modules/objdetect/src/aruco/apriltag/apriltag_quad_thresh.cpp @@ -0,0 +1,1666 @@ +// 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) 2013-2016, The Regents of The University of Michigan. +// +// This software was developed in the APRIL Robotics Lab under the +// direction of Edwin Olson, ebolson@umich.edu. This software may be +// available under alternative licensing terms; contact the address above. +// +// The views and conclusions contained in the software and documentation are those +// of the authors and should not be interpreted as representing official policies, +// either expressed or implied, of the Regents of The University of Michigan. + +// limitation: image size must be <32768 in width and height. This is +// because we use a fixed-point 16 bit integer representation with one +// fractional bit. + +#include "../../precomp.hpp" +#include "apriltag_quad_thresh.hpp" + +//#define APRIL_DEBUG +#ifdef APRIL_DEBUG + #include "opencv2/imgcodecs.hpp" + #include +#endif + +namespace cv { +namespace aruco { + +static void ptsort_(struct pt *pts, int sz); // forward delaration + +static inline +void ptsort(struct pt *pts, int sz) +{ +#define MAYBE_SWAP(arr,apos,bpos) \ + if (arr[apos].theta > arr[bpos].theta) { \ + tmp = arr[apos]; arr[apos] = arr[bpos]; arr[bpos] = tmp; \ + }; + + if (sz <= 1) + return; + + if (sz == 2) { + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); + return; + } + + // NB: Using less-branch-intensive sorting networks here on the + // hunch that it's better for performance. + if (sz == 3) { // 3 element bubble sort is optimal + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); + MAYBE_SWAP(pts, 1, 2); + MAYBE_SWAP(pts, 0, 1); + return; + } + + if (sz == 4) { // 4 element optimal sorting network. + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); // sort each half, like a merge sort + MAYBE_SWAP(pts, 2, 3); + MAYBE_SWAP(pts, 0, 2); // minimum value is now at 0. + MAYBE_SWAP(pts, 1, 3); // maximum value is now at end. + MAYBE_SWAP(pts, 1, 2); // that only leaves the middle two. + return; + } + + if (sz == 5) { + // this 9-step swap is optimal for a sorting network, but two + // steps slower than a generic sort. + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); // sort each half (3+2), like a merge sort + MAYBE_SWAP(pts, 3, 4); + MAYBE_SWAP(pts, 1, 2); + MAYBE_SWAP(pts, 0, 1); + MAYBE_SWAP(pts, 0, 3); // minimum element now at 0 + MAYBE_SWAP(pts, 2, 4); // maximum element now at end + MAYBE_SWAP(pts, 1, 2); // now resort the three elements 1-3. + MAYBE_SWAP(pts, 2, 3); + MAYBE_SWAP(pts, 1, 2); + return; + } + +#undef MAYBE_SWAP + + ptsort_(pts, sz); +} + +void ptsort_(struct pt *pts, int sz) +{ + // a merge sort with temp storage. + + // Use stack storage if it's not too big. + cv::AutoBuffer _tmp_stack(sz); + memcpy(_tmp_stack.data(), pts, sizeof(struct pt) * sz); + + int asz = sz/2; + int bsz = sz - asz; + + struct pt *as = &_tmp_stack[0]; + struct pt *bs = &_tmp_stack[asz]; + + ptsort(as, asz); + ptsort(bs, bsz); + +#define MERGE(apos,bpos) \ + if (as[apos].theta < bs[bpos].theta) \ + pts[outpos++] = as[apos++]; \ + else \ + pts[outpos++] = bs[bpos++]; + + int apos = 0, bpos = 0, outpos = 0; + while (apos + 8 < asz && bpos + 8 < bsz) { + MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); + MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); + } + + while (apos < asz && bpos < bsz) { + MERGE(apos,bpos); + } + + if (apos < asz) + memcpy(&pts[outpos], &as[apos], (asz-apos)*sizeof(struct pt)); + if (bpos < bsz) + memcpy(&pts[outpos], &bs[bpos], (bsz-bpos)*sizeof(struct pt)); + +#undef MERGE +} + +/** + * lfps contains *cumulative* moments for N points, with + * index j reflecting points [0,j] (inclusive). + * fit a line to the points [i0, i1] (inclusive). i0, i1 are both (0, sz) + * if i1 < i0, we treat this as a wrap around. + */ +void fit_line(struct line_fit_pt *lfps, int sz, int i0, int i1, double *lineparm, double *err, double *mse){ + CV_Assert(i0 != i1); + CV_Assert(i0 >= 0 && i1 >= 0 && i0 < sz && i1 < sz); + + double Mx, My, Mxx, Myy, Mxy, W; + int N; // how many points are included in the set? + + if (i0 < i1) { + N = i1 - i0 + 1; + + Mx = lfps[i1].Mx; + My = lfps[i1].My; + Mxx = lfps[i1].Mxx; + Mxy = lfps[i1].Mxy; + Myy = lfps[i1].Myy; + W = lfps[i1].W; + + if (i0 > 0) { + Mx -= lfps[i0-1].Mx; + My -= lfps[i0-1].My; + Mxx -= lfps[i0-1].Mxx; + Mxy -= lfps[i0-1].Mxy; + Myy -= lfps[i0-1].Myy; + W -= lfps[i0-1].W; + } + + } else { + // i0 > i1, e.g. [15, 2]. Wrap around. + CV_Assert(i0 > 0); + + Mx = lfps[sz-1].Mx - lfps[i0-1].Mx; + My = lfps[sz-1].My - lfps[i0-1].My; + Mxx = lfps[sz-1].Mxx - lfps[i0-1].Mxx; + Mxy = lfps[sz-1].Mxy - lfps[i0-1].Mxy; + Myy = lfps[sz-1].Myy - lfps[i0-1].Myy; + W = lfps[sz-1].W - lfps[i0-1].W; + + Mx += lfps[i1].Mx; + My += lfps[i1].My; + Mxx += lfps[i1].Mxx; + Mxy += lfps[i1].Mxy; + Myy += lfps[i1].Myy; + W += lfps[i1].W; + + N = sz - i0 + i1 + 1; + } + + CV_Assert(N >= 2); + + double Ex = Mx / W; + double Ey = My / W; + double Cxx = Mxx / W - Ex*Ex; + double Cxy = Mxy / W - Ex*Ey; + double Cyy = Myy / W - Ey*Ey; + + double nx, ny; + + if (1) { + // on iOS about 5% of total CPU spent in these trig functions. + // 85 ms per frame on 5S, example.pnm + // + // XXX this was using the double-precision atan2. Was there a case where + // we needed that precision? Seems doubtful. + float normal_theta = float(.5f * (CV_PI / 180)) * cv::fastAtan2((float)(-2*Cxy), (float)(Cyy - Cxx)); + nx = cosf(normal_theta); + ny = sinf(normal_theta); + } else { + // 73.5 ms per frame on 5S, example.pnm + double ty = -2*Cxy; + double tx = (Cyy - Cxx); + double mag = ty*ty + tx*tx; + + if (mag == 0) { + nx = 1; + ny = 0; + } else { + float norm = sqrtf((float)(ty*ty + tx*tx)); + tx /= norm; + + // ty is now sin(2theta) + // tx is now cos(2theta). We want sin(theta) and cos(theta) + + // due to precision err, tx could still have slightly too large magnitude. + if (tx > 1) { + ny = 0; + nx = 1; + } else if (tx < -1) { + ny = 1; + nx = 0; + } else { + // half angle formula + ny = sqrtf((1.0f - (float)tx)*0.5f); + nx = sqrtf((1.0f + (float)tx)*0.5f); + + // pick a consistent branch cut + if (ty < 0) + ny = - ny; + } + } + } + + if (lineparm) { + lineparm[0] = Ex; + lineparm[1] = Ey; + lineparm[2] = nx; + lineparm[3] = ny; + } + + // sum of squared errors = + // + // SUM_i ((p_x - ux)*nx + (p_y - uy)*ny)^2 + // SUM_i nx*nx*(p_x - ux)^2 + 2nx*ny(p_x -ux)(p_y-uy) + ny*ny*(p_y-uy)*(p_y-uy) + // nx*nx*SUM_i((p_x -ux)^2) + 2nx*ny*SUM_i((p_x-ux)(p_y-uy)) + ny*ny*SUM_i((p_y-uy)^2) + // + // nx*nx*N*Cxx + 2nx*ny*N*Cxy + ny*ny*N*Cyy + + // sum of squared errors + if (err) + *err = nx*nx*N*Cxx + 2*nx*ny*N*Cxy + ny*ny*N*Cyy; + + // mean squared error + if (mse) + *mse = nx*nx*Cxx + 2*nx*ny*Cxy + ny*ny*Cyy; +} + +int err_compare_descending(const void *_a, const void *_b){ + const double *a = (const double*)_a; + const double *b = (const double*)_b; + + return ((*a) < (*b)) ? 1 : -1; +} + +/** + 1. Identify A) white points near a black point and B) black points near a white point. + + 2. Find the connected components within each of the classes above, + yielding clusters of "white-near-black" and + "black-near-white". (These two classes are kept separate). Each + segment has a unique id. + + 3. For every pair of "white-near-black" and "black-near-white" + clusters, find the set of points that are in one and adjacent to the + other. In other words, a "boundary" layer between the two + clusters. (This is actually performed by iterating over the pixels, + rather than pairs of clusters.) Critically, this helps keep nearby + edges from becoming connected. + **/ +int quad_segment_maxima(const DetectorParameters &td, int sz, struct line_fit_pt *lfps, int indices[4]){ + + // ksz: when fitting points, how many points on either side do we consider? + // (actual "kernel" width is 2ksz). + // + // This value should be about: 0.5 * (points along shortest edge). + // + // If all edges were equally-sized, that would give a value of + // sz/8. We make it somewhat smaller to account for tags at high + // aspects. + + // XXX Tunable. Maybe make a multiple of JPEG block size to increase robustness + // to JPEG compression artifacts? + //int ksz = imin(20, sz / 12); + int ksz = 20 < (sz/12)? 20: (sz/12); + + // can't fit a quad if there are too few points. + if (ksz < 2) + return 0; + + // printf("sz %5d, ksz %3d\n", sz, ksz); + + std::vector errs(sz); + + for (int i = 0; i < sz; i++) { + fit_line(lfps, sz, (i + sz - ksz) % sz, (i + ksz) % sz, NULL, &errs[i], NULL); + } + + // apply a low-pass filter to errs + if (1) { + std::vector y(sz); + + // how much filter to apply? + + // XXX Tunable + double sigma = 1; // was 3 + + // cutoff = exp(-j*j/(2*sigma*sigma)); + // log(cutoff) = -j*j / (2*sigma*sigma) + // log(cutoff)*2*sigma*sigma = -j*j; + + // how big a filter should we use? We make our kernel big + // enough such that we represent any values larger than + // 'cutoff'. + + // XXX Tunable (though not super useful to change) + double cutoff = 0.05; + int fsz = cvFloor(sqrt(-log(cutoff)*2*sigma*sigma)) + 1; + fsz = 2*fsz + 1; + + // For default values of cutoff = 0.05, sigma = 3, + // we have fsz = 17. + std::vector f(fsz); + + for (int i = 0; i < fsz; i++) { + int j = i - fsz / 2; + f[i] = (float)exp(-j*j/(2*sigma*sigma)); + } + + for (int iy = 0; iy < sz; iy++) { + double acc = 0; + + for (int i = 0; i < fsz; i++) { + acc += errs[(iy + i - fsz / 2 + sz) % sz] * f[i]; + } + y[iy] = acc; + } + copy(y.begin(), y.end(), errs.begin()); + } + + std::vector maxima(sz); + std::vector maxima_errs(sz); + int nmaxima = 0; + + for (int i = 0; i < sz; i++) { + if (errs[i] > errs[(i+1)%sz] && errs[i] > errs[(i+sz-1)%sz]) { + maxima[nmaxima] = i; + maxima_errs[nmaxima] = errs[i]; + nmaxima++; + } + } + // if we didn't get at least 4 maxima, we can't fit a quad. + if (nmaxima < 4) + return 0; + + // select only the best maxima if we have too many + int max_nmaxima = td.aprilTagMaxNmaxima; + + if (nmaxima > max_nmaxima) { + std::vector maxima_errs_copy(maxima_errs.begin(), maxima_errs.begin()+nmaxima); + + // throw out all but the best handful of maxima. Sorts descending. + qsort(maxima_errs_copy.data(), nmaxima, sizeof(double), err_compare_descending); + + double maxima_thresh = maxima_errs_copy[max_nmaxima]; + int out = 0; + for (int in = 0; in < nmaxima; in++) { + if (maxima_errs[in] <= maxima_thresh) + continue; + maxima[out++] = maxima[in]; + } + nmaxima = out; + } + + int best_indices[4]; + double best_error = HUGE_VALF; + + double err01, err12, err23, err30; + double mse01, mse12, mse23, mse30; + double params01[4], params12[4], params23[4], params30[4]; + + // disallow quads where the angle is less than a critical value. + double max_dot = cos(td.aprilTagCriticalRad); //25*M_PI/180); + + for (int m0 = 0; m0 < nmaxima - 3; m0++) { + int i0 = maxima[m0]; + + for (int m1 = m0+1; m1 < nmaxima - 2; m1++) { + int i1 = maxima[m1]; + + fit_line(lfps, sz, i0, i1, params01, &err01, &mse01); + + if (mse01 > td.aprilTagMaxLineFitMse) + continue; + + for (int m2 = m1+1; m2 < nmaxima - 1; m2++) { + int i2 = maxima[m2]; + + fit_line(lfps, sz, i1, i2, params12, &err12, &mse12); + if (mse12 > td.aprilTagMaxLineFitMse) + continue; + + double dot = params01[2]*params12[2] + params01[3]*params12[3]; + if (fabs(dot) > max_dot) + continue; + + for (int m3 = m2+1; m3 < nmaxima; m3++) { + int i3 = maxima[m3]; + + fit_line(lfps, sz, i2, i3, params23, &err23, &mse23); + if (mse23 > td.aprilTagMaxLineFitMse) + continue; + + fit_line(lfps, sz, i3, i0, params30, &err30, &mse30); + if (mse30 > td.aprilTagMaxLineFitMse) + continue; + + double err = err01 + err12 + err23 + err30; + if (err < best_error) { + best_error = err; + best_indices[0] = i0; + best_indices[1] = i1; + best_indices[2] = i2; + best_indices[3] = i3; + } + } + } + } + } + + if (best_error == HUGE_VALF) + return 0; + + for (int i = 0; i < 4; i++) + indices[i] = best_indices[i]; + + if (best_error / sz < td.aprilTagMaxLineFitMse) + return 1; + return 0; +} + +/** + * returns 0 if the cluster looks bad. + */ +int quad_segment_agg(int sz, struct line_fit_pt *lfps, int indices[4]){ + //int sz = zarray_size(cluster); + + zmaxheap_t *heap = zmaxheap_create(sizeof(struct remove_vertex*)); + + // We will initially allocate sz rvs. We then have two types of + // iterations: some iterations that are no-ops in terms of + // allocations, and those that remove a vertex and allocate two + // more children. This will happen at most (sz-4) times. Thus we + // need: sz + 2*(sz-4) entries. + + int rvalloc_pos = 0; + int rvalloc_size = 3*sz; + cv::AutoBuffer rvalloc_(std::max(1, rvalloc_size)); + memset(rvalloc_.data(), 0, sizeof(rvalloc_[0]) * rvalloc_.size()); // TODO Add AutoBuffer zero fill + struct remove_vertex *rvalloc = rvalloc_.data(); + cv::AutoBuffer segs_(std::max(1, sz)); // TODO Add AutoBuffer zero fill + memset(segs_.data(), 0, sizeof(segs_[0]) * segs_.size()); + struct segment *segs = segs_.data(); + + // populate with initial entries + for (int i = 0; i < sz; i++) { + struct remove_vertex *rv = &rvalloc[rvalloc_pos++]; + rv->i = i; + if (i == 0) { + rv->left = sz-1; + rv->right = 1; + } else { + rv->left = i-1; + rv->right = (i+1) % sz; + } + + fit_line(lfps, sz, rv->left, rv->right, NULL, NULL, &rv->err); + + //TODO is finite CV_Assert(): + CV_DbgAssert (!cvIsNaN(-rv->err) && "zmaxheap_add: Trying to add non-finite number to heap. NaN's prohibited, could allow INF with testing"); + zmaxheap_add(heap, &rv, (float)-rv->err); + + segs[i].left = rv->left; + segs[i].right = rv->right; + segs[i].is_vertex = 1; + } + + int nvertices = sz; + + while (nvertices > 4) { + CV_Assert(rvalloc_pos < rvalloc_size); + + struct remove_vertex *rv; + float err; + + int res = zmaxheap_remove_max(heap, &rv, &err); + if (!res) + return 0; + CV_Assert(res); + + // is this remove_vertex valid? (Or has one of the left/right + // vertices changes since we last looked?) + if (!segs[rv->i].is_vertex || + !segs[rv->left].is_vertex || + !segs[rv->right].is_vertex) { + continue; + } + + // we now merge. + CV_Assert(segs[rv->i].is_vertex); + + segs[rv->i].is_vertex = 0; + segs[rv->left].right = rv->right; + segs[rv->right].left = rv->left; + + // create the join to the left + if (1) { + struct remove_vertex *child = &rvalloc[rvalloc_pos++]; + child->i = rv->left; + child->left = segs[rv->left].left; + child->right = rv->right; + + fit_line(lfps, sz, child->left, child->right, NULL, NULL, &child->err); + + //TODO is finite CV_Assert(): + CV_DbgAssert (!cvIsNaN(-child->err) && "zmaxheap_add: Trying to add non-finite number to heap. NaN's prohibited, could allow INF with testing"); + zmaxheap_add(heap, &child, (float)-child->err); + } + + // create the join to the right + if (1) { + struct remove_vertex *child = &rvalloc[rvalloc_pos++]; + child->i = rv->right; + child->left = rv->left; + child->right = segs[rv->right].right; + + fit_line(lfps, sz, child->left, child->right, NULL, NULL, &child->err); + + //TODO is finite CV_Assert(): + CV_DbgAssert (!cvIsNaN(-child->err) && "zmaxheap_add: Trying to add non-finite number to heap. NaN's prohibited, could allow INF with testing"); + zmaxheap_add(heap, &child, (float)-child->err); + } + + // we now have one less vertex + nvertices--; + } + + zmaxheap_destroy(heap); + + int idx = 0; + for (int i = 0; i < sz; i++) { + if (segs[i].is_vertex) { + indices[idx++] = i; + } + } + + return 1; +} + +#define DO_UNIONFIND(dx, dy) if (im.data[y*s + dy*s + x + dx] == v) unionfind_connect(uf, y*w + x, y*w + dy*w + x + dx); +static void do_unionfind_line(unionfind_t *uf, Mat &im, int w, int s, int y){ + CV_Assert(y+1 < im.rows); + CV_Assert(!im.empty()); + + for (int x = 1; x < w - 1; x++) { + uint8_t v = im.data[y*s + x]; + + if (v == 127) + continue; + + // (dx,dy) pairs for 8 connectivity: + // (REFERENCE) (1, 0) + // (-1, 1) (0, 1) (1, 1) + // + DO_UNIONFIND(1, 0); + DO_UNIONFIND(0, 1); + if (v == 255) { + DO_UNIONFIND(-1, 1); + DO_UNIONFIND(1, 1); + } + } +} +#undef DO_UNIONFIND + +/** + * return 1 if the quad looks okay, 0 if it should be discarded + * quad + **/ +int fit_quad(const DetectorParameters &_params, const Mat im, zarray_t *cluster, struct sQuad *quad){ + CV_Assert(cluster != NULL); + + int res = 0; + + int sz = _zarray_size(cluster); + if (sz < 4) // can't fit a quad to less than 4 points + return 0; + + ///////////////////////////////////////////////////////////// + // Step 1. Sort points so they wrap around the center of the + // quad. We will constrain our quad fit to simply partition this + // ordered set into 4 groups. + + // compute a bounding box so that we can order the points + // according to their angle WRT the center. + int32_t xmax = 0, xmin = INT32_MAX, ymax = 0, ymin = INT32_MAX; + + for (int pidx = 0; pidx < sz; pidx++) { + struct pt *p; + _zarray_get_volatile(cluster, pidx, &p); + + //(a > b) ? a : b; + //xmax = imax(xmax, p->x); + //xmin = imin(xmin, p->x); + //ymax = imax(ymax, p->y); + //ymin = imin(ymin, p->y); + + xmax = xmax > p->x? xmax : p->x; + xmin = xmin < p->x? xmin : p->x; + + ymax = ymax > p->y? ymax : p->y; + ymin = ymin < p->y? ymin : p->y; + } + + // add some noise to (cx,cy) so that pixels get a more diverse set + // of theta estimates. This will help us remove more points. + // (Only helps a small amount. The actual noise values here don't + // matter much at all, but we want them [-1, 1]. (XXX with + // fixed-point, should range be bigger?) + double cx = (xmin + xmax) * 0.5 + 0.05118; + double cy = (ymin + ymax) * 0.5 + -0.028581; + + double dot = 0; + + for (int pidx = 0; pidx < sz; pidx++) { + struct pt *p; + _zarray_get_volatile(cluster, pidx, &p); + + double dx = p->x - cx; + double dy = p->y - cy; + + p->theta = cv::fastAtan2((float)dy, (float)dx) * (float)(CV_PI/180); + + dot += dx*p->gx + dy*p->gy; + } + + // Ensure that the black border is inside the white border. + if (dot < 0) + return 0; + + // we now sort the points according to theta. This is a preparatory + // step for segmenting them into four lines. + if (1) { + // zarray_sort(cluster, pt_compare_theta); + ptsort((struct pt*) cluster->data, sz); + + // remove duplicate points. (A byproduct of our segmentation system.) + if (1) { + int outpos = 1; + + struct pt *last; + _zarray_get_volatile(cluster, 0, &last); + + for (int i = 1; i < sz; i++) { + + struct pt *p; + _zarray_get_volatile(cluster, i, &p); + + if (p->x != last->x || p->y != last->y) { + + if (i != outpos) { + struct pt *out; + _zarray_get_volatile(cluster, outpos, &out); + memcpy(out, p, sizeof(struct pt)); + } + + outpos++; + } + + last = p; + } + + cluster->size = outpos; + sz = outpos; + } + + } else { + // This is a counting sort in which we retain at most one + // point for every bucket; the bucket index is computed from + // theta. Since a good quad completes a complete revolution, + // there's reason to think that we should get a good + // distribution of thetas. We might "lose" a few points due + // to collisions, but this shouldn't affect quality very much. + + // XXX tunable. Increase to reduce the likelihood of "losing" + // points due to collisions. + int nbuckets = 4*sz; + +#define ASSOC 2 + std::vector > v(nbuckets, std::vector(ASSOC)); + + // put each point into a bucket. + for (int i = 0; i < sz; i++) { + struct pt *p; + _zarray_get_volatile(cluster, i, &p); + + CV_Assert(p->theta >= -CV_PI && p->theta <= CV_PI); + + int bucket = cvFloor((nbuckets - 1) * (p->theta + CV_PI) / (2*CV_PI)); + CV_Assert(bucket >= 0 && bucket < nbuckets); + + for (int j = 0; j < ASSOC; j++) { + if (v[bucket][j].theta == 0) { + v[bucket][j] = *p; + break; + } + } + } + + // collect the points from the buckets and put them back into the array. + int outsz = 0; + for (int i = 0; i < nbuckets; i++) { + for (int j = 0; j < ASSOC; j++) { + if (v[i][j].theta != 0) { + _zarray_set(cluster, outsz, &v[i][j], NULL); + outsz++; + } + } + } + + _zarray_truncate(cluster, outsz); + sz = outsz; + } + + if (sz < 4) + return 0; + + ///////////////////////////////////////////////////////////// + // Step 2. Precompute statistics that allow line fit queries to be + // efficiently computed for any contiguous range of indices. + + cv::AutoBuffer lfps_(sz); + memset(lfps_.data(), 0, sizeof(lfps_[0]) * lfps_.size()); // TODO Add AutoBuffer zero fill + struct line_fit_pt *lfps = lfps_.data(); + + for (int i = 0; i < sz; i++) { + struct pt *p; + _zarray_get_volatile(cluster, i, &p); + + if (i > 0) { + memcpy(&lfps[i], &lfps[i-1], sizeof(struct line_fit_pt)); + } + + if (0) { + // we now undo our fixed-point arithmetic. + double delta = 0.5; + double x = p->x * .5 + delta; + double y = p->y * .5 + delta; + double W; + + for (int dy = -1; dy <= 1; dy++) { + int iy = cvFloor(y + dy); + + if (iy < 0 || iy + 1 >= im.rows) + continue; + + for (int dx = -1; dx <= 1; dx++) { + int ix = cvFloor(x + dx); + + if (ix < 0 || ix + 1 >= im.cols) + continue; + + int grad_x = im.data[iy * im.cols + ix + 1] - + im.data[iy * im.cols + ix - 1]; + + int grad_y = im.data[(iy+1) * im.cols + ix] - + im.data[(iy-1) * im.cols + ix]; + + W = sqrtf(float(grad_x*grad_x + grad_y*grad_y)) + 1; + + // double fx = x + dx, fy = y + dy; + double fx = ix + .5, fy = iy + .5; + lfps[i].Mx += W * fx; + lfps[i].My += W * fy; + lfps[i].Mxx += W * fx * fx; + lfps[i].Mxy += W * fx * fy; + lfps[i].Myy += W * fy * fy; + lfps[i].W += W; + } + } + } else { + // we now undo our fixed-point arithmetic. + double delta = 0.5; // adjust for pixel center bias + double x = p->x * .5 + delta; + double y = p->y * .5 + delta; + int ix = cvFloor(x), iy = cvFloor(y); + double W = 1; + + if (ix > 0 && ix+1 < im.cols && iy > 0 && iy+1 < im.rows) { + int grad_x = im.data[iy * im.cols + ix + 1] - + im.data[iy * im.cols + ix - 1]; + + int grad_y = im.data[(iy+1) * im.cols + ix] - + im.data[(iy-1) * im.cols + ix]; + + // XXX Tunable. How to shape the gradient magnitude? + W = sqrt(grad_x*grad_x + grad_y*grad_y) + 1; + } + + double fx = x, fy = y; + lfps[i].Mx += W * fx; + lfps[i].My += W * fy; + lfps[i].Mxx += W * fx * fx; + lfps[i].Mxy += W * fx * fy; + lfps[i].Myy += W * fy * fy; + lfps[i].W += W; + } + } + + int indices[4]; + if (1) { + if (!quad_segment_maxima(_params, _zarray_size(cluster), lfps, indices)) + goto finish; + } else { + if (!quad_segment_agg(sz, lfps, indices)) + goto finish; + } + + // printf("%d %d %d %d\n", indices[0], indices[1], indices[2], indices[3]); + + if (0) { + // no refitting here; just use those points as the vertices. + // Note, this is useful for debugging, but pretty bad in + // practice since this code path also omits several + // plausibility checks that save us tons of time in quad + // decoding. + for (int i = 0; i < 4; i++) { + struct pt *p; + _zarray_get_volatile(cluster, indices[i], &p); + + quad->p[i][0] = (float)(.5*p->x); // undo fixed-point arith. + quad->p[i][1] = (float)(.5*p->y); + } + + res = 1; + + } else { + double lines[4][4]; + + for (int i = 0; i < 4; i++) { + int i0 = indices[i]; + int i1 = indices[(i+1)&3]; + + if (0) { + // if there are enough points, skip the points near the corners + // (because those tend not to be very good.) + if (i1-i0 > 8) { + int t = (i1-i0)/6; + if (t < 0) + t = -t; + + i0 = (i0 + t) % sz; + i1 = (i1 + sz - t) % sz; + } + } + + double err; + fit_line(lfps, sz, i0, i1, lines[i], NULL, &err); + + if (err > _params.aprilTagMaxLineFitMse) { + res = 0; + goto finish; + } + } + + for (int i = 0; i < 4; i++) { + // solve for the intersection of lines (i) and (i+1)&3. + // p0 + lambda0*u0 = p1 + lambda1*u1, where u0 and u1 + // are the line directions. + // + // lambda0*u0 - lambda1*u1 = (p1 - p0) + // + // rearrange (solve for lambdas) + // + // [u0_x -u1_x ] [lambda0] = [ p1_x - p0_x ] + // [u0_y -u1_y ] [lambda1] [ p1_y - p0_y ] + // + // remember that lines[i][0,1] = p, lines[i][2,3] = NORMAL vector. + // We want the unit vector, so we need the perpendiculars. Thus, below + // we have swapped the x and y components and flipped the y components. + + const int i1 = (i + 1) & 3; + double A00 = lines[i][3], A01 = -lines[i1][3]; + double A10 = -lines[i][2], A11 = lines[i1][2]; + double B0 = -lines[i][0] + lines[i1][0]; + double B1 = -lines[i][1] + lines[i1][1]; + + double det = A00 * A11 - A10 * A01; + if (fabs(det) < 0.001) { + res = 0; + goto finish; + } + + // inverse. + double det_inv = 1.0 / det; + double W00 = A11 * det_inv, W01 = -A01 * det_inv; + + // solve + double L0 = W00*B0 + W01*B1; + + // compute intersection + quad->p[i][0] = (float)(lines[i][0] + L0*A00); + quad->p[i][1] = (float)(lines[i][1] + L0*A10); + +#if !defined(NDEBUG) + { + // we should get the same intersection starting + // from point p1 and moving L1*u1. + double W10 = -A10 * det_inv, W11 = A00 * det_inv; + double L1 = W10*B0 + W11*B1; + + double x = lines[i1][0] - L1*A01; + double y = lines[i1][1] - L1*A11; + + CV_Assert(fabs(x - quad->p[i][0]) < 0.001); + CV_Assert(fabs(y - quad->p[i][1]) < 0.001); + } +#endif // NDEBUG + + res = 1; + } + } + + // reject quads that are too small + if (1) { + double area = 0; + + // get area of triangle formed by points 0, 1, 2, 0 + double length[3], p; + for (int i = 0; i < 3; i++) { + int idxa = i; // 0, 1, 2, + int idxb = (i+1) % 3; // 1, 2, 0 + //length[i] = sqrt( + // sq(quad->p[idxb][0] - quad->p[idxa][0]) + // + sq(quad->p[idxb][1] - quad->p[idxa][1])); + double sq1 = quad->p[idxb][0] - quad->p[idxa][0]; + sq1 = sq1 * sq1; + double sq2 = quad->p[idxb][1] - quad->p[idxa][1]; + sq2 = sq2 * sq2; + length[i] = sqrt(sq1 + sq2); + } + p = (length[0] + length[1] + length[2]) / 2; + + area += sqrt(p*(p-length[0])*(p-length[1])*(p-length[2])); + + // get area of triangle formed by points 2, 3, 0, 2 + for (int i = 0; i < 3; i++) { + int idxs[] = { 2, 3, 0, 2 }; + int idxa = idxs[i]; + int idxb = idxs[i+1]; + //length[i] = sqrt( + // sq(quad->p[idxb][0] - quad->p[idxa][0]) + // + sq(quad->p[idxb][1] - quad->p[idxa][1])); + double sq1 = quad->p[idxb][0] - quad->p[idxa][0]; + sq1 = sq1 * sq1; + double sq2 = quad->p[idxb][1] - quad->p[idxa][1]; + sq2 = sq2 * sq2; + length[i] = sqrt(sq1 + sq2); + } + p = (length[0] + length[1] + length[2]) / 2; + + area += sqrt(p*(p-length[0])*(p-length[1])*(p-length[2])); + + // we don't actually know the family yet (quad detection is generic.) + // This threshold is based on a 6x6 tag (which is actually 8x8) + // int d = fam->d + fam->black_border*2; + int d = 8; + if (area < d*d) { + res = 0; + goto finish; + } + } + + // reject quads whose cumulative angle change isn't equal to 2PI + if(1){ + double total = 0; + + for (int i = 0; i < 4; i++) { + int i0 = i, i1 = (i+1)&3, i2 = (i+2)&3; + + double theta0 = atan2f(quad->p[i0][1] - quad->p[i1][1], + quad->p[i0][0] - quad->p[i1][0]); + double theta1 = atan2f(quad->p[i2][1] - quad->p[i1][1], + quad->p[i2][0] - quad->p[i1][0]); + + double dtheta = theta0 - theta1; + if (dtheta < 0) + dtheta += 2*CV_PI; + + if (dtheta < _params.aprilTagCriticalRad || dtheta > (CV_PI - _params.aprilTagCriticalRad)) + res = 0; + + total += dtheta; + } + + // looking for 2PI + if (total < 6.2 || total > 6.4) { + res = 0; + goto finish; + } + } + + finish: + + return res; +} + + +static void do_quad(int nCidx0, int nCidx1, zarray_t &nClusters, int nW, int nH, zarray_t *nquads, const DetectorParameters &td, const Mat im){ + + CV_Assert(nquads != NULL); + + //struct quad_task *task = (struct quad_task*) p; + + //zarray_t *clusters = nClusters; + zarray_t *quads = nquads; + int w = nW, h = nH; + + for (int cidx = nCidx0; cidx < nCidx1; cidx++) { + + zarray_t *cluster; + _zarray_get(&nClusters, cidx, &cluster); + + if (_zarray_size(cluster) < td.aprilTagMinClusterPixels) + continue; + + // a cluster should contain only boundary points around the + // tag. it cannot be bigger than the whole screen. (Reject + // large connected blobs that will be prohibitively slow to + // fit quads to.) A typical point along an edge is added three + // times (because it has 3 neighbors). The maximum perimeter + // is 2w+2h. + if (_zarray_size(cluster) > 3*(2*w+2*h)) { + continue; + } + + struct sQuad quad; + memset(&quad, 0, sizeof(struct sQuad)); + + if (fit_quad(td, im, cluster, &quad)) { + //pthread_mutex_lock(&td.mutex); + _zarray_add(quads, &quad); + //pthread_mutex_unlock(&td.mutex); + } + } +} + +void threshold(const Mat mIm, const DetectorParameters ¶meters, Mat& mThresh){ + int w = mIm.cols, h = mIm.rows; + int s = (unsigned) mIm.step; + CV_Assert(w < 32768); + CV_Assert(h < 32768); + + CV_Assert(mThresh.step == (unsigned)s); + + // The idea is to find the maximum and minimum values in a + // window around each pixel. If it's a contrast-free region + // (max-min is small), don't try to binarize. Otherwise, + // threshold according to (max+min)/2. + // + // Mark low-contrast regions with value 127 so that we can skip + // future work on these areas too. + + // however, computing max/min around every pixel is needlessly + // expensive. We compute max/min for tiles. To avoid artifacts + // that arise when high-contrast features appear near a tile + // edge (and thus moving from one tile to another results in a + // large change in max/min value), the max/min values used for + // any pixel are computed from all 3x3 surrounding tiles. Thus, + // the max/min sampling area for nearby pixels overlap by at least + // one tile. + // + // The important thing is that the windows be large enough to + // capture edge transitions; the tag does not need to fit into + // a tile. + + // XXX Tunable. Generally, small tile sizes--- so long as they're + // large enough to span a single tag edge--- seem to be a winner. + const int tilesz = 4; + + // the last (possibly partial) tiles along each row and column will + // just use the min/max value from the last full tile. + int tw = w / tilesz; + int th = h / tilesz; + + uint8_t *im_max = (uint8_t*)calloc(tw*th, sizeof(uint8_t)); + uint8_t *im_min = (uint8_t*)calloc(tw*th, sizeof(uint8_t)); + + + // first, collect min/max statistics for each tile + for (int ty = 0; ty < th; ty++) { + for (int tx = 0; tx < tw; tx++) { + uint8_t max = 0, min = 255; + + for (int dy = 0; dy < tilesz; dy++) { + + for (int dx = 0; dx < tilesz; dx++) { + + uint8_t v = mIm.data[(ty*tilesz+dy)*s + tx*tilesz + dx]; + if (v < min) + min = v; + if (v > max) + max = v; + } + } + im_max[ty*tw+tx] = max; + im_min[ty*tw+tx] = min; + } + } + + // second, apply 3x3 max/min convolution to "blur" these values + // over larger areas. This reduces artifacts due to abrupt changes + // in the threshold value. + uint8_t *im_max_tmp = (uint8_t*)calloc(tw*th, sizeof(uint8_t)); + uint8_t *im_min_tmp = (uint8_t*)calloc(tw*th, sizeof(uint8_t)); + + for (int ty = 0; ty < th; ty++) { + for (int tx = 0; tx < tw; tx++) { + uint8_t max = 0, min = 255; + + for (int dy = -1; dy <= 1; dy++) { + if (ty+dy < 0 || ty+dy >= th) + continue; + for (int dx = -1; dx <= 1; dx++) { + if (tx+dx < 0 || tx+dx >= tw) + continue; + + uint8_t m = im_max[(ty+dy)*tw+tx+dx]; + if (m > max) + max = m; + m = im_min[(ty+dy)*tw+tx+dx]; + if (m < min) + min = m; + } + } + + im_max_tmp[ty*tw + tx] = max; + im_min_tmp[ty*tw + tx] = min; + } + } + free(im_max); + free(im_min); + im_max = im_max_tmp; + im_min = im_min_tmp; + + for (int ty = 0; ty < th; ty++) { + for (int tx = 0; tx < tw; tx++) { + + int min_ = im_min[ty*tw + tx]; + int max_ = im_max[ty*tw + tx]; + + // low contrast region? (no edges) + if (max_ - min_ < parameters.aprilTagMinWhiteBlackDiff) { + for (int dy = 0; dy < tilesz; dy++) { + int y = ty*tilesz + dy; + + for (int dx = 0; dx < tilesz; dx++) { + int x = tx*tilesz + dx; + + //threshim->buf[y*s+x] = 127; + mThresh.data[y*s+x] = 127; + } + } + continue; + } + + // otherwise, actually threshold this tile. + + // argument for biasing towards dark; specular highlights + // can be substantially brighter than white tag parts + uint8_t thresh = saturate_cast((max_ + min_) / 2); + + for (int dy = 0; dy < tilesz; dy++) { + int y = ty*tilesz + dy; + + for (int dx = 0; dx < tilesz; dx++) { + int x = tx*tilesz + dx; + + uint8_t v = mIm.data[y*s+x]; + mThresh.data[y*s+x] = (v > thresh) ? 255 : 0; + } + } + } + } + + // we skipped over the non-full-sized tiles above. Fix those now. + for (int y = 0; y < h; y++) { + + // what is the first x coordinate we need to process in this row? + + int x0; + + if (y >= th*tilesz) { + x0 = 0; // we're at the bottom; do the whole row. + } else { + x0 = tw*tilesz; // we only need to do the right most part. + } + + // compute tile coordinates and clamp. + int ty = y / tilesz; + if (ty >= th) + ty = th - 1; + + for (int x = x0; x < w; x++) { + int tx = x / tilesz; + if (tx >= tw) + tx = tw - 1; + + int max = im_max[ty*tw + tx]; + int min = im_min[ty*tw + tx]; + int thresh = min + (max - min) / 2; + + uint8_t v = mIm.data[y*s+x]; + if (v > thresh){ + mThresh.data[y*s+x] = 255; + } + else{ + mThresh.data[y*s+x] = 0; + } + } + } + free(im_min); + free(im_max); + + // this is a dilate/erode deglitching scheme that does not improve + // anything as far as I can tell. + if (parameters.aprilTagDeglitch) { + Mat tmp(h,w, mIm.type()); + for (int y = 1; y + 1 < h; y++) { + for (int x = 1; x + 1 < w; x++) { + uint8_t max = 0; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + uint8_t v = mThresh.data[(y+dy)*s + x + dx]; + if (v > max) + max = v; + } + } + tmp.data[y*s+x] = max; + } + } + + for (int y = 1; y + 1 < h; y++) { + for (int x = 1; x + 1 < w; x++) { + uint8_t min = 255; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + uint8_t v = tmp.data[(y+dy)*s + x + dx]; + if (v < min) + min = v; + } + } + mThresh.data[y*s+x] = min; + } + } + } + +} + +#ifdef APRIL_DEBUG +static void _darken(const Mat &im){ + for (int y = 0; y < im.rows; y++) { + for (int x = 0; x < im.cols; x++) { + im.data[im.cols*y+x] /= 2; + } + } +} +#endif + +zarray_t *apriltag_quad_thresh(const DetectorParameters ¶meters, const Mat & mImg, std::vector > &contours){ + + //////////////////////////////////////////////////////// + // step 1. threshold the image, creating the edge image. + + int w = mImg.cols, h = mImg.rows; + + Mat thold(h, w, mImg.type()); + threshold(mImg, parameters, thold); + + int ts = thold.cols; + +#ifdef APRIL_DEBUG + imwrite("2.2 debug_threshold.pnm", thold); +#endif + + //////////////////////////////////////////////////////// + // step 2. find connected components. + + unionfind_t *uf = unionfind_create(w * h); + + // TODO PARALLELIZE + for (int y = 0; y < h - 1; y++) { + do_unionfind_line(uf, thold, w, ts, y); + } + + // XXX sizing?? + int nclustermap = 2*w*h - 1; + + struct uint64_zarray_entry **clustermap = (struct uint64_zarray_entry**)calloc(nclustermap, sizeof(struct uint64_zarray_entry*)); + + for (int y = 1; y < h-1; y++) { + for (int x = 1; x < w-1; x++) { + + uint8_t v0 = thold.data[y*ts + x]; + if (v0 == 127) + continue; + + // XXX don't query this until we know we need it? + uint64_t rep0 = unionfind_get_representative(uf, y*w + x); + + // whenever we find two adjacent pixels such that one is + // white and the other black, we add the point half-way + // between them to a cluster associated with the unique + // ids of the white and black regions. + // + // We additionally compute the gradient direction (i.e., which + // direction was the white pixel?) Note: if (v1-v0) == 255, then + // (dx,dy) points towards the white pixel. if (v1-v0) == -255, then + // (dx,dy) points towards the black pixel. p.gx and p.gy will thus + // be -255, 0, or 255. + // + // Note that any given pixel might be added to multiple + // different clusters. But in the common case, a given + // pixel will be added multiple times to the same cluster, + // which increases the size of the cluster and thus the + // computational costs. + // + // A possible optimization would be to combine entries + // within the same cluster. + +#define DO_CONN(dx, dy) \ + if (1) { \ + uint8_t v1 = thold.data[y*ts + dy*ts + x + dx]; \ + \ + if (v0 + v1 == 255) { \ + uint64_t rep1 = unionfind_get_representative(uf, y*w + dy*w + x + dx); \ + uint64_t clusterid; \ + if (rep0 < rep1) \ + clusterid = (rep1 << 32) + rep0; \ + else \ + clusterid = (rep0 << 32) + rep1; \ + \ + /* XXX lousy hash function */ \ + uint32_t clustermap_bucket = u64hash_2(clusterid) % nclustermap; \ + struct uint64_zarray_entry *entry = clustermap[clustermap_bucket]; \ + while (entry && entry->id != clusterid) { \ + entry = entry->next; \ + } \ + \ + if (!entry) { \ + entry = (struct uint64_zarray_entry*)calloc(1, sizeof(struct uint64_zarray_entry)); \ + entry->id = clusterid; \ + entry->cluster = _zarray_create(sizeof(struct pt)); \ + entry->next = clustermap[clustermap_bucket]; \ + clustermap[clustermap_bucket] = entry; \ + } \ + \ + struct pt p; \ + p.x = saturate_cast(2*x + dx); \ + p.y = saturate_cast(2*y + dy); \ + p.gx = saturate_cast(dx*((int) v1-v0)); \ + p.gy = saturate_cast(dy*((int) v1-v0)); \ + _zarray_add(entry->cluster, &p); \ + } \ + } + + // do 4 connectivity. NB: Arguments must be [-1, 1] or we'll overflow .gx, .gy + DO_CONN(1, 0); + DO_CONN(0, 1); + + // do 8 connectivity + DO_CONN(-1, 1); + DO_CONN(1, 1); +} +} +#undef DO_CONN + +#ifdef APRIL_DEBUG +Mat out = Mat::zeros(h, w, CV_8UC3); + +uint32_t *colors = (uint32_t*) calloc(w*h, sizeof(*colors)); + +for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + uint32_t v = unionfind_get_representative(uf, y*w+x); + + if (unionfind_get_set_size(uf, v) < parameters->aprilTagMinClusterPixels) + continue; + + uint32_t color = colors[v]; + uint8_t r = color >> 16, + g = color >> 8, + b = color; + + if (color == 0) { + const int bias = 50; + r = bias + (random() % (200-bias)); + g = bias + (random() % (200-bias)); + b = bias + (random() % (200-bias)); + colors[v] = (r << 16) | (g << 8) | b; + } + out.at(y, x)[0]=b; + out.at(y, x)[1]=g; + out.at(y, x)[2]=r; + } +} +free(colors); +imwrite("2.3 debug_segmentation.pnm", out); +out = Mat::zeros(h, w, CV_8UC3); +#endif + + //////////////////////////////////////////////////////// + // step 3. process each connected component. + zarray_t *clusters = _zarray_create(sizeof(zarray_t*)); //, uint64_zarray_hash_size(clustermap)); + CV_Assert(clusters != NULL); + + for (int i = 0; i < nclustermap; i++) { + for (struct uint64_zarray_entry *entry = clustermap[i]; entry; entry = entry->next) { + // XXX reject clusters here? + _zarray_add(clusters, &entry->cluster); + } + } + +#ifdef APRIL_DEBUG +for (int i = 0; i < _zarray_size(clusters); i++) { + zarray_t *cluster; + _zarray_get(clusters, i, &cluster); + + uint32_t r, g, b; + + const int bias = 50; + r = bias + (random() % (200-bias)); + g = bias + (random() % (200-bias)); + b = bias + (random() % (200-bias)); + + for (int j = 0; j < _zarray_size(cluster); j++) { + struct pt *p; + _zarray_get_volatile(cluster, j, &p); + + int x = p->x / 2; + int y = p->y / 2; + out.at(y, x)[0]=b; + out.at(y, x)[1]=g; + out.at(y, x)[2]=r; + } +} + +imwrite("2.4 debug_clusters.pnm", out); +out = Mat::zeros(h, w, CV_8UC3); +#endif + + for (int i = 0; i < _zarray_size(clusters); i++) { + zarray_t *cluster; + _zarray_get(clusters, i, &cluster); + + std::vector cnt; + for (int j = 0; j < _zarray_size(cluster); j++) { + struct pt *p; + _zarray_get_volatile(cluster, j, &p); + + Point pnt(p->x, p->y); + cnt.push_back(pnt); + } + contours.push_back(cnt); + } + + for (int i = 0; i < nclustermap; i++) { + struct uint64_zarray_entry *entry = clustermap[i]; + while (entry) { + struct uint64_zarray_entry *tmp = entry->next; + free(entry); + entry = tmp; + } + } + free(clustermap); + + zarray_t *quads = _zarray_create(sizeof(struct sQuad)); + + //int chunksize = 1 + sz / (APRILTAG_TASKS_PER_THREAD_TARGET * numberOfThreads); + int chunksize = std::max(1, h / (10 * getNumThreads())); + int sz = _zarray_size(clusters); + + // TODO PARALLELIZE + for (int i = 0; i < sz; i += chunksize) { + int min = sz < (i+chunksize)? sz: (i+chunksize); + do_quad(i, min, *clusters, w, h, quads, parameters, mImg); + } + +#ifdef APRIL_DEBUG +mImg.copyTo(out); +_darken(out); +_darken(out); +srandom(0); + +for (int i = 0; i < _zarray_size(quads); i++) { + struct sQuad *quad; + _zarray_get_volatile(quads, i, &quad); + + float rgb[3]; + int bias = 100; + + for (int i = 0; i < 3; i++) + rgb[i] = bias + (random() % (255-bias)); + + line(out, Point(quad->p[0][0], quad->p[0][1]), Point(quad->p[1][0], quad->p[1][1]), rgb[i]); + line(out, Point(quad->p[1][0], quad->p[1][1]), Point(quad->p[2][0], quad->p[2][1]), rgb[i]); + line(out, Point(quad->p[2][0], quad->p[2][1]), Point(quad->p[3][0], quad->p[3][1]), rgb[i]); + line(out, Point(quad->p[3][0], quad->p[3][1]), Point(quad->p[0][0], quad->p[0][1]), rgb[i]); +} +imwrite("2.5 debug_lines.pnm", out); +#endif + + unionfind_destroy(uf); + + for (int i = 0; i < _zarray_size(clusters); i++) { + zarray_t *cluster; + _zarray_get(clusters, i, &cluster); + _zarray_destroy(cluster); + } + _zarray_destroy(clusters); + return quads; +} + +void _apriltag(Mat im_orig, const DetectorParameters & _params, std::vector > &candidates, + std::vector > &contours){ + + /////////////////////////////////////////////////////////// + /// Step 1. Detect quads according to requested image decimation + /// and blurring parameters. + Mat quad_im; + + if (_params.aprilTagQuadDecimate > 1){ + resize(im_orig, quad_im, Size(), 1/_params.aprilTagQuadDecimate, 1/_params.aprilTagQuadDecimate, INTER_AREA); + } + else { + im_orig.copyTo(quad_im); + } + + // Apply a Blur + if (_params.aprilTagQuadSigma != 0) { + // compute a reasonable kernel width by figuring that the + // kernel should go out 2 std devs. + // + // max sigma ksz + // 0.499 1 (disabled) + // 0.999 3 + // 1.499 5 + // 1.999 7 + + float sigma = fabsf((float) _params.aprilTagQuadSigma); + + int ksz = cvFloor(4 * sigma); // 2 std devs in each direction + ksz |= 1; // make odd number + + if (ksz > 1) { + if (_params.aprilTagQuadSigma > 0) + GaussianBlur(quad_im, quad_im, Size(ksz, ksz), sigma, sigma, BORDER_REPLICATE); + else { + Mat orig; + quad_im.copyTo(orig); + GaussianBlur(quad_im, quad_im, Size(ksz, ksz), sigma, sigma, BORDER_REPLICATE); + + // SHARPEN the image by subtracting the low frequency components. + for (int y = 0; y < orig.rows; y++) { + for (int x = 0; x < orig.cols; x++) { + int vorig = orig.data[y*orig.step + x]; + int vblur = quad_im.data[y*quad_im.step + x]; + + int v = 2*vorig - vblur; + if (v < 0) + v = 0; + if (v > 255) + v = 255; + + quad_im.data[y*quad_im.step + x] = (uint8_t) v; + } + } + } + } + } + +#ifdef APRIL_DEBUG + imwrite("1.1 debug_preprocess.pnm", quad_im); +#endif + + /////////////////////////////////////////////////////////// + /// Step 2. do the Threshold :: get the set of candidate quads + zarray_t *quads = apriltag_quad_thresh(_params, quad_im, contours); + + CV_Assert(quads != NULL); + + // adjust centers of pixels so that they correspond to the + // original full-resolution image. + if (_params.aprilTagQuadDecimate > 1) { + for (int i = 0; i < _zarray_size(quads); i++) { + struct sQuad *q; + _zarray_get_volatile(quads, i, &q); + for (int j = 0; j < 4; j++) { + q->p[j][0] *= _params.aprilTagQuadDecimate; + q->p[j][1] *= _params.aprilTagQuadDecimate; + } + } + } + +#ifdef APRIL_DEBUG + Mat im_quads = im_orig.clone(); + im_quads = im_quads*0.5; + srandom(0); + + for (int i = 0; i < _zarray_size(quads); i++) { + struct sQuad *quad; + _zarray_get_volatile(quads, i, &quad); + + const int bias = 100; + int color = bias + (random() % (255-bias)); + + line(im_quads, Point(quad->p[0][0], quad->p[0][1]), Point(quad->p[1][0], quad->p[1][1]), color, 1); + line(im_quads, Point(quad->p[1][0], quad->p[1][1]), Point(quad->p[2][0], quad->p[2][1]), color, 1); + line(im_quads, Point(quad->p[2][0], quad->p[2][1]), Point(quad->p[3][0], quad->p[3][1]), color, 1); + line(im_quads, Point(quad->p[3][0], quad->p[3][1]), Point(quad->p[0][0], quad->p[0][1]), color, 1); + } + imwrite("1.2 debug_quads_raw.pnm", im_quads); +#endif + + //////////////////////////////////////////////////////////////// + /// Step 3. Save the output :: candidate corners + for (int i = 0; i < _zarray_size(quads); i++) { + struct sQuad *quad; + _zarray_get_volatile(quads, i, &quad); + + std::vector corners; + corners.push_back(Point2f(quad->p[3][0], quad->p[3][1])); //pA + corners.push_back(Point2f(quad->p[0][0], quad->p[0][1])); //pB + corners.push_back(Point2f(quad->p[1][0], quad->p[1][1])); //pC + corners.push_back(Point2f(quad->p[2][0], quad->p[2][1])); //pD + + candidates.push_back(corners); + } + + _zarray_destroy(quads); +} + +}} diff --git a/modules/objdetect/src/aruco/apriltag/apriltag_quad_thresh.hpp b/modules/objdetect/src/aruco/apriltag/apriltag_quad_thresh.hpp new file mode 100644 index 0000000000..bc1334a12d --- /dev/null +++ b/modules/objdetect/src/aruco/apriltag/apriltag_quad_thresh.hpp @@ -0,0 +1,117 @@ +// 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) 2013-2016, The Regents of The University of Michigan. +// +// This software was developed in the APRIL Robotics Lab under the +// direction of Edwin Olson, ebolson@umich.edu. This software may be +// available under alternative licensing terms; contact the address above. +// +// The views and conclusions contained in the software and documentation are those +// of the authors and should not be interpreted as representing official policies, +// either expressed or implied, of the Regents of The University of Michigan. + +// limitation: image size must be <32768 in width and height. This is +// because we use a fixed-point 16 bit integer representation with one +// fractional bit. + +#ifndef _OPENCV_APRIL_QUAD_THRESH_HPP_ +#define _OPENCV_APRIL_QUAD_THRESH_HPP_ + +#include "unionfind.hpp" +#include "zmaxheap.hpp" +#include "zarray.hpp" + +namespace cv { +namespace aruco { + +static inline uint32_t u64hash_2(uint64_t x) { + return uint32_t((2654435761UL * x) >> 32); +} + +struct uint64_zarray_entry{ + uint64_t id; + zarray_t *cluster; + + struct uint64_zarray_entry *next; +}; + +struct pt{ + // Note: these represent 2*actual value. + uint16_t x, y; + float theta; + int16_t gx, gy; +}; + +struct remove_vertex{ + int i; // which vertex to remove? + int left, right; // left vertex, right vertex + + double err; +}; + +struct segment{ + int is_vertex; + + // always greater than zero, but right can be > size, which denotes + // a wrap around back to the beginning of the points. and left < right. + int left, right; +}; + +struct line_fit_pt{ + double Mx, My; + double Mxx, Myy, Mxy; + double W; // total weight +}; + +/** + * lfps contains *cumulative* moments for N points, with + * index j reflecting points [0,j] (inclusive). + * fit a line to the points [i0, i1] (inclusive). i0, i1 are both (0, sz) + * if i1 < i0, we treat this as a wrap around. + */ +void fit_line(struct line_fit_pt *lfps, int sz, int i0, int i1, double *lineparm, double *err, double *mse); + +int err_compare_descending(const void *_a, const void *_b); + +/** + 1. Identify A) white points near a black point and B) black points near a white point. + + 2. Find the connected components within each of the classes above, + yielding clusters of "white-near-black" and + "black-near-white". (These two classes are kept separate). Each + segment has a unique id. + + 3. For every pair of "white-near-black" and "black-near-white" + clusters, find the set of points that are in one and adjacent to the + other. In other words, a "boundary" layer between the two + clusters. (This is actually performed by iterating over the pixels, + rather than pairs of clusters.) Critically, this helps keep nearby + edges from becoming connected. + **/ +int quad_segment_maxima(const DetectorParameters &td, int sz, struct line_fit_pt *lfps, int indices[4]); + +/** + * returns 0 if the cluster looks bad. + */ +int quad_segment_agg(int sz, struct line_fit_pt *lfps, int indices[4]); + +/** + * return 1 if the quad looks okay, 0 if it should be discarded + * quad + **/ +int fit_quad(const DetectorParameters &_params, const Mat im, zarray_t *cluster, struct sQuad *quad); + + +void threshold(const Mat mIm, const DetectorParameters ¶meters, Mat& mThresh); + + +zarray_t *apriltag_quad_thresh(const DetectorParameters ¶meters, const Mat & mImg, + std::vector > &contours); + +void _apriltag(Mat im_orig, const DetectorParameters &_params, std::vector > &candidates, + std::vector > &contours); + +}} +#endif diff --git a/modules/objdetect/src/aruco/apriltag/predefined_dictionaries_apriltag.hpp b/modules/objdetect/src/aruco/apriltag/predefined_dictionaries_apriltag.hpp new file mode 100644 index 0000000000..cad9802f53 --- /dev/null +++ b/modules/objdetect/src/aruco/apriltag/predefined_dictionaries_apriltag.hpp @@ -0,0 +1,14900 @@ +// 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. + +namespace { + +/** + * Dictionaries are stored as a list of bytes in its four rotations. + * On each rotation, the marker is divided in bytes assuming a row-major order. + * This format allows a faster marker identification. + * For a dictionary composed by M markers of NxN bits, the structure dimensions should be: + * const char name[nMarkers][4rotations][nBytes], or more specifically: + * const char name[M][4][ceil(NxN/8)] + * The element [i][j][k] represents the k-th byte of the i-th marker in the dictionary + * in its j-th rotation. + * Each rotation implies a 90 degree rotation of the marker in anticlockwise direction. + */ +static unsigned char DICT_APRILTAG_16h5_BYTES[][4][2] = +{ + { {216, 196}, + {128, 190}, + {35, 27}, + {125, 1} + }, + { {165, 116}, + {106, 120}, + {46, 165}, + {30, 86} + }, + { {86, 44}, + {134, 209}, + {52, 106}, + {139, 97} + }, + { {157, 162}, + {195, 78}, + {69, 185}, + {114, 195} + }, + { {101, 158}, + {105, 211}, + {121, 166}, + {203, 150} + }, + { {214, 254}, + {167, 251}, + {127, 107}, + {223, 229} + }, + { {26, 205}, + {148, 55}, + {179, 88}, + {236, 41} + }, + { {162, 231}, + {31, 58}, + {231, 69}, + {92, 248} + }, + { {154, 127}, + {183, 61}, + {254, 89}, + {188, 237} + }, + { {182, 168}, + {142, 75}, + {21, 109}, + {210, 113} + }, + { {208, 28}, + {160, 153}, + {56, 11}, + {153, 5} + }, + { {213, 15}, + {209, 217}, + {240, 171}, + {155, 139} + }, + { {33, 176}, + {106, 2}, + {13, 132}, + {64, 86} + }, + { {108, 226}, + {11, 230}, + {71, 54}, + {103, 208} + }, + { {78, 49}, + {54, 196}, + {140, 114}, + {35, 108} + }, + { {8, 245}, + {50, 54}, + {175, 16}, + {108, 76} + }, + { {60, 144}, + {168, 70}, + {9, 60}, + {98, 21} + }, + { {45, 201}, + {88, 103}, + {147, 180}, + {230, 26} + }, + { {192, 165}, + {18, 154}, + {165, 3}, + {89, 72} + }, + { {241, 98}, + {203, 168}, + {70, 143}, + {21, 211} + }, + { {236, 135}, + {25, 222}, + {225, 55}, + {123, 152} + }, + { {169, 234}, + {75, 47}, + {87, 149}, + {244, 210} + }, + { {66, 251}, + {55, 163}, + {223, 66}, + {197, 236} + }, + { {184, 56}, + {170, 13}, + {28, 29}, + {176, 85} + }, + { {59, 151}, + {253, 22}, + {233, 220}, + {104, 191} + }, + { {181, 206}, + {201, 123}, + {115, 173}, + {222, 147} + }, + { {250, 181}, + {190, 158}, + {173, 95}, + {121, 125} + }, + { {12, 171}, + {19, 71}, + {213, 48}, + {226, 200} + }, + { {83, 224}, + {198, 162}, + {7, 202}, + {69, 99} + }, + { {116, 245}, + {186, 242}, + {175, 46}, + {79, 93} + } +}; + + + +static unsigned char DICT_APRILTAG_25h9_BYTES[][4][4] = +{ + { {143, 211, 170, 1}, + {234, 146, 237, 1}, + {170, 229, 248, 1}, + {219, 164, 171, 1} + }, + { {109, 139, 39, 1}, + {170, 251, 21, 0}, + {242, 104, 219, 0}, + {84, 111, 170, 1} + }, + { {22, 208, 222, 1}, + {94, 6, 244, 1}, + {189, 133, 180, 0}, + {151, 176, 61, 0} + }, + { {179, 147, 87, 1}, + {62, 118, 217, 0}, + {245, 100, 230, 1}, + {77, 183, 62, 0} + }, + { {115, 40, 116, 1}, + {28, 189, 146, 0}, + {151, 10, 103, 0}, + {36, 222, 156, 0} + }, + { {221, 18, 172, 0}, + {164, 145, 124, 1}, + {26, 164, 93, 1}, + {159, 68, 146, 1} + }, + { {103, 234, 238, 0}, + {98, 191, 182, 1}, + {59, 171, 243, 0}, + {182, 254, 163, 0} + }, + { {213, 186, 96, 1}, + {46, 157, 78, 0}, + {131, 46, 213, 1}, + {57, 92, 186, 0} + }, + { {231, 69, 93, 1}, + {89, 117, 157, 1}, + {221, 81, 115, 1}, + {220, 215, 77, 0} + }, + { {17, 101, 123, 0}, + {85, 214, 3, 1}, + {111, 83, 68, 0}, + {224, 53, 213, 0} + }, + { {228, 155, 129, 0}, + {34, 105, 109, 0}, + {64, 236, 147, 1}, + {91, 75, 34, 0} + }, + { {130, 77, 138, 0}, + {65, 10, 169, 1}, + {40, 217, 32, 1}, + {202, 168, 65, 0} + }, + { {117, 19, 219, 0}, + {52, 119, 101, 1}, + {109, 228, 87, 0}, + {211, 119, 22, 0} + }, + { {152, 112, 167, 1}, + {204, 194, 122, 0}, + {242, 135, 12, 1}, + {47, 33, 153, 1} + }, + { {75, 152, 26, 1}, + {154, 27, 192, 1}, + {172, 12, 233, 0}, + {129, 236, 44, 1} + }, + { {65, 65, 182, 1}, + {88, 147, 49, 0}, + {182, 193, 65, 0}, + {70, 100, 141, 0} + }, + { {172, 7, 198, 1}, + {169, 38, 61, 0}, + {177, 240, 26, 1}, + {94, 50, 74, 1} + }, + { {129, 217, 185, 0}, + {82, 216, 105, 1}, + {78, 205, 192, 1}, + {203, 13, 165, 0} + }, + { {35, 119, 206, 1}, + {105, 54, 243, 1}, + {185, 247, 98, 0}, + {231, 182, 75, 0} + }, + { {144, 92, 117, 0}, + {85, 204, 88, 0}, + {87, 29, 4, 1}, + {13, 25, 213, 0} + }, + { {138, 62, 250, 0}, + {177, 142, 234, 1}, + {47, 190, 40, 1}, + {171, 184, 198, 1} + }, + { {63, 236, 133, 1}, + {207, 120, 182, 0}, + {208, 155, 254, 0}, + {54, 143, 121, 1} + }, + { {70, 169, 59, 0}, + {18, 203, 135, 1}, + {110, 74, 177, 0}, + {240, 233, 164, 0} + }, + { {162, 116, 1, 1}, + {73, 96, 202, 0}, + {192, 23, 34, 1}, + {41, 131, 73, 0} + }, + { {52, 42, 195, 1}, + {44, 110, 38, 0}, + {225, 170, 22, 0}, + {50, 59, 26, 0} + }, + { {37, 240, 225, 0}, + {66, 244, 102, 0}, + {67, 135, 210, 0}, + {51, 23, 161, 0} + }, + { {144, 226, 137, 0}, + {102, 64, 42, 1}, + {72, 163, 132, 1}, + {170, 1, 51, 0} + }, + { {15, 33, 102, 1}, + {136, 150, 151, 0}, + {179, 66, 120, 0}, + {116, 180, 136, 1} + }, + { {165, 79, 19, 0}, + {113, 122, 13, 0}, + {100, 121, 82, 1}, + {88, 47, 71, 0} + }, + { {221, 124, 193, 0}, + {197, 93, 110, 0}, + {65, 159, 93, 1}, + {59, 93, 81, 1} + }, + { {254, 142, 154, 0}, + {183, 43, 172, 1}, + {44, 184, 191, 1}, + {154, 234, 118, 1} + }, + { {10, 11, 60, 1}, + {184, 136, 145, 1}, + {158, 104, 40, 0}, + {196, 136, 142, 1} + }, + { {139, 80, 95, 0}, + {208, 86, 216, 1}, + {125, 5, 104, 1}, + {141, 181, 5, 1} + }, + { {13, 198, 237, 1}, + {235, 212, 52, 1}, + {219, 177, 216, 0}, + {150, 21, 235, 1} + }, + { {115, 137, 157, 0}, + {22, 121, 177, 1}, + {92, 200, 231, 0}, + {198, 207, 52, 0} + } +}; + + + +static unsigned char DICT_APRILTAG_36h10_BYTES[][4][5] = +{ + { {225, 101, 73, 83, 8}, + {49, 6, 165, 238, 1}, + {28, 169, 42, 104, 7}, + {135, 122, 86, 8, 12} + }, + { {50, 53, 132, 160, 4}, + {65, 139, 98, 33, 2}, + {32, 82, 26, 196, 12}, + {72, 68, 109, 24, 2} + }, + { {107, 237, 252, 94, 4}, + {43, 167, 244, 249, 13}, + {39, 163, 251, 125, 6}, + {185, 242, 254, 93, 4} + }, + { {217, 189, 115, 45, 4}, + {123, 106, 82, 246, 12}, + {43, 76, 235, 217, 11}, + {54, 244, 165, 109, 14} + }, + { {87, 115, 222, 38, 12}, + {233, 207, 79, 209, 1}, + {54, 71, 188, 238, 10}, + {136, 191, 47, 57, 7} + }, + { {245, 43, 80, 249, 12}, + {161, 42, 235, 222, 10}, + {57, 240, 173, 74, 15}, + {87, 189, 117, 72, 5} + }, + { {46, 91, 153, 155, 12}, + {210, 166, 237, 21, 11}, + {61, 153, 157, 167, 4}, + {218, 139, 118, 84, 11} + }, + { {127, 167, 237, 114, 2}, + {179, 155, 190, 233, 5}, + {68, 235, 126, 95, 14}, + {169, 119, 221, 156, 13} + }, + { {72, 191, 207, 28, 10}, + {123, 243, 157, 160, 8}, + {83, 143, 63, 209, 2}, + {16, 91, 156, 253, 14} + }, + { {235, 64, 193, 206, 10}, + {58, 180, 33, 203, 3}, + {87, 56, 48, 45, 7}, + {205, 56, 66, 213, 12} + }, + { {215, 152, 253, 186, 6}, + {240, 185, 214, 211, 15}, + {101, 219, 241, 158, 11}, + {252, 182, 185, 208, 15} + }, + { {92, 212, 54, 176, 14}, + {194, 93, 211, 176, 6}, + {112, 214, 194, 179, 10}, + {96, 220, 187, 164, 3} + }, + { {114, 66, 21, 239, 14}, + {24, 61, 107, 157, 3}, + {127, 122, 132, 36, 14}, + {203, 157, 107, 193, 8} + }, + { {200, 18, 236, 76, 1}, + {110, 161, 12, 138, 4}, + {131, 35, 116, 129, 3}, + {37, 19, 8, 87, 6} + }, + { {27, 178, 99, 54, 1}, + {127, 72, 154, 65, 5}, + {134, 204, 100, 221, 8}, + {168, 37, 145, 47, 14} + }, + { {185, 202, 170, 229, 1}, + {14, 204, 62, 78, 14}, + {138, 117, 85, 57, 13}, + {119, 39, 195, 55, 0} + }, + { {55, 38, 64, 234, 9}, + {165, 40, 43, 105, 3}, + {149, 112, 38, 78, 12}, + {201, 109, 65, 74, 5} + }, + { {141, 86, 137, 137, 9}, + {214, 164, 13, 102, 2}, + {153, 25, 22, 171, 1}, + {70, 107, 2, 86, 11} + }, + { {220, 110, 245, 96, 5}, + {183, 141, 74, 186, 12}, + {160, 106, 247, 99, 11}, + {53, 213, 43, 30, 13} + }, + { {81, 225, 58, 109, 5}, + {13, 110, 86, 220, 4}, + {171, 101, 200, 120, 10}, + {35, 182, 167, 107, 0} + }, + { {40, 201, 94, 212, 13}, + {46, 71, 245, 24, 10}, + {178, 183, 169, 49, 4}, + {81, 138, 254, 39, 4} + }, + { {155, 25, 144, 102, 13}, + {78, 138, 67, 91, 9}, + {182, 96, 153, 141, 9}, + {157, 172, 37, 23, 2} + }, + { {198, 37, 228, 219, 13}, + {165, 163, 193, 175, 7}, + {189, 178, 122, 70, 3}, + {239, 88, 60, 90, 5} + }, + { {238, 221, 198, 163, 3}, + {230, 215, 50, 167, 11}, + {204, 86, 59, 183, 7}, + {222, 84, 206, 182, 7} + }, + { {168, 123, 189, 36, 7}, + {95, 151, 110, 18, 12}, + {226, 75, 221, 225, 5}, + {52, 135, 110, 159, 10} + }, + { {38, 247, 86, 43, 7}, + {229, 119, 122, 53, 1}, + {237, 70, 174, 246, 4}, + {138, 197, 238, 234, 7} + }, + { {119, 95, 17, 146, 15}, + {212, 30, 233, 241, 11}, + {244, 152, 143, 174, 14}, + {216, 249, 119, 130, 11} + }, + { {227, 232, 42, 118, 0}, + {9, 68, 182, 203, 13}, + {6, 229, 65, 124, 7}, + {189, 54, 210, 41, 0} + }, + { {219, 116, 9, 26, 8}, + {83, 44, 133, 227, 1}, + {21, 137, 2, 237, 11}, + {140, 122, 19, 76, 10} + }, + { {166, 92, 117, 179, 8}, + {240, 5, 163, 55, 15}, + {28, 218, 227, 166, 5}, + {254, 204, 90, 0, 15} + }, + { {247, 162, 51, 66, 4}, + {145, 72, 120, 219, 5}, + {36, 44, 196, 94, 15}, + {173, 177, 225, 40, 9} + }, + { {45, 210, 218, 190, 4}, + {234, 228, 254, 80, 3}, + {39, 213, 180, 187, 4}, + {192, 167, 242, 117, 7} + }, + { {158, 138, 87, 237, 4}, + {186, 105, 90, 31, 10}, + {43, 126, 165, 23, 9}, + {95, 133, 169, 101, 13} + }, + { {192, 186, 16, 180, 12}, + {73, 0, 219, 146, 10}, + {50, 208, 133, 208, 3}, + {84, 157, 176, 9, 2} + }, + { {19, 70, 233, 230, 12}, + {56, 140, 79, 105, 7}, + {54, 121, 118, 44, 8}, + {233, 111, 35, 17, 12} + }, + { {177, 22, 100, 69, 12}, + {104, 9, 105, 110, 4}, + {58, 34, 102, 136, 13}, + {39, 105, 105, 1, 6} + }, + { {74, 118, 173, 59, 12}, + {83, 165, 207, 165, 5}, + {61, 203, 86, 229, 2}, + {170, 95, 58, 92, 10} + }, + { {133, 254, 70, 49, 2}, + {225, 85, 154, 102, 8}, + {72, 198, 39, 250, 1}, + {22, 101, 154, 168, 7} + }, + { {173, 105, 121, 110, 10}, + {187, 54, 39, 90, 13}, + {87, 105, 233, 107, 5}, + {181, 174, 70, 205, 13} + }, + { {32, 21, 210, 100, 6}, + {104, 210, 98, 56, 0}, + {98, 100, 186, 128, 4}, + {1, 196, 100, 177, 6} + }, + { {147, 181, 91, 6, 6}, + {121, 90, 84, 115, 1}, + {102, 13, 170, 220, 9}, + {140, 226, 165, 169, 14} + }, + { {202, 157, 31, 171, 6}, + {82, 115, 86, 183, 11}, + {109, 95, 139, 149, 3}, + {222, 214, 172, 228, 10} + }, + { {187, 35, 104, 210, 14}, + {35, 26, 237, 75, 7}, + {116, 177, 108, 77, 13}, + {237, 43, 117, 140, 4} + }, + { {52, 107, 195, 95, 14}, + {185, 254, 233, 12, 9}, + {127, 172, 61, 98, 12}, + {147, 9, 119, 249, 13} + }, + { {160, 207, 113, 248, 9}, + {52, 38, 187, 58, 14}, + {145, 248, 239, 48, 5}, + {117, 205, 214, 66, 12} + }, + { {83, 31, 184, 90, 9}, + {68, 170, 141, 249, 13}, + {149, 161, 223, 140, 10}, + {185, 251, 21, 82, 2} + }, + { {241, 127, 53, 41, 9}, + {85, 47, 43, 246, 12}, + {153, 74, 207, 232, 15}, + {54, 253, 79, 74, 10} + }, + { {152, 80, 211, 144, 5}, + {118, 204, 192, 18, 2}, + {160, 156, 176, 161, 9}, + {68, 128, 51, 54, 14} + }, + { {123, 240, 90, 34, 5}, + {103, 76, 118, 209, 1}, + {164, 69, 160, 253, 14}, + {136, 182, 227, 46, 6} + }, + { {197, 168, 151, 126, 5}, + {157, 225, 210, 218, 9}, + {167, 238, 145, 90, 3}, + {149, 180, 184, 123, 9} + }, + { {22, 216, 30, 157, 5}, + {204, 109, 212, 21, 10}, + {171, 151, 129, 182, 8}, + {90, 130, 187, 99, 3} + }, + { {180, 132, 224, 47, 5}, + {172, 168, 114, 38, 5}, + {175, 64, 114, 18, 13}, + {166, 68, 225, 83, 5} + }, + { {237, 52, 164, 150, 13}, + {207, 129, 225, 226, 7}, + {182, 146, 82, 203, 7}, + {228, 120, 120, 31, 3} + }, + { {9, 146, 15, 177, 3}, + {86, 81, 158, 68, 2}, + {200, 223, 4, 153, 0}, + {66, 39, 152, 166, 10} + }, + { {251, 90, 185, 188, 11}, + {94, 188, 175, 211, 14}, + {211, 217, 213, 173, 15}, + {124, 191, 83, 215, 10} + }, + { {37, 6, 52, 30, 11}, + {140, 49, 169, 112, 5}, + {215, 130, 198, 10, 4}, + {160, 233, 88, 195, 1} + }, + { {224, 145, 168, 72, 15}, + {68, 178, 117, 138, 4}, + {241, 33, 88, 144, 7}, + {37, 26, 228, 210, 2} + }, + { {194, 115, 147, 11, 8}, + {81, 230, 9, 151, 1}, + {29, 12, 156, 228, 3}, + {142, 153, 6, 120, 10} + }, + { {16, 43, 26, 208, 4}, + {1, 74, 204, 24, 10}, + {32, 181, 141, 64, 8}, + {81, 131, 53, 40, 0} + }, + { {56, 215, 41, 175, 4}, + {90, 46, 126, 36, 7}, + {47, 89, 78, 177, 12}, + {226, 71, 231, 69, 10} + }, + { {214, 63, 130, 165, 12}, + {201, 202, 75, 167, 10}, + {58, 84, 31, 198, 11}, + {94, 93, 37, 57, 3} + }, + { {93, 176, 247, 42, 2}, + {243, 249, 18, 208, 5}, + {69, 78, 240, 219, 10}, + {160, 180, 137, 252, 15} + }, + { {115, 36, 212, 252, 10}, + {41, 185, 163, 249, 2}, + {83, 242, 178, 76, 14}, + {73, 252, 89, 217, 4} + }, + { {71, 220, 230, 148, 6}, + {232, 213, 208, 225, 14}, + {98, 150, 115, 190, 2}, + {120, 112, 186, 177, 7} + }, + { {221, 26, 76, 114, 14}, + {226, 25, 207, 202, 9}, + {116, 227, 37, 139, 11}, + {149, 63, 57, 132, 7} + }, + { {154, 222, 49, 181, 1}, + {94, 12, 154, 55, 14}, + {138, 216, 199, 181, 9}, + {126, 197, 147, 7, 10} + }, + { {76, 25, 234, 75, 9}, + {230, 226, 5, 140, 13}, + {157, 37, 121, 131, 2}, + {179, 26, 4, 118, 7} + }, + { {129, 149, 32, 65, 5}, + {68, 2, 80, 110, 4}, + {168, 32, 74, 152, 1}, + {39, 96, 164, 2, 2} + }, + { {208, 173, 68, 239, 5}, + {45, 43, 82, 174, 11}, + {175, 114, 43, 80, 11}, + {215, 84, 173, 75, 4} + }, + { {169, 3, 2, 54, 13}, + {14, 66, 235, 66, 1}, + {182, 196, 12, 9, 5}, + {132, 45, 116, 39, 0} + }, + { {90, 99, 243, 101, 13}, + {63, 206, 75, 157, 4}, + {186, 108, 252, 101, 10}, + {43, 157, 39, 63, 12} + }, + { {167, 56, 140, 168, 15}, + {197, 177, 103, 67, 10}, + {241, 83, 17, 206, 5}, + {92, 46, 104, 218, 3} + }, + { {193, 252, 209, 46, 0}, + {121, 164, 18, 242, 9}, + {7, 72, 179, 248, 3}, + {148, 244, 130, 89, 14} + }, + { {18, 162, 88, 245, 0}, + {41, 8, 158, 29, 2}, + {10, 241, 164, 84, 8}, + {75, 135, 145, 9, 4} + }, + { {176, 210, 149, 151, 0}, + {88, 141, 184, 22, 3}, + {14, 154, 148, 176, 13}, + {198, 129, 219, 17, 10} + }, + { {172, 158, 132, 125, 4}, + {202, 161, 250, 46, 8}, + {43, 226, 23, 147, 5}, + {23, 69, 248, 85, 3} + }, + { {112, 105, 55, 71, 12}, + {25, 79, 97, 156, 13}, + {62, 46, 201, 96, 14}, + {179, 152, 111, 41, 8} + }, + { {68, 181, 20, 179, 2}, + {193, 19, 146, 180, 3}, + {76, 210, 138, 210, 2}, + {194, 212, 156, 136, 3} + }, + { {231, 77, 237, 224, 10}, + {176, 151, 39, 235, 14}, + {80, 123, 123, 46, 7}, + {125, 126, 78, 144, 13} + }, + { {53, 29, 98, 66, 10}, + {224, 90, 33, 104, 13}, + {84, 36, 107, 138, 12}, + {177, 104, 69, 160, 7} + }, + { {142, 125, 171, 62, 10}, + {219, 246, 135, 35, 13}, + {87, 205, 91, 231, 1}, + {188, 78, 22, 253, 11} + }, + { {82, 219, 5, 133, 6}, + {88, 31, 88, 133, 10}, + {106, 26, 13, 180, 10}, + {90, 17, 175, 129, 10} + }, + { {240, 135, 244, 59, 6}, + {32, 187, 250, 182, 5}, + {109, 194, 254, 16, 15}, + {166, 213, 253, 208, 4} + }, + { {78, 144, 152, 110, 1}, + {206, 160, 22, 153, 1}, + {135, 97, 144, 151, 2}, + {137, 150, 128, 87, 3} + }, + { {210, 100, 39, 89, 9}, + {21, 109, 129, 175, 4}, + {153, 174, 66, 100, 11}, + {47, 88, 27, 106, 8} + }, + { {36, 194, 245, 253, 5}, + {188, 165, 250, 28, 6}, + {171, 250, 244, 50, 4}, + {99, 133, 250, 83, 13} + }, + { {50, 142, 228, 41, 3}, + {36, 185, 58, 37, 12}, + {201, 66, 119, 20, 12}, + {58, 69, 201, 210, 4} + }, + { {107, 62, 162, 144, 11}, + {71, 208, 169, 225, 14}, + {208, 148, 87, 205, 6}, + {120, 121, 80, 190, 2} + }, + { {217, 65, 43, 194, 11}, + {22, 94, 5, 202, 7}, + {212, 61, 72, 41, 11}, + {229, 58, 7, 166, 8} + }, + { {156, 165, 116, 197, 7}, + {175, 27, 80, 62, 6}, + {234, 50, 234, 83, 9}, + {103, 192, 173, 143, 5} + }, + { {17, 109, 219, 74, 15}, + {53, 254, 69, 120, 9}, + {245, 45, 187, 104, 8}, + {145, 234, 39, 250, 12} + }, + { {174, 87, 71, 134, 8}, + {250, 71, 41, 35, 3}, + {22, 30, 46, 167, 5}, + {204, 73, 78, 37, 15} + }, + { {114, 224, 117, 97, 4}, + {49, 13, 114, 157, 4}, + {40, 106, 224, 116, 14}, + {43, 148, 235, 8, 12} + }, + { {198, 150, 180, 86, 6}, + {200, 145, 216, 187, 5}, + {102, 162, 214, 150, 3}, + {173, 209, 184, 145, 3} + }, + { {168, 197, 195, 205, 1}, + {62, 230, 48, 46, 2}, + {139, 60, 58, 49, 5}, + {71, 64, 198, 119, 12} + }, + { {91, 21, 74, 119, 1}, + {110, 74, 134, 237, 1}, + {142, 229, 42, 141, 10}, + {139, 118, 21, 39, 6} + }, + { {205, 163, 184, 200, 5}, + {135, 162, 92, 218, 6}, + {161, 49, 220, 91, 3}, + {101, 179, 164, 94, 1} + }, + { {74, 204, 221, 214, 7}, + {62, 149, 212, 185, 11}, + {230, 187, 179, 53, 2}, + {217, 210, 186, 151, 12} + }, + { {102, 82, 239, 170, 15}, + {244, 245, 111, 129, 7}, + {245, 95, 116, 166, 6}, + {232, 31, 106, 242, 15} + }, + { {173, 218, 37, 160, 0}, + {210, 5, 58, 66, 14}, + {0, 90, 69, 187, 5}, + {116, 37, 202, 4, 11} + }, + { {252, 230, 67, 94, 0}, + {187, 108, 184, 170, 1}, + {7, 172, 38, 115, 15}, + {133, 81, 211, 109, 13} + }, + { {187, 145, 56, 91, 8}, + {66, 42, 181, 95, 5}, + {29, 161, 200, 157, 13}, + {175, 170, 213, 68, 2} + }, + { {52, 217, 147, 145, 4}, + {208, 206, 240, 20, 10}, + {40, 156, 153, 178, 12}, + {82, 128, 247, 48, 11} + }, + { {160, 29, 41, 117, 12}, + {88, 2, 231, 46, 12}, + {58, 233, 75, 128, 5}, + {55, 78, 116, 1, 10} + }, + { {197, 91, 119, 176, 10}, + {240, 87, 139, 210, 14}, + {80, 222, 237, 170, 3}, + {116, 189, 30, 160, 15} + }, + { {180, 103, 48, 193, 10}, + {129, 30, 41, 62, 6}, + {88, 48, 206, 98, 13}, + {103, 201, 71, 136, 1} + }, + { {79, 215, 217, 189, 10}, + {250, 182, 159, 245, 2}, + {91, 217, 190, 191, 2}, + {74, 255, 150, 213, 15} + }, + { {62, 239, 157, 76, 6}, + {155, 191, 124, 57, 8}, + {99, 43, 159, 119, 12}, + {25, 195, 239, 221, 9} + }, + { {211, 16, 230, 71, 6}, + {104, 217, 64, 207, 5}, + {110, 38, 112, 140, 11}, + {175, 48, 41, 177, 6} + }, + { {150, 212, 131, 130, 1}, + {212, 204, 16, 35, 3}, + {132, 28, 18, 182, 9}, + {204, 64, 131, 50, 11} + }, + { {190, 66, 191, 212, 9}, + {158, 205, 173, 27, 6}, + {146, 191, 212, 39, 13}, + {109, 139, 91, 55, 9} + }, + { {66, 18, 49, 102, 9}, + {92, 0, 11, 153, 5}, + {150, 104, 196, 132, 2}, + {169, 157, 0, 3, 10} + }, + { {51, 202, 85, 219, 9}, + {52, 45, 185, 93, 11}, + {157, 186, 165, 60, 12}, + {219, 169, 219, 66, 12} + }, + { {165, 14, 174, 0, 13}, + {132, 193, 109, 98, 12}, + {176, 7, 87, 10, 5}, + {52, 107, 104, 50, 1} + }, + { {86, 110, 32, 220, 13}, + {141, 44, 201, 169, 14}, + {179, 176, 71, 102, 10}, + {121, 89, 51, 75, 1} + }, + { {126, 177, 2, 164, 3}, + {207, 90, 50, 129, 2}, + {194, 84, 8, 215, 14}, + {72, 20, 197, 175, 3} + }, + { {175, 51, 166, 149, 7}, + {207, 211, 232, 71, 6}, + {234, 150, 92, 207, 5}, + {110, 33, 124, 191, 3} + }, + { {144, 39, 133, 201, 15}, + {21, 187, 73, 46, 2}, + {249, 58, 30, 64, 9}, + {71, 73, 45, 218, 8} + }, + { {26, 175, 114, 194, 0}, + {35, 74, 24, 57, 15}, + {4, 52, 239, 85, 8}, + {249, 193, 133, 44, 4} + }, + { {140, 72, 88, 22, 8}, + {170, 4, 133, 18, 9}, + {22, 129, 161, 35, 1}, + {148, 138, 18, 5, 5} + }, + { {221, 184, 28, 187, 8}, + {195, 41, 151, 214, 11}, + {29, 211, 129, 219, 11}, + {214, 190, 153, 76, 3} + }, + { {41, 92, 192, 64, 12}, + {98, 132, 97, 104, 8}, + {48, 32, 51, 169, 4}, + {17, 104, 98, 20, 6} + }, + { {204, 21, 5, 90, 14}, + {210, 51, 193, 170, 1}, + {117, 170, 10, 131, 3}, + {133, 88, 60, 196, 11} + }, + { {218, 107, 24, 244, 9}, + {15, 14, 143, 155, 10}, + {146, 241, 141, 101, 11}, + {93, 159, 23, 15, 0} + }, + { {167, 135, 108, 37, 9}, + {172, 3, 63, 103, 4}, + {154, 67, 110, 30, 5}, + {46, 111, 204, 3, 5} + }, + { {246, 183, 42, 152, 5}, + {197, 106, 252, 163, 6}, + {161, 149, 78, 214, 15}, + {108, 83, 245, 106, 3} + }, + { {176, 8, 245, 30, 13}, + {60, 169, 225, 18, 13}, + {183, 138, 241, 0, 13}, + {180, 136, 121, 83, 12} + }, + { {75, 168, 124, 205, 13}, + {47, 33, 85, 221, 14}, + {187, 51, 225, 93, 2}, + {123, 186, 168, 79, 4} + }, + { {132, 228, 215, 70, 3}, + {189, 213, 16, 58, 1}, + {198, 46, 178, 114, 1}, + {133, 192, 138, 187, 13} + }, + { {108, 145, 86, 98, 0}, + {226, 67, 50, 152, 1}, + {4, 102, 168, 147, 6}, + {129, 148, 204, 36, 7} + }, + { {161, 89, 232, 111, 0}, + {104, 166, 38, 78, 13}, + {15, 97, 121, 168, 5}, + {183, 38, 70, 81, 6} + }, + { {240, 101, 172, 214, 8}, + {9, 143, 165, 170, 7}, + {22, 179, 90, 96, 15}, + {229, 90, 95, 25, 0} + }, + { {151, 176, 34, 145, 10}, + {193, 88, 145, 71, 6}, + {88, 148, 64, 222, 9}, + {110, 40, 145, 168, 3} + }, + { {191, 36, 1, 206, 6}, + {155, 56, 96, 107, 3}, + {103, 56, 2, 79, 13}, + {205, 96, 97, 205, 9} + }, + { {164, 202, 31, 226, 1}, + {148, 69, 62, 26, 11}, + {132, 127, 133, 50, 5}, + {213, 135, 202, 34, 9} + }, + { {17, 222, 135, 135, 9}, + {92, 205, 25, 100, 11}, + {158, 30, 23, 184, 8}, + {210, 105, 139, 51, 10} + }, + { {57, 73, 184, 51, 5}, + {6, 142, 230, 84, 13}, + {172, 193, 217, 41, 12}, + {178, 166, 119, 22, 0} + }, + { {134, 25, 53, 96, 13}, + {212, 3, 67, 27, 12}, + {176, 106, 201, 134, 1}, + {61, 140, 44, 2, 11} + }, + { {200, 75, 66, 10, 11}, + {38, 118, 9, 130, 9}, + {213, 4, 45, 33, 3}, + {148, 25, 6, 230, 4} + }, + { {81, 136, 22, 43, 15}, + {4, 121, 83, 212, 9}, + {253, 70, 129, 24, 10}, + {146, 188, 169, 226, 0} + }, + { {198, 116, 204, 159, 0}, + {233, 165, 132, 167, 3}, + {15, 147, 50, 230, 3}, + {206, 82, 26, 89, 7} + }, + { {168, 22, 147, 66, 12}, + {82, 192, 105, 58, 1}, + {52, 44, 150, 129, 5}, + {133, 201, 96, 52, 10} + }, + { {91, 182, 26, 190, 12}, + {75, 104, 223, 241, 3}, + {55, 213, 134, 221, 10}, + {200, 255, 177, 109, 2} + }, + { {64, 57, 11, 138, 10}, + {81, 114, 5, 128, 11}, + {85, 29, 9, 192, 2}, + {208, 26, 4, 232, 10} + }, + { {49, 229, 119, 99, 10}, + {49, 95, 51, 124, 5}, + {92, 110, 234, 120, 12}, + {163, 236, 207, 168, 12} + }, + { {138, 181, 190, 128, 6}, + {67, 211, 84, 51, 6}, + {96, 23, 218, 213, 1}, + {108, 194, 172, 188, 2} + }, + { {232, 127, 242, 172, 9}, + {111, 230, 43, 178, 14}, + {147, 84, 255, 225, 7}, + {116, 221, 70, 127, 6} + }, + { {133, 144, 118, 85, 9}, + {236, 65, 145, 94, 4}, + {154, 166, 224, 154, 1}, + {39, 168, 152, 35, 7} + }, + { {173, 4, 85, 41, 5}, + {182, 33, 98, 118, 0}, + {169, 74, 162, 11, 5}, + {6, 228, 104, 70, 13} + }, + { {94, 100, 156, 243, 5}, + {135, 141, 198, 189, 3}, + {172, 243, 146, 103, 10}, + {203, 214, 59, 30, 1} + }, + { {113, 188, 174, 157, 13}, + {77, 233, 245, 228, 14}, + {187, 151, 83, 216, 14}, + {114, 122, 249, 123, 2} + }, + { {202, 194, 32, 47, 13}, + {14, 36, 91, 135, 5}, + {191, 64, 68, 53, 3}, + {174, 29, 162, 71, 0} + }, + { {237, 109, 12, 225, 0}, + {131, 7, 38, 238, 10}, + {8, 115, 11, 107, 7}, + {87, 118, 78, 12, 1} + }, + { {211, 179, 54, 142, 8}, + {73, 107, 25, 211, 7}, + {23, 22, 204, 220, 11}, + {236, 185, 141, 105, 2} + }, + { {88, 251, 152, 132, 4}, + {75, 142, 92, 144, 10}, + {34, 17, 157, 241, 10}, + {80, 147, 167, 29, 2} + }, + { {66, 176, 73, 175, 12}, + {121, 32, 87, 133, 3}, + {63, 89, 32, 212, 2}, + {202, 30, 160, 73, 14} + }, + { {165, 172, 222, 241, 10}, + {161, 209, 183, 126, 10}, + {88, 247, 179, 90, 5}, + {87, 238, 216, 184, 5} + }, + { {126, 74, 98, 45, 6}, + {170, 124, 106, 133, 12}, + {107, 68, 101, 39, 14}, + {58, 21, 99, 229, 5} + }, + { {93, 233, 95, 78, 9}, + {191, 111, 21, 216, 9}, + {151, 47, 169, 123, 10}, + {145, 186, 143, 111, 13} + }, + { {34, 197, 24, 23, 9}, + {12, 6, 181, 53, 1}, + {158, 129, 138, 52, 4}, + {138, 202, 214, 3, 0} + }, + { {115, 117, 108, 186, 5}, + {101, 47, 230, 225, 7}, + {165, 211, 106, 236, 14}, + {232, 118, 127, 74, 6} + }, + { {80, 47, 87, 216, 11}, + {53, 123, 137, 184, 10}, + {209, 190, 175, 64, 10}, + {81, 217, 29, 234, 12} + }, + { {154, 160, 25, 211, 11}, + {23, 24, 149, 31, 3}, + {220, 185, 128, 85, 9}, + {207, 138, 145, 142, 8} + }, + { {61, 180, 129, 166, 15}, + {223, 152, 115, 96, 3}, + {246, 88, 18, 219, 12}, + {192, 108, 225, 159, 11} + }, + { {39, 218, 159, 156, 8}, + {216, 229, 189, 81, 10}, + {19, 159, 149, 190, 4}, + {88, 171, 218, 113, 11} + }, + { {58, 103, 134, 237, 6}, + {11, 255, 106, 45, 2}, + {107, 118, 30, 101, 12}, + {75, 69, 111, 253, 0} + }, + { {132, 55, 15, 143, 6}, + {217, 115, 76, 38, 3}, + {111, 31, 14, 194, 1}, + {198, 67, 44, 233, 11} + }, + { {53, 66, 74, 6, 5}, + {172, 76, 108, 64, 1}, + {166, 5, 36, 42, 12}, + {128, 35, 99, 35, 5} + }, + { {131, 37, 139, 194, 7}, + {21, 210, 68, 107, 3}, + {228, 61, 26, 76, 1}, + {205, 98, 36, 186, 8} + }, + { {6, 72, 246, 57, 4}, + {160, 229, 194, 21, 12}, + {41, 198, 241, 38, 0}, + {58, 132, 58, 112, 5} + }, + { {46, 148, 213, 97, 12}, + {242, 129, 115, 61, 0}, + {56, 106, 178, 151, 4}, + {11, 204, 232, 20, 15} + }, + { {72, 50, 160, 103, 2}, + {75, 144, 10, 140, 5}, + {78, 96, 84, 193, 2}, + {163, 21, 0, 157, 2} + }, + { {228, 117, 33, 147, 1}, + {213, 6, 160, 166, 7}, + {140, 152, 74, 226, 7}, + {230, 80, 86, 10, 11} + }, + { {110, 253, 140, 158, 9}, + {207, 167, 181, 161, 11}, + {151, 147, 27, 247, 6}, + {216, 90, 222, 95, 3} + }, + { {0, 211, 242, 119, 9}, + {108, 198, 155, 28, 5}, + {158, 228, 252, 176, 0}, + {163, 141, 150, 51, 6} + }, + { {99, 216, 68, 68, 11}, + {108, 21, 49, 201, 8}, + {210, 34, 33, 188, 6}, + {25, 56, 202, 131, 6} + }, + { {249, 28, 183, 98, 7}, + {86, 217, 98, 250, 13}, + {228, 110, 211, 137, 15}, + {181, 244, 105, 182, 10} + }, + { {148, 34, 208, 61, 7}, + {173, 184, 202, 22, 0}, + {235, 192, 180, 66, 9}, + {6, 133, 49, 219, 5} + }, + { {187, 200, 20, 31, 6}, + {10, 61, 240, 87, 9}, + {111, 130, 129, 61, 13}, + {158, 160, 251, 197, 0} + }, + { {69, 152, 237, 76, 14}, + {248, 177, 85, 200, 12}, + {115, 43, 113, 154, 2}, + {49, 58, 168, 209, 15} + }, + { {83, 226, 244, 224, 9}, + {37, 141, 27, 217, 6}, + {144, 114, 244, 124, 10}, + {105, 189, 139, 26, 4} + }, + { {241, 178, 125, 130, 9}, + {117, 9, 61, 210, 7}, + {148, 27, 228, 216, 15}, + {228, 187, 201, 10, 14} + }, + { {42, 202, 178, 62, 9}, + {14, 228, 187, 17, 13}, + {151, 196, 213, 53, 4}, + {184, 141, 210, 119, 0} + }, + { {123, 122, 214, 143, 9}, + {111, 237, 41, 213, 11}, + {159, 22, 181, 237, 14}, + {218, 185, 75, 127, 6} + }, + { {130, 225, 42, 209, 13}, + {5, 70, 213, 15, 6}, + {184, 181, 72, 116, 1}, + {111, 10, 182, 42, 0} + }, + { {37, 245, 188, 164, 11}, + {205, 151, 55, 112, 6}, + {210, 83, 218, 250, 4}, + {96, 238, 206, 155, 3} + }, + { {150, 173, 51, 250, 11}, + {149, 122, 147, 59, 15}, + {213, 252, 203, 86, 9}, + {253, 204, 149, 234, 9} + }, + { {116, 221, 218, 89, 11}, + {228, 254, 181, 188, 8}, + {217, 165, 187, 178, 14}, + {19, 218, 215, 242, 7} + }, + { {190, 51, 16, 82, 7}, + {199, 26, 232, 27, 1}, + {228, 160, 140, 199, 13}, + {141, 129, 117, 142, 3} + }, + { {175, 106, 142, 227, 12}, + {131, 197, 111, 79, 11}, + {60, 119, 21, 111, 5}, + {223, 47, 106, 60, 1} + }, + { {80, 157, 15, 85, 14}, + {88, 91, 213, 172, 8}, + {122, 175, 11, 144, 10}, + {19, 90, 189, 161, 10} + }, + { {90, 168, 135, 202, 13}, + {23, 233, 81, 137, 11}, + {181, 62, 17, 85, 10}, + {217, 24, 169, 126, 8} + }, + { {248, 216, 14, 113, 13}, + {70, 77, 247, 142, 8}, + {184, 231, 1, 177, 15}, + {23, 30, 251, 38, 2} + }, + { {18, 41, 212, 58, 0}, + {33, 171, 130, 17, 9}, + {5, 194, 185, 68, 8}, + {152, 132, 29, 88, 4} + }, + { {58, 213, 230, 98, 8}, + {98, 207, 51, 41, 5}, + {20, 102, 122, 181, 12}, + {169, 76, 207, 52, 6} + }, + { {68, 63, 156, 94, 2}, + {201, 179, 140, 184, 9}, + {71, 163, 159, 194, 2}, + {145, 211, 28, 217, 3} + }, + { {53, 224, 26, 247, 2}, + {137, 92, 182, 92, 3}, + {78, 245, 128, 122, 12}, + {195, 166, 211, 169, 1} + }, + { {82, 84, 77, 114, 6}, + {112, 29, 198, 169, 1}, + {100, 235, 34, 164, 10}, + {137, 86, 59, 128, 14} + }, + { {8, 222, 33, 65, 9}, + {86, 4, 25, 44, 12}, + {152, 40, 71, 177, 0}, + {51, 73, 130, 6, 10} + }, + { {151, 233, 242, 229, 5}, + {173, 206, 82, 95, 14}, + {170, 116, 249, 126, 9}, + {127, 164, 167, 59, 5} + }, + { {117, 185, 123, 135, 5}, + {253, 74, 116, 212, 15}, + {174, 29, 233, 218, 14}, + {242, 178, 229, 43, 15} + }, + { {28, 149, 63, 106, 13}, + {214, 107, 87, 56, 5}, + {181, 111, 202, 147, 8}, + {161, 206, 173, 102, 11} + }, + { {72, 136, 208, 174, 15}, + {46, 176, 83, 144, 11}, + {247, 80, 177, 17, 2}, + {208, 156, 160, 215, 4} + }, + { {35, 82, 133, 125, 8}, + {88, 165, 171, 77, 0}, + {27, 234, 20, 172, 4}, + {11, 45, 90, 81, 10} + }, + { {145, 10, 12, 159, 8}, + {8, 41, 141, 70, 11}, + {31, 147, 5, 8, 9}, + {214, 43, 25, 65, 0} + }, + { {114, 106, 253, 204, 4}, + {57, 173, 108, 153, 14}, + {35, 59, 245, 100, 14}, + {121, 147, 107, 89, 12} + }, + { {70, 182, 223, 184, 12}, + {241, 225, 223, 177, 2}, + {49, 223, 182, 214, 2}, + {72, 223, 184, 120, 15} + }, + { {55, 30, 152, 201, 12}, + {192, 168, 109, 125, 10}, + {57, 49, 151, 142, 12}, + {91, 235, 97, 80, 3} + }, + { {242, 133, 64, 180, 10}, + {40, 26, 179, 163, 2}, + {82, 208, 42, 20, 15}, + {76, 92, 213, 129, 4} + }, + { {155, 53, 4, 69, 10}, + {75, 27, 1, 111, 0}, + {90, 34, 10, 205, 9}, + {15, 104, 13, 141, 2} + }, + { {138, 236, 145, 248, 13}, + {23, 164, 211, 59, 10}, + {177, 248, 147, 117, 1}, + {93, 204, 178, 94, 8} + }, + { {173, 85, 173, 77, 15}, + {222, 183, 101, 110, 4}, + {251, 43, 90, 171, 5}, + {39, 106, 110, 215, 11} + }, + { {42, 24, 202, 208, 2}, + {98, 208, 164, 9, 10}, + {64, 181, 49, 133, 4}, + {89, 2, 80, 180, 6} + }, + { {237, 2, 60, 165, 10}, + {138, 17, 47, 214, 6}, + {90, 83, 196, 11, 7}, + {102, 191, 72, 133, 1} + }, + { {190, 137, 112, 155, 1}, + {166, 42, 176, 23, 15}, + {141, 144, 233, 23, 13}, + {254, 128, 213, 70, 5} + }, + { {66, 233, 185, 200, 9}, + {21, 166, 21, 153, 14}, + {145, 57, 217, 116, 2}, + {121, 154, 134, 90, 8} + }, + { {224, 185, 52, 114, 9}, + {69, 3, 179, 154, 13}, + {148, 226, 201, 208, 7}, + {181, 156, 220, 10, 2} + }, + { {135, 32, 220, 54, 11}, + {173, 145, 135, 83, 1}, + {214, 195, 176, 78, 1}, + {140, 174, 24, 155, 5} + }, + { {93, 132, 96, 137, 7}, + {166, 56, 80, 228, 6}, + {233, 16, 98, 27, 10}, + {98, 112, 161, 198, 5} + }, + { {144, 204, 205, 2, 15}, + {52, 157, 85, 34, 9}, + {244, 11, 51, 48, 9}, + {148, 74, 171, 146, 12} + }, + { {140, 134, 212, 168, 8}, + {162, 161, 27, 50, 2}, + {17, 82, 182, 19, 1}, + {68, 205, 136, 84, 5} + }, + { {120, 169, 76, 36, 12}, + {43, 11, 119, 128, 8}, + {50, 67, 41, 81, 14}, + {16, 30, 237, 13, 4} + }, + { {182, 101, 251, 203, 12}, + {177, 238, 101, 63, 7}, + {61, 61, 250, 102, 13}, + {239, 202, 103, 120, 13} + }, + { {39, 167, 32, 26, 6}, + {129, 50, 248, 97, 5}, + {101, 128, 78, 94, 4}, + {168, 97, 244, 200, 1} + }, + { {193, 202, 69, 13, 5}, + {60, 37, 88, 198, 8}, + {171, 10, 37, 56, 3}, + {22, 49, 170, 67, 12} + }, + { {75, 38, 242, 6, 13}, + {47, 192, 73, 241, 5}, + {182, 4, 246, 77, 2}, + {168, 249, 32, 63, 4} + }, + { {146, 37, 46, 39, 11}, + {13, 91, 7, 39, 5}, + {222, 71, 74, 68, 9}, + {174, 78, 13, 171, 0} + }, + { {240, 175, 98, 24, 8}, + {33, 106, 185, 162, 12}, + {17, 132, 111, 80, 15}, + {52, 89, 213, 104, 4} + }, + { {50, 161, 124, 11, 6}, + {33, 59, 116, 21, 5}, + {109, 3, 232, 84, 12}, + {170, 130, 237, 200, 4} + }, + { {107, 9, 58, 162, 14}, + {2, 82, 103, 209, 15}, + {116, 85, 201, 13, 6}, + {248, 190, 100, 164, 0} + }, + { {134, 231, 189, 205, 5}, + {157, 167, 92, 63, 6}, + {171, 59, 222, 118, 1}, + {111, 195, 174, 91, 9} + }, + { {70, 76, 128, 181, 11}, + {140, 148, 131, 165, 10}, + {218, 208, 19, 38, 2}, + {90, 92, 18, 147, 1} + }, + { {110, 146, 188, 233, 7}, + {198, 177, 126, 157, 6}, + {233, 115, 212, 151, 6}, + {107, 151, 232, 214, 3} + }, + { {10, 141, 210, 173, 4}, + {42, 226, 82, 53, 10}, + {43, 84, 187, 21, 0}, + {90, 196, 164, 117, 4} + }, + { {133, 26, 254, 234, 9}, + {228, 225, 15, 90, 15}, + {149, 119, 245, 138, 1}, + {245, 175, 8, 114, 7} + }, + { {102, 122, 112, 73, 9}, + {229, 36, 41, 157, 12}, + {153, 32, 229, 230, 6}, + {59, 153, 66, 74, 7} + }, + { {113, 49, 97, 102, 13}, + {125, 10, 99, 200, 5}, + {182, 104, 104, 200, 14}, + {161, 60, 101, 11, 14} + }, + { {197, 119, 160, 82, 15}, + {197, 150, 201, 234, 5}, + {244, 160, 94, 234, 3}, + {165, 121, 54, 154, 3} + }, + { {224, 70, 76, 187, 12}, + {32, 37, 239, 166, 3}, + {61, 211, 38, 32, 7}, + {198, 95, 122, 64, 4} + }, + { {244, 9, 85, 141, 10}, + {184, 59, 33, 150, 10}, + {91, 26, 169, 2, 15}, + {86, 152, 77, 193, 13} + }, + { {34, 79, 148, 169, 9}, + {4, 167, 43, 53, 10}, + {153, 82, 159, 36, 4}, + {90, 205, 78, 82, 0} + }, + { {229, 88, 200, 244, 13}, + {236, 132, 231, 202, 10}, + {178, 241, 49, 170, 7}, + {85, 62, 114, 19, 7} + }, + { {243, 18, 209, 192, 11}, + {116, 152, 41, 219, 2}, + {208, 56, 180, 140, 15}, + {77, 185, 65, 146, 14} + }, + { {41, 114, 88, 124, 11}, + {111, 52, 175, 88, 0}, + {211, 225, 164, 233, 4}, + {1, 175, 82, 207, 6} + }, + { {65, 179, 38, 98, 4}, + {65, 67, 90, 200, 5}, + {36, 102, 76, 216, 2}, + {161, 53, 172, 40, 2} + }, + { {218, 87, 244, 214, 12}, + {106, 143, 201, 187, 7}, + {54, 178, 254, 165, 11}, + {237, 217, 63, 21, 6} + }, + { {186, 1, 109, 186, 9}, + {54, 43, 167, 3, 7}, + {149, 219, 104, 5, 13}, + {236, 14, 93, 70, 12} + }, + { {53, 137, 198, 176, 5}, + {164, 203, 242, 64, 10}, + {160, 214, 57, 26, 12}, + {80, 36, 253, 50, 5} + }, + { {223, 69, 8, 189, 5}, + {142, 46, 198, 231, 2}, + {171, 209, 10, 47, 11}, + {78, 118, 55, 71, 1} + }, + { {50, 82, 34, 250, 8}, + {64, 108, 171, 9, 7}, + {21, 244, 68, 164, 12}, + {233, 13, 83, 96, 2} + }, + { {87, 54, 121, 125, 4}, + {249, 40, 206, 253, 4}, + {43, 233, 230, 206, 10}, + {43, 247, 49, 73, 15} + }, + { {100, 77, 74, 190, 10}, + {168, 118, 167, 160, 11}, + {87, 213, 43, 34, 6}, + {208, 94, 86, 225, 5} + }, + { {13, 189, 14, 79, 10}, + {203, 115, 21, 108, 9}, + {95, 39, 11, 219, 0}, + {147, 106, 140, 237, 3} + }, + { {20, 18, 155, 246, 13}, + {220, 200, 207, 24, 3}, + {182, 253, 148, 130, 8}, + {193, 143, 49, 51, 11} + }, + { {160, 137, 141, 158, 1}, + {28, 163, 180, 2, 11}, + {135, 155, 25, 16, 5}, + {212, 2, 220, 83, 8} + }, + { {116, 80, 182, 226, 11}, + {196, 221, 35, 152, 7}, + {212, 118, 208, 162, 14}, + {225, 156, 75, 178, 3} + }, + { {224, 228, 28, 54, 7}, + {13, 21, 246, 178, 1}, + {230, 195, 130, 112, 7}, + {132, 214, 250, 139, 0} + }, + { {105, 149, 101, 253, 11}, + {126, 51, 179, 236, 6}, + {219, 250, 106, 153, 6}, + {99, 124, 220, 199, 14} + }, + { {103, 160, 233, 22, 8}, + {185, 128, 181, 193, 5}, + {22, 137, 112, 94, 6}, + {168, 58, 208, 25, 13} + }, + { {25, 252, 180, 77, 12}, + {75, 173, 81, 124, 12}, + {59, 34, 211, 249, 8}, + {51, 232, 171, 93, 2} + }, + { {240, 254, 74, 108, 6}, + {105, 124, 126, 170, 8}, + {99, 101, 39, 240, 15}, + {21, 87, 227, 233, 6} + }, + { {78, 229, 220, 11, 14}, + {163, 183, 85, 181, 1}, + {125, 3, 186, 119, 2}, + {138, 218, 174, 220, 5} + }, + { {178, 90, 177, 231, 7}, + {92, 156, 106, 31, 15}, + {238, 120, 213, 164, 13}, + {255, 133, 99, 147, 10} + }, + { {84, 197, 166, 143, 13}, + {140, 239, 81, 164, 7}, + {191, 22, 90, 50, 10}, + {226, 88, 175, 115, 1} + }, + { {247, 21, 47, 212, 3}, + {220, 91, 164, 235, 6}, + {194, 191, 74, 142, 15}, + {109, 114, 93, 163, 11} + }, + { {79, 145, 125, 232, 10}, + {242, 51, 23, 217, 6}, + {81, 123, 232, 159, 2}, + {105, 190, 140, 196, 15} + }, + { {9, 53, 24, 238, 6}, + {75, 50, 70, 120, 3}, + {103, 113, 138, 201, 0}, + {193, 230, 36, 205, 2} + }, + { {170, 77, 229, 141, 6}, + {58, 183, 96, 39, 14}, + {107, 26, 123, 37, 5}, + {126, 64, 110, 213, 12} + }, + { {251, 189, 163, 100, 14}, + {91, 218, 115, 235, 12}, + {114, 108, 91, 221, 15}, + {61, 124, 229, 189, 10} + }, + { {194, 140, 79, 157, 13}, + {60, 97, 213, 167, 10}, + {187, 159, 35, 20, 3}, + {94, 90, 184, 99, 12} + }, + { {221, 221, 90, 236, 8}, + {234, 110, 23, 250, 10}, + {19, 117, 171, 187, 11}, + {85, 254, 135, 101, 7} + }, + { {154, 123, 45, 105, 4}, + {83, 47, 78, 15, 12}, + {41, 107, 77, 229, 9}, + {63, 7, 47, 76, 10} + }, + { {218, 46, 220, 145, 1}, + {39, 137, 140, 183, 10}, + {136, 147, 183, 69, 11}, + {94, 211, 25, 30, 4} + }, + { {18, 21, 66, 25, 5}, + {100, 106, 192, 37, 0}, + {169, 132, 42, 132, 8}, + {10, 64, 53, 98, 6} + }, + { {58, 253, 126, 65, 13}, + {103, 79, 117, 61, 12}, + {184, 39, 235, 245, 12}, + {59, 202, 239, 46, 6} + }, + { {210, 145, 48, 33, 12}, + {64, 10, 83, 151, 4}, + {56, 64, 200, 148, 11}, + {46, 156, 165, 0, 2} + }, + { {136, 91, 123, 237, 6}, + {122, 118, 78, 30, 14}, + {107, 125, 237, 161, 1}, + {119, 135, 38, 229, 14} + }, + { {255, 26, 148, 3, 13}, + {198, 137, 105, 215, 9}, + {188, 2, 149, 143, 15}, + {158, 185, 105, 22, 3} + }, + { {138, 210, 5, 218, 10}, + {82, 53, 153, 11, 3}, + {85, 186, 4, 181, 1}, + {205, 9, 154, 196, 10} + }, + { {124, 254, 229, 86, 14}, + {251, 157, 249, 168, 13}, + {118, 170, 119, 243, 14}, + {177, 89, 251, 157, 15} + }, + { {15, 29, 188, 45, 9}, + {206, 163, 7, 117, 12}, + {155, 67, 219, 143, 0}, + {58, 238, 12, 87, 3} + }, + { {173, 125, 51, 119, 9}, + {223, 70, 163, 126, 13}, + {158, 236, 203, 235, 5}, + {183, 236, 86, 47, 11} + }, + { {94, 35, 218, 148, 5}, + {175, 202, 204, 145, 2}, + {162, 149, 188, 71, 10}, + {72, 147, 53, 63, 5} + }, + { {202, 135, 100, 114, 13}, + {38, 3, 219, 171, 5}, + {180, 226, 110, 21, 3}, + {173, 93, 188, 6, 4} + }, + { {42, 52, 86, 226, 7}, + {103, 81, 98, 57, 3}, + {228, 118, 162, 197, 4}, + {201, 196, 104, 174, 6} + }, + { {179, 128, 104, 125, 6}, + {40, 56, 246, 79, 4}, + {107, 225, 96, 28, 13}, + {47, 38, 241, 193, 4} + }, + { {73, 224, 165, 31, 6}, + {27, 181, 208, 196, 5}, + {111, 138, 80, 121, 2}, + {162, 48, 186, 221, 8} + }, + { {144, 226, 83, 62, 9}, + {61, 108, 155, 18, 1}, + {151, 204, 164, 112, 9}, + {132, 141, 147, 107, 12} + }, + { {221, 1, 6, 99, 13}, + {134, 75, 67, 206, 1}, + {188, 102, 8, 11, 11}, + {135, 60, 45, 38, 1} + }, + { {118, 160, 92, 144, 8}, + {161, 9, 181, 145, 2}, + {16, 147, 160, 86, 14}, + {72, 154, 217, 8, 5} + }, + { {14, 217, 3, 223, 6}, + {218, 118, 208, 13, 11}, + {111, 188, 9, 183, 0}, + {219, 0, 182, 229, 11} + }, + { {43, 112, 183, 113, 13}, + {87, 197, 227, 93, 4}, + {184, 238, 208, 237, 4}, + {43, 172, 122, 62, 10} + }, + { {78, 140, 226, 119, 3}, + {174, 208, 146, 173, 13}, + {206, 228, 115, 23, 2}, + {187, 84, 144, 183, 5} + }, + { {164, 109, 4, 140, 4}, + {137, 39, 96, 34, 10}, + {35, 18, 11, 98, 5}, + {84, 64, 110, 73, 1} + }, + { {174, 72, 136, 91, 10}, + {130, 180, 165, 15, 9}, + {93, 161, 17, 39, 5}, + {159, 10, 82, 212, 1} + }, + { {10, 163, 70, 143, 3}, + {47, 115, 24, 5, 3}, + {207, 22, 44, 85, 0}, + {202, 1, 140, 239, 4} + }, + { {106, 225, 111, 19, 7}, + {55, 87, 244, 133, 5}, + {236, 143, 104, 117, 6}, + {170, 18, 254, 174, 12} + }, + { {149, 35, 13, 186, 13}, + {149, 43, 207, 66, 3}, + {181, 219, 12, 74, 9}, + {196, 47, 61, 74, 9} + }, + { {228, 136, 181, 77, 4}, + {152, 161, 112, 158, 12}, + {43, 42, 209, 18, 7}, + {55, 144, 232, 81, 9} + }, + { {121, 10, 75, 108, 10}, + {58, 120, 47, 200, 8}, + {83, 109, 37, 9, 14}, + {17, 63, 65, 229, 12} + }, + { {64, 35, 127, 147, 9}, + {53, 67, 141, 148, 7}, + {156, 159, 236, 64, 2}, + {226, 155, 28, 42, 12} + }, + { {45, 191, 41, 200, 3}, + {215, 50, 60, 104, 14}, + {193, 57, 79, 219, 4}, + {113, 99, 196, 206, 11} + }, + { {202, 214, 76, 54, 0}, + {106, 5, 158, 163, 1}, + {6, 195, 38, 181, 3}, + {140, 87, 154, 5, 6} + }, + { {211, 175, 89, 151, 10}, + {57, 26, 157, 247, 11}, + {94, 153, 175, 92, 11}, + {222, 251, 149, 137, 12} + }, + { {157, 46, 167, 143, 5}, + {159, 233, 72, 102, 15}, + {175, 30, 87, 75, 9}, + {246, 97, 41, 127, 9} + }, + { {34, 211, 98, 6, 7}, + {108, 86, 120, 1, 5}, + {230, 4, 108, 180, 4}, + {168, 1, 230, 163, 6} + }, + { {204, 169, 43, 134, 6}, + {155, 82, 84, 130, 15}, + {102, 29, 73, 83, 3}, + {244, 18, 164, 173, 9} + }, + { {85, 43, 159, 167, 1}, + {157, 203, 14, 212, 11}, + {142, 95, 157, 74, 10}, + {210, 183, 13, 59, 9} + }, + { {203, 178, 173, 205, 11}, + {95, 177, 29, 207, 6}, + {219, 59, 84, 221, 3}, + {111, 59, 136, 223, 10} + }, + { {68, 250, 70, 198, 7}, + {237, 85, 88, 136, 11}, + {230, 54, 37, 242, 2}, + {209, 17, 170, 171, 7} + }, + { {231, 38, 143, 185, 7}, + {149, 241, 238, 231, 2}, + {233, 223, 22, 78, 7}, + {78, 119, 120, 250, 9} + }, + { {36, 175, 23, 170, 2}, + {145, 115, 58, 48, 11}, + {69, 94, 143, 82, 4}, + {208, 197, 204, 232, 9} + }, + { {67, 232, 194, 173, 10}, + {41, 244, 19, 197, 10}, + {91, 84, 49, 124, 2}, + {90, 60, 130, 249, 4} + }, + { {145, 205, 26, 71, 13}, + {12, 78, 85, 126, 9}, + {190, 37, 139, 56, 9}, + {151, 234, 167, 35, 0} + }, + { {104, 5, 2, 194, 10}, + {2, 82, 33, 168, 3}, + {84, 52, 10, 1, 6}, + {193, 88, 68, 164, 0} + }, + { {5, 213, 122, 29, 10}, + {232, 118, 149, 116, 4}, + {91, 133, 234, 186, 0}, + {34, 234, 150, 225, 7} + }, + { {88, 51, 178, 20, 8}, + {75, 202, 137, 144, 4}, + {18, 132, 220, 193, 10}, + {32, 153, 21, 61, 2} + }, + { {207, 23, 24, 178, 4}, + {194, 2, 206, 243, 3}, + {36, 209, 142, 143, 3}, + {204, 247, 52, 4, 3} + }, + { {86, 20, 124, 139, 2}, + {224, 57, 4, 181, 7}, + {77, 19, 226, 134, 10}, + {234, 210, 9, 192, 7} + }, + { {254, 81, 189, 45, 1}, + {222, 175, 38, 151, 4}, + {139, 75, 216, 167, 15}, + {46, 150, 79, 87, 11} + }, + { {48, 238, 165, 168, 4}, + {17, 173, 122, 32, 14}, + {33, 90, 87, 112, 12}, + {112, 69, 235, 88, 8} + }, + { {94, 27, 78, 166, 2}, + {234, 91, 14, 129, 11}, + {70, 87, 45, 135, 10}, + {216, 23, 13, 165, 7} + }, + { {37, 57, 142, 52, 3}, + {205, 211, 166, 64, 8}, + {194, 199, 25, 202, 4}, + {16, 38, 92, 187, 3} + }, + { {71, 238, 53, 187, 2}, + {145, 53, 154, 245, 15}, + {77, 218, 199, 126, 2}, + {250, 245, 154, 200, 9} + }, + { {105, 226, 147, 152, 11}, + {23, 244, 185, 208, 2}, + {209, 156, 148, 121, 6}, + {64, 185, 210, 254, 8} + }, + { {203, 235, 3, 139, 4}, + {19, 102, 88, 199, 11}, + {45, 28, 13, 125, 3}, + {222, 49, 166, 108, 8} + }, + { {151, 33, 193, 117, 9}, + {189, 138, 131, 79, 0}, + {154, 232, 56, 78, 9}, + {15, 44, 21, 27, 13} + }, + { {117, 81, 72, 151, 9}, + {236, 14, 165, 196, 3}, + {158, 145, 40, 170, 14}, + {194, 58, 87, 3, 7} + }, + { {87, 243, 99, 229, 3}, + {253, 94, 26, 205, 6}, + {202, 124, 108, 254, 10}, + {107, 53, 135, 171, 15} + }, + { {174, 100, 20, 104, 0}, + {131, 37, 34, 59, 0}, + {1, 98, 130, 103, 5}, + {13, 196, 74, 76, 1} + }, + { {126, 147, 161, 155, 5}, + {214, 170, 248, 133, 7}, + {173, 152, 92, 151, 14}, + {234, 17, 245, 86, 11} + }, + { {169, 131, 209, 245, 2}, + {58, 146, 186, 94, 2}, + {74, 248, 188, 25, 5}, + {71, 165, 212, 149, 12} + }, + { {100, 91, 1, 175, 9}, + {220, 38, 43, 132, 11}, + {159, 88, 13, 162, 6}, + {210, 29, 70, 67, 11} + }, + { {13, 167, 121, 86, 5}, + {191, 2, 220, 120, 5}, + {166, 169, 238, 91, 0}, + {161, 227, 180, 15, 13} + }, + { {114, 16, 159, 147, 13}, + {84, 201, 229, 149, 3}, + {188, 159, 144, 132, 14}, + {202, 154, 121, 50, 10} + }, + { {141, 12, 71, 198, 11}, + {190, 81, 1, 106, 11}, + {214, 62, 35, 11, 1}, + {213, 104, 8, 167, 13} + }, + { {109, 229, 76, 66, 3}, + {167, 23, 52, 232, 1}, + {196, 35, 42, 123, 6}, + {129, 114, 206, 142, 5} + }, + { {241, 35, 191, 102, 11}, + {29, 219, 47, 218, 5}, + {214, 111, 220, 72, 15}, + {165, 191, 77, 187, 8} + }, + { {180, 231, 234, 99, 7}, + {165, 222, 126, 46, 5}, + {236, 101, 126, 114, 13}, + {167, 71, 231, 186, 5} + }, + { {229, 224, 226, 108, 5}, + {173, 228, 114, 202, 4}, + {163, 100, 112, 122, 7}, + {37, 52, 226, 123, 5} + }, + { {90, 49, 4, 158, 15}, + {79, 59, 193, 129, 3}, + {247, 146, 8, 197, 10}, + {200, 24, 61, 207, 2} + }, + { {137, 248, 81, 23, 11}, + {127, 20, 145, 86, 9}, + {222, 136, 161, 249, 1}, + {150, 168, 146, 143, 14} + }, + { {111, 187, 159, 46, 10}, + {219, 243, 63, 209, 9}, + {87, 79, 157, 223, 6}, + {152, 191, 204, 253, 11} + }, + { {80, 175, 172, 250, 6}, + {1, 187, 222, 168, 15}, + {101, 243, 95, 80, 10}, + {241, 87, 189, 216, 0} + }, + { {181, 113, 7, 57, 6}, + {209, 127, 226, 70, 0}, + {105, 206, 8, 234, 13}, + {6, 36, 127, 232, 11} + }, + { {121, 119, 47, 9, 2}, + {83, 127, 44, 228, 4}, + {73, 15, 78, 233, 14}, + {34, 115, 79, 236, 10} + }, + { {177, 29, 65, 73, 3}, + {116, 58, 32, 110, 8}, + {201, 40, 43, 136, 13}, + {23, 96, 69, 194, 14} + }, + { {29, 227, 35, 74, 14}, + {147, 126, 89, 72, 5}, + {117, 44, 76, 123, 8}, + {161, 41, 167, 236, 9} + }, + { {179, 148, 34, 108, 13}, + {76, 104, 115, 107, 4}, + {179, 100, 66, 156, 13}, + {45, 108, 225, 99, 2} + }, + { {44, 126, 92, 173, 1}, + {239, 37, 46, 52, 10}, + {139, 83, 167, 227, 4}, + {82, 199, 74, 79, 7} + }, + { {95, 115, 48, 151, 13}, + {207, 14, 201, 213, 7}, + {190, 144, 204, 239, 10}, + {234, 185, 55, 15, 3} + }, + { {69, 251, 224, 160, 13}, + {229, 134, 91, 192, 14}, + {176, 80, 125, 250, 2}, + {112, 61, 166, 26, 7} + }, + { {9, 122, 74, 176, 0}, + {99, 68, 142, 64, 10}, + {0, 213, 37, 233, 0}, + {80, 39, 18, 44, 6} + }, + { {179, 119, 146, 163, 10}, + {65, 222, 43, 119, 3}, + {92, 84, 158, 236, 13}, + {206, 237, 71, 184, 2} + }, + { {98, 35, 175, 220, 7}, + {29, 243, 236, 137, 6}, + {227, 191, 92, 68, 6}, + {105, 19, 124, 251, 8} + }, + { {108, 155, 99, 108, 9}, + {254, 98, 59, 136, 12}, + {147, 108, 109, 147, 6}, + {49, 29, 196, 103, 15} + }, + { {155, 78, 142, 46, 9}, + {14, 237, 15, 99, 9}, + {151, 71, 23, 45, 9}, + {156, 111, 11, 119, 0} + }, + { {249, 195, 194, 21, 3}, + {46, 222, 184, 198, 0}, + {202, 132, 60, 57, 15}, + {6, 49, 215, 183, 4} + }, + { {119, 43, 15, 234, 11}, + {149, 123, 47, 201, 11}, + {213, 127, 13, 78, 14}, + {217, 63, 77, 234, 9} + }, + { {102, 250, 154, 171, 4}, + {193, 228, 126, 149, 11}, + {45, 85, 149, 246, 6}, + {218, 151, 226, 120, 3} + }, + { {116, 98, 77, 223, 13}, + {189, 45, 237, 140, 3}, + {191, 187, 36, 98, 14}, + {195, 27, 123, 75, 13} + }, + { {25, 222, 78, 102, 4}, + {106, 77, 94, 104, 9}, + {38, 103, 39, 185, 8}, + {145, 103, 171, 37, 6} + }, + { {148, 41, 230, 53, 14}, + {169, 219, 195, 6, 12}, + {122, 198, 121, 66, 9}, + {54, 12, 61, 185, 5} + }, + { {76, 236, 163, 246, 4}, + {155, 196, 210, 168, 15}, + {38, 252, 83, 115, 2}, + {241, 84, 178, 61, 9} + }, + { {164, 180, 6, 106, 6}, + {193, 113, 114, 42, 1}, + {101, 102, 2, 210, 5}, + {133, 68, 232, 232, 3} + }, + { {30, 86, 88, 215, 4}, + {234, 12, 204, 61, 3}, + {46, 177, 166, 167, 8}, + {203, 195, 51, 5, 7} + }, + { {219, 73, 192, 161, 2}, + {34, 158, 2, 199, 10}, + {72, 80, 57, 45, 11}, + {94, 52, 7, 148, 4} + }, + { {79, 81, 144, 218, 7}, + {198, 182, 192, 217, 3}, + {229, 176, 152, 175, 2}, + {201, 176, 54, 214, 3} + }, + { {1, 67, 44, 173, 5}, + {12, 39, 78, 68, 6}, + {171, 83, 76, 40, 0}, + {98, 39, 46, 67, 0} + }, + { {82, 252, 40, 115, 9}, + {69, 12, 151, 173, 13}, + {156, 225, 67, 244, 10}, + {187, 94, 147, 10, 2} + }, + { {67, 147, 62, 210, 15}, + {68, 83, 221, 217, 7}, + {244, 183, 204, 156, 2}, + {233, 187, 188, 162, 2} + }, + { {107, 7, 29, 172, 0}, + {26, 35, 46, 241, 2}, + {3, 91, 142, 13, 6}, + {72, 247, 76, 69, 8} + }, + { {81, 13, 171, 142, 1}, + {28, 234, 4, 224, 15}, + {135, 29, 91, 8, 10}, + {240, 114, 5, 115, 8} + }, + { {80, 201, 26, 157, 10}, + {8, 126, 149, 148, 10}, + {91, 149, 137, 48, 10}, + {82, 154, 151, 225, 0} + }, + { {95, 229, 148, 45, 0}, + {139, 175, 18, 245, 0}, + {11, 66, 154, 127, 10}, + {10, 244, 143, 93, 1} + }, + { {88, 194, 177, 178, 10}, + {18, 156, 155, 144, 7}, + {84, 216, 212, 49, 10}, + {224, 157, 147, 148, 8} + }, + { {137, 65, 77, 139, 14}, + {50, 55, 69, 70, 3}, + {125, 27, 40, 41, 1}, + {198, 42, 46, 196, 12} + }, + { {132, 10, 38, 120, 5}, + {132, 97, 202, 10, 12}, + {161, 230, 69, 2, 1}, + {53, 5, 56, 98, 1} + }, + { {185, 96, 91, 1, 13}, + {55, 76, 101, 86, 0}, + {184, 13, 160, 105, 13}, + {6, 170, 99, 46, 12} + }, + { {20, 186, 220, 247, 7}, + {237, 153, 222, 28, 11}, + {238, 243, 181, 210, 8}, + {211, 135, 185, 155, 7} + }, + { {246, 219, 203, 198, 4}, + {248, 206, 124, 139, 11}, + {38, 61, 61, 182, 15}, + {221, 19, 231, 49, 15} + }, + { {192, 171, 73, 169, 0}, + {49, 34, 30, 134, 10}, + {9, 89, 45, 80, 3}, + {86, 23, 132, 72, 12} + }, + { {153, 38, 27, 157, 2}, + {27, 120, 140, 118, 2}, + {75, 157, 134, 73, 9}, + {70, 227, 17, 237, 8} + }, + { {97, 25, 251, 19, 6}, + {112, 210, 228, 212, 13}, + {108, 141, 249, 136, 6}, + {178, 178, 116, 176, 14} + }, + { {136, 247, 2, 236, 5}, + {79, 102, 90, 42, 2}, + {163, 116, 14, 241, 1}, + {69, 69, 166, 111, 2} + }, + { {59, 109, 198, 146, 3}, + {39, 223, 160, 97, 11}, + {196, 150, 59, 109, 12}, + {216, 96, 95, 190, 4} + }, + { {78, 98, 149, 182, 0}, + {155, 133, 138, 145, 3}, + {6, 218, 148, 103, 2}, + {200, 149, 26, 29, 9} + }, + { {217, 253, 38, 220, 10}, + {75, 127, 145, 234, 14}, + {83, 182, 75, 249, 11}, + {117, 120, 159, 237, 2} + }, + { {185, 170, 215, 180, 13}, + {63, 201, 251, 82, 10}, + {178, 222, 181, 89, 13}, + {84, 173, 249, 63, 12} + }, + { {77, 237, 86, 136, 15}, + {167, 119, 81, 240, 10}, + {241, 22, 171, 123, 2}, + {80, 248, 174, 238, 5} + }, + { {32, 85, 135, 53, 1}, + {92, 199, 162, 36, 0}, + {138, 206, 26, 160, 4}, + {2, 68, 94, 51, 10} + }, + { {229, 194, 49, 87, 1}, + {156, 4, 184, 222, 5}, + {142, 168, 196, 58, 7}, + {167, 177, 210, 3, 9} + }, + { {203, 185, 136, 37, 6}, + {75, 146, 86, 199, 8}, + {106, 65, 25, 221, 3}, + {30, 54, 164, 157, 2} + }, + { {43, 23, 42, 185, 9}, + {70, 98, 175, 101, 6}, + {153, 213, 78, 141, 4}, + {106, 111, 84, 102, 2} + }, + { {171, 204, 25, 105, 3}, + {22, 52, 54, 127, 8}, + {201, 105, 131, 61, 5}, + {31, 230, 194, 198, 8} + }, + { {136, 158, 50, 170, 15}, + {70, 112, 91, 50, 15}, + {245, 84, 199, 145, 1}, + {244, 205, 160, 230, 2} + }, + { {13, 103, 151, 181, 12}, + {155, 199, 203, 116, 2}, + {58, 222, 158, 107, 0}, + {66, 237, 62, 61, 9} + }, + { {110, 236, 61, 135, 6}, + {155, 21, 116, 181, 15}, + {110, 27, 195, 119, 6}, + {250, 210, 234, 141, 9} + }, + { {163, 18, 150, 137, 14}, + {64, 241, 105, 87, 2}, + {121, 22, 148, 140, 5}, + {78, 169, 104, 240, 2} + }, + { {49, 39, 126, 125, 11}, + {45, 123, 175, 124, 4}, + {219, 231, 238, 72, 12}, + {35, 239, 93, 235, 4} + }, + { {197, 59, 33, 41, 14}, + {209, 50, 75, 198, 12}, + {121, 72, 77, 202, 3}, + {54, 61, 36, 200, 11} + }, + { {14, 92, 116, 153, 15}, + {230, 53, 193, 53, 14}, + {249, 146, 227, 167, 0}, + {122, 200, 58, 198, 7} + }, + { {206, 216, 31, 33, 1}, + {214, 69, 22, 151, 8}, + {136, 79, 129, 183, 3}, + {30, 150, 138, 38, 11} + }, + { {17, 190, 238, 22, 3}, + {109, 217, 156, 96, 13}, + {198, 135, 119, 216, 8}, + {176, 99, 153, 187, 6} + }, + { {91, 246, 119, 192, 6}, + {115, 93, 88, 249, 6}, + {96, 62, 230, 253, 10}, + {105, 241, 171, 172, 14} + }, + { {205, 49, 157, 20, 14}, + {219, 147, 197, 210, 0}, + {114, 139, 152, 203, 3}, + {4, 186, 60, 157, 11} + }, + { {33, 104, 14, 72, 13}, + {5, 101, 101, 72, 8}, + {177, 39, 1, 104, 4}, + {17, 42, 106, 106, 0} + }, + { {40, 39, 132, 250, 11}, + {7, 179, 171, 40, 3}, + {213, 242, 30, 65, 4}, + {193, 77, 92, 222, 0} + }, + { {168, 244, 166, 26, 0}, + {67, 229, 176, 34, 5}, + {5, 134, 82, 241, 5}, + {164, 64, 218, 124, 2} + }, + { {49, 118, 81, 59, 4}, + {113, 44, 234, 116, 1}, + {45, 200, 166, 232, 12}, + {130, 229, 115, 72, 14} + }, + { {247, 105, 206, 110, 2}, + {169, 255, 38, 203, 9}, + {71, 103, 57, 110, 15}, + {157, 54, 79, 249, 5} + }, + { {83, 146, 163, 177, 13}, + {84, 200, 219, 197, 6}, + {184, 220, 84, 156, 10}, + {106, 61, 177, 50, 10} + }, + { {88, 101, 173, 108, 15}, + {31, 191, 71, 168, 4}, + {243, 107, 90, 97, 10}, + {33, 94, 47, 223, 8} + }, + { {190, 115, 121, 11, 0}, + {243, 46, 44, 23, 5}, + {13, 9, 236, 231, 13}, + {174, 131, 71, 76, 15} + }, + { {90, 232, 238, 91, 11}, + {39, 253, 149, 141, 13}, + {221, 167, 113, 117, 10}, + {187, 26, 155, 254, 4} + }, + { {139, 106, 57, 122, 0}, + {19, 36, 142, 91, 13}, + {5, 233, 197, 109, 1}, + {189, 167, 18, 76, 8} + }, + { {163, 233, 94, 124, 14}, + {41, 119, 247, 91, 8}, + {115, 231, 169, 124, 5}, + {29, 174, 254, 233, 4} + }, + { {91, 159, 159, 132, 13}, + {94, 203, 93, 241, 10}, + {178, 31, 159, 157, 10}, + {88, 251, 173, 55, 10} + }, + { {47, 75, 147, 199, 11}, + {158, 214, 41, 93, 11}, + {222, 60, 157, 47, 4}, + {219, 169, 70, 183, 9} + }, + { {76, 117, 32, 169, 6}, + {195, 54, 66, 164, 6}, + {105, 80, 74, 227, 2}, + {98, 84, 38, 204, 3} + }, + { {98, 163, 2, 254, 14}, + {9, 114, 251, 137, 3}, + {119, 244, 12, 84, 6}, + {201, 29, 244, 233, 0} + }, + { {103, 190, 132, 139, 7}, + {197, 177, 120, 229, 11}, + {237, 18, 23, 222, 6}, + {218, 113, 232, 218, 3} + }, + { {183, 81, 31, 133, 12}, + {216, 79, 101, 87, 2}, + {58, 31, 136, 174, 13}, + {78, 170, 111, 33, 11} + }, + { {24, 89, 92, 70, 7}, + {110, 31, 68, 24, 9}, + {230, 35, 169, 161, 8}, + {145, 130, 47, 135, 6} + }, + { {21, 230, 55, 117, 0}, + {153, 77, 154, 124, 4}, + {10, 238, 198, 122, 8}, + {35, 229, 155, 41, 9} + }, + { {152, 85, 193, 87, 13}, + {126, 142, 193, 46, 1}, + {190, 168, 58, 161, 9}, + {135, 72, 55, 23, 14} + }, + { {221, 31, 87, 116, 9}, + {254, 75, 139, 250, 8}, + {146, 238, 175, 139, 11}, + {21, 253, 29, 39, 15} + }, + { {116, 139, 16, 186, 5}, + {132, 42, 250, 144, 11}, + {165, 208, 141, 18, 14}, + {208, 149, 245, 66, 1} + }, + { {28, 253, 149, 152, 1}, + {215, 175, 144, 48, 10}, + {129, 154, 155, 243, 8}, + {80, 192, 159, 94, 11} + }, + { {99, 111, 43, 206, 2}, + {25, 118, 44, 233, 15}, + {71, 61, 79, 108, 6}, + {249, 115, 70, 233, 8} + }, + { {175, 140, 36, 152, 13}, + {134, 33, 241, 99, 14}, + {177, 146, 67, 31, 5}, + {124, 104, 248, 70, 1} + }, + { {33, 45, 73, 54, 5}, + {61, 2, 230, 96, 9}, + {166, 201, 43, 72, 4}, + {144, 102, 116, 11, 12} + }, + { {173, 161, 22, 32, 14}, + {131, 83, 115, 82, 0}, + {112, 70, 136, 91, 5}, + {4, 172, 236, 172, 1} + }, + { {239, 49, 13, 89, 13}, + {215, 35, 229, 207, 0}, + {185, 171, 8, 207, 7}, + {15, 58, 124, 78, 11} + }, + { {155, 50, 155, 65, 14}, + {83, 216, 77, 95, 0}, + {120, 45, 148, 205, 9}, + {15, 171, 33, 188, 10} + }, + { {95, 76, 1, 168, 15}, + {150, 60, 67, 225, 10}, + {241, 88, 3, 47, 10}, + {88, 124, 35, 198, 9} + }, + { {110, 151, 175, 238, 0}, + {218, 227, 62, 169, 7}, + {7, 127, 94, 151, 6}, + {233, 87, 204, 117, 11} + }, + { {129, 68, 91, 180, 8}, + {56, 68, 135, 114, 2}, + {18, 221, 162, 40, 1}, + {68, 238, 18, 33, 12} + }, + { {196, 142, 130, 78, 7}, + {140, 240, 88, 170, 9}, + {231, 36, 23, 18, 3}, + {149, 81, 160, 243, 1} + }, + { {225, 39, 182, 160, 12}, + {1, 195, 107, 242, 6}, + {48, 86, 222, 72, 7}, + {100, 253, 108, 56, 0} + }, + { {88, 201, 151, 188, 7}, + {30, 255, 210, 144, 10}, + {227, 222, 153, 49, 10}, + {80, 148, 191, 247, 8} + }, + { {37, 121, 224, 205, 7}, + {237, 182, 96, 76, 14}, + {235, 48, 121, 234, 4}, + {115, 32, 102, 219, 7} + }, + { {107, 93, 4, 34, 7}, + {70, 23, 98, 225, 9}, + {228, 66, 11, 173, 6}, + {152, 116, 110, 134, 2} + }, + { {109, 39, 67, 156, 2}, + {187, 114, 168, 224, 2}, + {67, 156, 46, 75, 6}, + {64, 113, 84, 237, 13} + }, + { {168, 101, 37, 190, 2}, + {27, 55, 162, 34, 7}, + {71, 218, 74, 97, 5}, + {228, 68, 94, 205, 8} + }, + { {197, 187, 243, 235, 0}, + {241, 226, 26, 222, 15}, + {13, 124, 253, 218, 3}, + {247, 181, 132, 120, 15} + }, + { {224, 138, 63, 86, 10}, + {24, 81, 189, 154, 13}, + {86, 175, 197, 16, 7}, + {181, 155, 216, 161, 8} + }, + { {209, 250, 49, 233, 7}, + {85, 60, 90, 222, 14}, + {233, 120, 197, 248, 11}, + {119, 181, 163, 202, 10} + }, + { {102, 72, 123, 117, 10}, + {184, 84, 167, 157, 12}, + {90, 237, 225, 38, 6}, + {59, 158, 82, 161, 13} + }, + { {152, 233, 237, 143, 5}, + {63, 175, 84, 6, 15}, + {175, 27, 121, 113, 9}, + {246, 2, 175, 95, 12} + }, + { {156, 76, 216, 209, 15}, + {166, 156, 197, 62, 10}, + {248, 177, 179, 35, 9}, + {87, 202, 51, 150, 5} + }, + { {219, 162, 17, 122, 14}, + {19, 56, 219, 219, 1}, + {117, 232, 132, 93, 11}, + {141, 189, 177, 204, 8} + }, + { {252, 157, 45, 143, 13}, + {222, 43, 117, 166, 15}, + {191, 27, 75, 147, 15}, + {246, 90, 237, 71, 11} + }, + { {42, 216, 44, 171, 15}, + {70, 53, 119, 5, 15}, + {253, 83, 65, 181, 4}, + {250, 14, 234, 198, 2} + }, + { {203, 77, 206, 196, 5}, + {46, 199, 68, 235, 10}, + {162, 55, 59, 45, 3}, + {93, 114, 46, 55, 4} + }, + { {212, 230, 126, 105, 10}, + {161, 125, 31, 190, 4}, + {89, 103, 230, 114, 11}, + {39, 223, 139, 232, 5} + }, + { {182, 118, 99, 52, 5}, + {253, 76, 234, 35, 4}, + {162, 204, 102, 230, 13}, + {44, 69, 115, 43, 15} + }, + { {129, 161, 64, 232, 13}, + {37, 34, 83, 74, 2}, + {177, 112, 40, 88, 1}, + {69, 44, 164, 74, 4} + }, + { {36, 248, 181, 42, 13}, + {213, 165, 115, 16, 13}, + {181, 74, 209, 242, 4}, + {176, 140, 234, 90, 11} + }, + { {33, 212, 75, 137, 7}, + {116, 116, 116, 100, 2}, + {233, 29, 34, 184, 4}, + {66, 98, 226, 226, 14} + }, + { {143, 130, 58, 23, 11}, + {142, 80, 157, 87, 5}, + {222, 133, 196, 31, 1}, + {174, 171, 144, 167, 1} + }, + { {246, 113, 26, 188, 10}, + {201, 126, 167, 147, 2}, + {83, 213, 136, 230, 15}, + {76, 158, 87, 233, 3} + }, + { {63, 94, 2, 121, 7}, + {198, 124, 234, 109, 8}, + {233, 228, 7, 175, 12}, + {27, 101, 115, 230, 3} + }, + { {205, 212, 228, 99, 15}, + {230, 149, 83, 238, 5}, + {252, 98, 114, 187, 3}, + {167, 124, 170, 150, 7} + }, + { {89, 70, 5, 49, 5}, + {22, 13, 202, 228, 0}, + {168, 202, 6, 41, 10}, + {2, 117, 59, 6, 8} + }, + { {32, 186, 102, 97, 13}, + {101, 65, 123, 12, 12}, + {184, 102, 101, 208, 4}, + {51, 13, 232, 42, 6} + }, + { {151, 191, 26, 90, 0}, + {193, 106, 156, 123, 9}, + {5, 165, 143, 222, 9}, + {157, 227, 149, 104, 3} + }, + { {126, 66, 218, 161, 8}, + {162, 204, 47, 149, 2}, + {24, 85, 180, 39, 14}, + {74, 159, 67, 52, 5} + }, + { {98, 148, 45, 213, 5}, + {92, 1, 244, 173, 6}, + {170, 187, 66, 148, 6}, + {107, 82, 248, 3, 10} + }, + { {177, 229, 50, 4, 7}, + {13, 94, 112, 114, 4}, + {226, 4, 202, 120, 13}, + {36, 224, 231, 171, 0} + }, + { {222, 100, 233, 20, 12}, + {187, 140, 197, 163, 4}, + {50, 137, 114, 103, 11}, + {44, 90, 51, 29, 13} + }, + { {71, 94, 177, 229, 0}, + {216, 132, 10, 253, 14}, + {10, 120, 215, 174, 2}, + {123, 245, 2, 17, 11} + }, + { {25, 165, 108, 166, 9}, + {47, 11, 23, 96, 7}, + {150, 83, 106, 89, 8}, + {224, 110, 141, 15, 4} + }, + { {149, 5, 117, 79, 13}, + {188, 43, 65, 126, 5}, + {191, 42, 234, 10, 9}, + {167, 232, 45, 67, 13} + }, + { {54, 171, 61, 147, 0}, + {145, 11, 188, 21, 15}, + {12, 155, 205, 86, 12}, + {250, 131, 221, 8, 9} + }, + { {87, 132, 157, 120, 1}, + {148, 169, 150, 249, 0}, + {129, 235, 146, 30, 10}, + {9, 246, 153, 82, 9} + }, + { {164, 135, 244, 206, 4}, + {168, 163, 120, 58, 7}, + {39, 50, 254, 18, 5}, + {229, 193, 236, 81, 5} + }, + { {177, 1, 150, 215, 4}, + {8, 203, 224, 94, 3}, + {46, 182, 152, 8, 13}, + {199, 160, 125, 49, 0} + }, + { {202, 139, 220, 248, 6}, + {34, 179, 222, 155, 10}, + {97, 243, 189, 21, 3}, + {93, 151, 188, 212, 4} + }, + { {241, 104, 15, 187, 1}, + {21, 109, 166, 198, 11}, + {141, 223, 1, 104, 15}, + {214, 54, 91, 106, 8} + }, + { {119, 235, 139, 132, 9}, + {157, 206, 61, 193, 10}, + {146, 29, 29, 126, 14}, + {88, 59, 199, 59, 9} + }, + { {2, 83, 18, 189, 4}, + {72, 102, 202, 21, 2}, + {43, 212, 140, 164, 0}, + {74, 133, 54, 97, 2} + }, + { {29, 42, 141, 92, 1}, + {159, 169, 140, 72, 8}, + {131, 171, 21, 75, 8}, + {17, 35, 25, 95, 9} + }, + { {39, 117, 108, 47, 10}, + {233, 55, 39, 101, 5}, + {95, 67, 106, 238, 4}, + {170, 110, 78, 201, 7} + }, + { {61, 80, 83, 188, 9}, + {254, 108, 163, 80, 2}, + {147, 220, 160, 171, 12}, + {64, 172, 83, 103, 15} + }, + { {120, 181, 174, 182, 4}, + {75, 203, 246, 160, 7}, + {38, 215, 90, 209, 14}, + {224, 86, 253, 61, 2} + }, + { {42, 93, 133, 78, 1}, + {94, 167, 32, 41, 9}, + {135, 42, 27, 165, 4}, + {153, 64, 78, 87, 10} + }, + { {0, 239, 149, 206, 12}, + {25, 167, 89, 56, 11}, + {55, 58, 159, 112, 0}, + {209, 201, 174, 89, 8} + }, + { {198, 248, 201, 176, 10}, + {241, 148, 151, 131, 10}, + {80, 217, 49, 246, 3}, + {92, 30, 146, 152, 15} + }, + { {52, 138, 244, 180, 8}, + {168, 137, 187, 16, 14}, + {18, 210, 245, 18, 12}, + {112, 141, 217, 17, 5} + }, + { {154, 221, 191, 118, 3}, + {94, 223, 150, 59, 13}, + {198, 239, 219, 181, 9}, + {189, 198, 159, 183, 10} + }, + { {68, 183, 153, 113, 1}, + {213, 130, 158, 188, 0}, + {136, 233, 158, 210, 2}, + {3, 215, 148, 26, 11} + }, + { {53, 31, 237, 192, 9}, + {244, 139, 45, 104, 14}, + {144, 59, 127, 138, 12}, + {113, 107, 77, 18, 15} + }, + { {240, 132, 133, 185, 5}, + {20, 169, 242, 166, 2}, + {169, 218, 18, 16, 15}, + {70, 84, 249, 82, 8} + }, + { {130, 188, 93, 116, 5}, + {125, 1, 214, 59, 8}, + {162, 235, 163, 212, 1}, + {29, 198, 184, 11, 14} + }, + { {58, 134, 179, 151, 15}, + {30, 216, 249, 53, 7}, + {254, 156, 214, 21, 12}, + {234, 201, 241, 183, 8} + }, + { {140, 232, 137, 29, 15}, + {159, 180, 213, 6, 8}, + {251, 137, 17, 115, 1}, + {22, 10, 178, 223, 9} + }, + { {63, 123, 205, 83, 8}, + {243, 143, 173, 77, 9}, + {28, 171, 61, 239, 12}, + {155, 43, 95, 28, 15} + }, + { {180, 13, 145, 70, 7}, + {156, 154, 96, 58, 9}, + {230, 40, 155, 2, 13}, + {149, 192, 101, 147, 9} + }, + { {109, 172, 86, 182, 6}, + {171, 81, 242, 240, 11}, + {102, 214, 163, 91, 6}, + {208, 244, 248, 173, 5} + }, + { {88, 149, 105, 0, 13}, + {118, 10, 85, 160, 4}, + {176, 9, 106, 145, 10}, + {32, 90, 165, 6, 14} + }, + { {76, 163, 63, 89, 3}, + {151, 115, 156, 156, 4}, + {201, 175, 204, 83, 2}, + {35, 147, 156, 238, 9} + }, + { {53, 167, 215, 80, 1}, + {181, 203, 184, 120, 0}, + {128, 174, 190, 90, 12}, + {1, 225, 221, 58, 13} + }, + { {22, 233, 116, 244, 11}, + {173, 31, 147, 25, 14}, + {210, 242, 233, 118, 8}, + {121, 140, 159, 139, 5} + }, + { {106, 219, 126, 99, 3}, + {102, 87, 62, 157, 13}, + {204, 103, 237, 181, 6}, + {187, 151, 206, 166, 6} + }, + { {172, 254, 199, 138, 5}, + {247, 229, 120, 34, 11}, + {165, 30, 55, 243, 5}, + {212, 65, 234, 126, 15} + }, + { {195, 109, 112, 53, 6}, + {41, 22, 194, 247, 12}, + {106, 192, 235, 108, 3}, + {62, 244, 54, 137, 4} + }, + { {133, 82, 84, 245, 6}, + {232, 21, 202, 94, 2}, + {106, 242, 164, 170, 1}, + {71, 165, 58, 129, 7} + }, + { {211, 112, 148, 110, 7}, + {77, 189, 66, 219, 1}, + {231, 98, 144, 236, 11}, + {141, 180, 43, 219, 2} + }, + { {157, 177, 206, 126, 12}, + {235, 235, 215, 74, 1}, + {55, 231, 56, 219, 9}, + {133, 46, 189, 125, 7} + }, + { {163, 136, 217, 180, 14}, + {56, 144, 247, 83, 10}, + {114, 217, 177, 28, 5}, + {92, 174, 240, 145, 12} + }, + { {98, 252, 134, 118, 14}, + {73, 213, 243, 169, 9}, + {118, 230, 19, 244, 6}, + {153, 92, 250, 185, 2} + }, + { {209, 228, 206, 15, 2}, + {41, 253, 20, 230, 1}, + {79, 7, 50, 120, 11}, + {134, 114, 139, 249, 4} + }, + { {224, 197, 119, 135, 0}, + {56, 71, 48, 182, 7}, + {14, 30, 234, 48, 7}, + {230, 208, 206, 33, 12} + }, + { {136, 137, 241, 121, 7}, + {54, 178, 210, 30, 12}, + {233, 232, 249, 17, 1}, + {55, 132, 180, 214, 12} + }, + { {175, 103, 124, 158, 0}, + {171, 39, 172, 115, 7}, + {7, 147, 238, 111, 5}, + {236, 227, 94, 77, 5} + }, + { {250, 135, 32, 206, 8}, + {10, 42, 57, 171, 7}, + {23, 48, 78, 21, 15}, + {237, 89, 197, 69, 0} + }, + { {32, 136, 108, 197, 14}, + {40, 17, 117, 12, 14}, + {122, 51, 97, 16, 4}, + {115, 10, 232, 129, 4} + }, + { {26, 153, 204, 140, 14}, + {106, 187, 85, 1, 10}, + {115, 19, 57, 149, 8}, + {88, 10, 173, 213, 6} + }, + { {136, 165, 101, 91, 1}, + {55, 35, 144, 46, 5}, + {141, 170, 106, 81, 1}, + {167, 64, 156, 78, 12} + }, + { {214, 247, 236, 2, 5}, + {229, 143, 92, 163, 5}, + {164, 3, 126, 246, 11}, + {172, 83, 175, 26, 7} + }, + { {244, 246, 145, 8, 4}, + {209, 172, 120, 178, 0}, + {33, 8, 150, 242, 15}, + {4, 209, 227, 88, 11} + }, + { {76, 10, 202, 63, 5}, + {174, 224, 206, 132, 9}, + {175, 197, 53, 3, 2}, + {146, 23, 48, 119, 5} + }, + { {31, 5, 164, 12, 7}, + {142, 187, 64, 97, 4}, + {227, 2, 90, 15, 8}, + {40, 96, 45, 215, 1} + }, + { {250, 93, 132, 124, 6}, + {74, 191, 226, 171, 8}, + {99, 226, 27, 165, 15}, + {29, 84, 127, 213, 2} + }, + { {19, 29, 173, 215, 12}, + {88, 139, 197, 109, 15}, + {62, 187, 91, 140, 8}, + {251, 106, 61, 17, 10} + }, + { {195, 96, 214, 57, 15}, + {37, 245, 195, 215, 0}, + {249, 198, 176, 108, 3}, + {14, 188, 58, 250, 4} + }, + { {126, 199, 122, 149, 3}, + {174, 94, 188, 181, 6}, + {202, 149, 238, 55, 14}, + {106, 211, 215, 167, 5} + }, + { {45, 212, 161, 248, 1}, + {214, 164, 178, 104, 6}, + {129, 248, 82, 187, 4}, + {97, 100, 210, 86, 11} + }, + { {196, 27, 169, 22, 11}, + {220, 146, 141, 130, 13}, + {214, 137, 93, 130, 3}, + {180, 27, 20, 147, 11} + }, + { {174, 83, 48, 32, 14}, + {194, 22, 107, 19, 4}, + {112, 64, 204, 167, 5}, + {44, 141, 102, 132, 3} + }, + { {137, 195, 118, 107, 2}, + {34, 119, 26, 94, 5}, + {77, 102, 236, 57, 1}, + {167, 165, 142, 228, 4} + }, + { {17, 167, 104, 69, 2}, + {41, 26, 28, 108, 4}, + {74, 33, 110, 88, 8}, + {35, 99, 133, 137, 4} + }, + { {115, 24, 54, 184, 10}, + {64, 121, 163, 209, 14}, + {81, 214, 193, 140, 14}, + {120, 188, 89, 224, 2} + }, + { {152, 195, 136, 238, 2}, + {10, 190, 30, 10, 3}, + {71, 113, 28, 49, 9}, + {197, 7, 135, 213, 0} + }, + { {50, 158, 254, 117, 6}, + {104, 217, 254, 61, 12}, + {106, 231, 247, 148, 12}, + {59, 199, 249, 177, 6} + }, + { {235, 41, 103, 213, 2}, + {59, 83, 160, 207, 14}, + {74, 190, 105, 77, 7}, + {127, 48, 92, 173, 12} + }, + { {118, 246, 246, 147, 13}, + {229, 205, 249, 181, 7}, + {188, 150, 246, 246, 14}, + {234, 217, 251, 58, 7} + }, + { {75, 54, 229, 94, 10}, + {123, 177, 137, 233, 5}, + {87, 170, 118, 205, 2}, + {169, 121, 24, 221, 14} + }, + { {18, 100, 241, 41, 15}, + {53, 188, 67, 53, 4}, + {249, 72, 242, 100, 8}, + {42, 204, 35, 218, 12} + }, + { {195, 216, 179, 216, 12}, + {80, 228, 209, 219, 14}, + {49, 188, 209, 188, 3}, + {125, 184, 178, 112, 10} + }, + { {184, 252, 222, 50, 9}, + {103, 205, 183, 50, 9}, + {148, 199, 179, 241, 13}, + {148, 206, 219, 62, 6} + }, + { {53, 97, 247, 116, 12}, + {185, 207, 227, 88, 4}, + {50, 238, 248, 106, 12}, + {33, 172, 127, 57, 13} + }, + { {116, 255, 31, 22, 6}, + {217, 95, 252, 176, 9}, + {102, 143, 143, 242, 14}, + {144, 211, 255, 169, 11} + }, + { {29, 80, 232, 123, 6}, + {226, 188, 198, 76, 5}, + {109, 225, 112, 171, 8}, + {163, 38, 51, 212, 7} + }, + { {71, 201, 216, 44, 9}, + {172, 166, 23, 209, 8}, + {147, 65, 185, 62, 2}, + {24, 190, 134, 83, 5} + }, + { {198, 131, 135, 46, 9}, + {156, 227, 27, 131, 1}, + {151, 78, 28, 22, 3}, + {140, 29, 140, 115, 9} + }, + { {182, 245, 65, 140, 14}, + {249, 62, 113, 35, 2}, + {115, 24, 42, 246, 13}, + {76, 72, 231, 201, 15} + }, + { {209, 45, 250, 27, 10}, + {33, 250, 133, 246, 13}, + {93, 133, 251, 72, 11}, + {182, 250, 21, 248, 4} + }, + { {123, 222, 164, 209, 14}, + {66, 157, 249, 237, 14}, + {120, 178, 87, 189, 14}, + {123, 121, 251, 148, 2} + }, + { {148, 215, 11, 206, 8}, + {216, 110, 29, 42, 3}, + {23, 61, 14, 178, 9}, + {197, 75, 135, 97, 11} + }, + { {139, 161, 235, 181, 10}, + {59, 210, 151, 71, 6}, + {90, 221, 120, 93, 1}, + {110, 46, 148, 189, 12} + }, + { {2, 76, 23, 111, 5}, + {28, 101, 66, 61, 9}, + {175, 110, 131, 36, 0}, + {155, 196, 42, 99, 8} + }, + { {54, 104, 158, 115, 11}, + {133, 221, 167, 29, 9}, + {220, 231, 145, 102, 12}, + {155, 142, 91, 186, 1} + }, + { {208, 33, 104, 228, 4}, + {41, 10, 70, 138, 6}, + {34, 113, 104, 64, 11}, + {101, 22, 37, 9, 4} + }, + { {155, 143, 16, 45, 14}, + {10, 58, 91, 119, 8}, + {123, 64, 143, 29, 9}, + {30, 237, 165, 197, 0} + }, + { {126, 132, 92, 39, 11}, + {174, 25, 55, 181, 1}, + {222, 67, 162, 23, 14}, + {138, 222, 201, 135, 5} + }, + { {118, 223, 8, 118, 12}, + {200, 14, 255, 169, 9}, + {54, 225, 15, 182, 14}, + {153, 95, 247, 1, 3} + }, + { {251, 188, 21, 149, 7}, + {95, 25, 240, 247, 10}, + {234, 154, 131, 221, 15}, + {94, 240, 249, 143, 10} + }, + { {70, 128, 175, 59, 2}, + {144, 241, 150, 133, 5}, + {77, 207, 80, 22, 2}, + {170, 22, 152, 240, 9} + }, + { {209, 94, 13, 85, 9}, + {92, 13, 141, 238, 8}, + {154, 171, 7, 168, 11}, + {23, 123, 27, 3, 10} + }, + { {140, 34, 108, 129, 4}, + {163, 1, 76, 6, 6}, + {40, 19, 100, 67, 1}, + {102, 3, 40, 12, 5} + }, + { {118, 43, 180, 156, 1}, + {141, 171, 168, 145, 14}, + {131, 146, 221, 70, 14}, + {120, 145, 93, 91, 1} + }, + { {115, 86, 122, 41, 0}, + {96, 108, 46, 245, 4}, + {9, 69, 230, 172, 14}, + {42, 247, 67, 96, 6} + }, + { {65, 92, 222, 11, 1}, + {100, 229, 4, 244, 9}, + {141, 7, 179, 168, 2}, + {146, 242, 10, 114, 6} + }, + { {9, 152, 244, 128, 11}, + {102, 145, 17, 80, 14}, + {208, 18, 241, 153, 0}, + {112, 168, 136, 150, 6} + }, + { {227, 53, 25, 14, 13}, + {93, 34, 101, 243, 1}, + {183, 9, 138, 204, 7}, + {140, 250, 100, 75, 10} + }, + { {54, 133, 243, 244, 3}, + {188, 218, 178, 57, 6}, + {194, 252, 250, 22, 12}, + {105, 196, 213, 179, 13} + }, + { {149, 140, 227, 231, 8}, + {184, 200, 19, 110, 15}, + {30, 124, 115, 26, 9}, + {247, 108, 129, 49, 13} + }, + { {119, 22, 87, 104, 15}, + {244, 121, 107, 249, 0}, + {241, 110, 166, 142, 14}, + {9, 253, 105, 226, 15} + }, + { {192, 211, 150, 95, 4}, + {72, 231, 216, 158, 1}, + {47, 166, 156, 176, 3}, + {135, 145, 190, 113, 2} + }, + { {212, 159, 135, 115, 2}, + {208, 219, 154, 174, 9}, + {76, 238, 31, 146, 11}, + {151, 85, 157, 176, 11} + }, + { {69, 191, 48, 71, 8}, + {201, 2, 25, 252, 13}, + {30, 32, 207, 218, 2}, + {179, 249, 132, 9, 3} + }, + { {178, 247, 139, 151, 9}, + {93, 206, 189, 39, 3}, + {158, 157, 30, 244, 13}, + {206, 75, 215, 59, 10} + }, + { {182, 81, 172, 193, 7}, + {196, 159, 100, 15, 6}, + {232, 51, 88, 166, 13}, + {111, 2, 111, 146, 3} + }, + { {18, 188, 225, 89, 10}, + {113, 184, 145, 45, 12}, + {89, 168, 115, 212, 8}, + {59, 72, 145, 216, 14} + }, + { {142, 109, 66, 22, 5}, + {175, 70, 192, 35, 9}, + {166, 132, 43, 103, 1}, + {156, 64, 54, 47, 5} + }, + { {247, 219, 218, 17, 6}, + {224, 222, 252, 215, 8}, + {104, 133, 189, 190, 15}, + {30, 179, 247, 176, 7} + }, + { {95, 40, 2, 124, 14}, + {139, 120, 195, 201, 8}, + {115, 228, 1, 79, 10}, + {25, 60, 49, 237, 1} + }, + { {2, 18, 169, 157, 14}, + {88, 176, 205, 5, 6}, + {123, 153, 84, 132, 0}, + {106, 11, 48, 209, 10} + }, + { {191, 254, 168, 18, 10}, + {195, 156, 189, 99, 13}, + {84, 129, 87, 255, 13}, + {188, 107, 211, 156, 3} + }, + { {186, 49, 92, 161, 1}, + {103, 11, 38, 23, 2}, + {136, 83, 168, 197, 13}, + {78, 134, 77, 14, 6} + }, + { {7, 242, 225, 29, 7}, + {253, 180, 216, 69, 4}, + {235, 136, 116, 254, 0}, + {42, 33, 178, 219, 15} + }, + { {234, 79, 43, 164, 13}, + {30, 70, 111, 163, 14}, + {178, 93, 79, 37, 7}, + {124, 95, 102, 39, 8} + }, + { {165, 190, 34, 214, 12}, + {201, 64, 249, 106, 15}, + {54, 180, 71, 218, 5}, + {245, 105, 240, 41, 3} + }, + { {241, 169, 225, 184, 14}, + {49, 186, 243, 194, 14}, + {113, 216, 121, 88, 15}, + {116, 60, 245, 216, 12} + }, + { {202, 168, 155, 183, 7}, + {31, 208, 214, 151, 11}, + {238, 221, 145, 85, 3}, + {222, 150, 176, 191, 8} + }, + { {84, 134, 196, 245, 2}, + {168, 153, 154, 172, 2}, + {74, 242, 54, 18, 10}, + {67, 85, 153, 145, 5} + }, + { {53, 121, 76, 30, 4}, + {233, 47, 228, 64, 9}, + {39, 131, 41, 234, 12}, + {144, 34, 127, 73, 7} + }, + { {110, 216, 72, 137, 8}, + {226, 36, 53, 133, 10}, + {25, 17, 33, 183, 6}, + {90, 26, 194, 68, 7} + }, + { {186, 232, 137, 103, 6}, + {27, 156, 118, 15, 9}, + {110, 105, 17, 117, 13}, + {159, 6, 227, 157, 8} + }, + { {148, 192, 40, 189, 0}, + {136, 44, 150, 6, 6}, + {11, 209, 64, 50, 9}, + {102, 6, 147, 65, 1} + }, + { {115, 76, 162, 183, 6}, + {8, 220, 226, 229, 15}, + {110, 212, 83, 44, 14}, + {250, 116, 115, 177, 0} + }, + { {47, 241, 60, 122, 11}, + {199, 55, 183, 89, 5}, + {213, 227, 200, 255, 4}, + {169, 174, 222, 206, 3} + }, + { {204, 2, 99, 100, 7}, + {190, 80, 74, 138, 4}, + {226, 108, 100, 3, 3}, + {37, 21, 32, 167, 13} + }, + { {222, 144, 69, 41, 2}, + {242, 57, 18, 135, 0}, + {73, 74, 32, 151, 11}, + {14, 20, 137, 196, 15} + }, + { {2, 43, 108, 122, 15}, + {37, 51, 207, 9, 13}, + {245, 227, 109, 68, 0}, + {185, 15, 60, 202, 4} + }, + { {64, 75, 24, 30, 13}, + {12, 38, 205, 144, 9}, + {183, 129, 141, 32, 2}, + {144, 155, 54, 67, 0} + }, + { {44, 49, 42, 198, 5}, + {207, 66, 100, 8, 7}, + {166, 53, 72, 195, 4}, + {225, 2, 100, 47, 3} + }, + { {191, 189, 15, 65, 6}, + {211, 91, 116, 111, 8}, + {104, 47, 11, 223, 13}, + {31, 98, 237, 172, 11} + }, + { {232, 58, 99, 203, 12}, + {115, 96, 105, 142, 15}, + {61, 60, 101, 193, 7}, + {247, 25, 96, 108, 14} + }, + { {231, 89, 156, 35, 2}, + {192, 151, 38, 215, 9}, + {76, 67, 153, 174, 7}, + {158, 182, 78, 144, 3} + }, + { {255, 230, 113, 141, 6}, + {187, 60, 120, 247, 6}, + {107, 24, 230, 127, 15}, + {110, 241, 227, 205, 13} + }, + { {197, 136, 65, 238, 2}, + {184, 48, 18, 202, 11}, + {71, 120, 33, 26, 3}, + {213, 52, 128, 193, 13} + }, + { {171, 180, 217, 16, 2}, + {115, 144, 180, 115, 0}, + {64, 137, 178, 221, 5}, + {12, 226, 208, 156, 14} + }, + { {10, 23, 123, 120, 4}, + {114, 98, 206, 57, 4}, + {33, 237, 238, 133, 0}, + {41, 199, 52, 100, 14} + }, + { {78, 123, 115, 156, 14}, + {251, 118, 201, 145, 14}, + {115, 156, 237, 231, 2}, + {120, 153, 54, 237, 15} + }, + { {34, 227, 251, 26, 1}, + {53, 230, 188, 17, 5}, + {133, 141, 252, 116, 4}, + {168, 131, 214, 122, 12} + }, + { {54, 93, 186, 61, 2}, + {200, 254, 166, 53, 12}, + {75, 197, 219, 166, 12}, + {58, 198, 87, 241, 3} + }, + { {59, 220, 189, 56, 4}, + {82, 173, 246, 113, 12}, + {33, 203, 211, 189, 12}, + {56, 230, 251, 84, 10} + }, + { {200, 199, 188, 19, 5}, + {6, 135, 220, 182, 5}, + {172, 131, 222, 49, 3}, + {166, 211, 190, 22, 0} + }, + { {91, 16, 250, 200, 6}, + {98, 248, 68, 217, 6}, + {97, 53, 240, 141, 10}, + {105, 178, 33, 244, 6} + }, + { {208, 249, 92, 92, 11}, + {109, 63, 149, 154, 8}, + {211, 163, 169, 240, 11}, + {21, 154, 159, 203, 6} + }, + { {153, 6, 142, 77, 7}, + {14, 249, 76, 110, 0}, + {235, 39, 22, 9, 9}, + {7, 99, 41, 247, 0} + }, + { {200, 39, 17, 208, 2}, + {19, 18, 136, 186, 2}, + {64, 184, 142, 65, 3}, + {69, 209, 20, 140, 8} + }, + { {164, 84, 34, 73, 9}, + {196, 100, 33, 46, 4}, + {153, 36, 66, 162, 5}, + {39, 72, 66, 98, 3} + }, + { {169, 103, 185, 198, 3}, + {31, 150, 44, 122, 7}, + {198, 57, 222, 105, 5}, + {229, 227, 70, 159, 8} + }, + { {227, 227, 61, 193, 6}, + {17, 23, 124, 223, 6}, + {104, 59, 204, 124, 7}, + {111, 179, 238, 136, 8} + }, + { {242, 227, 250, 36, 6}, + {41, 222, 126, 147, 4}, + {98, 69, 252, 116, 15}, + {44, 151, 231, 185, 4} + }, + { {171, 214, 110, 194, 10}, + {98, 85, 61, 107, 7}, + {84, 55, 102, 189, 5}, + {237, 107, 202, 164, 6} + }, + { {150, 166, 172, 174, 12}, + {137, 169, 95, 35, 7}, + {55, 83, 86, 86, 9}, + {236, 79, 169, 89, 1} + }, + { {17, 94, 218, 37, 15}, + {108, 220, 79, 116, 8}, + {250, 69, 183, 168, 8}, + {18, 239, 35, 179, 6} + }, + { {224, 72, 217, 79, 9}, + {60, 164, 37, 158, 9}, + {159, 41, 177, 32, 7}, + {151, 154, 66, 83, 12} + }, + { {62, 162, 162, 69, 13}, + {143, 200, 121, 13, 4}, + {186, 36, 84, 87, 12}, + {43, 9, 225, 63, 1} + }, + { {147, 122, 113, 157, 10}, + {121, 60, 137, 87, 14}, + {91, 152, 229, 236, 9}, + {126, 169, 19, 201, 14} + }, + { {109, 101, 53, 32, 4}, + {147, 7, 98, 240, 4}, + {32, 74, 202, 107, 6}, + {32, 244, 110, 12, 9} + }, + { {218, 6, 151, 74, 11}, + {22, 249, 9, 187, 1}, + {213, 46, 150, 5, 11}, + {141, 217, 9, 246, 8} + }, + { {20, 176, 37, 32, 10}, + {209, 25, 19, 0, 4}, + {80, 74, 64, 210, 8}, + {32, 12, 137, 136, 11} + }, + { {122, 150, 135, 109, 5}, + {94, 233, 122, 173, 0}, + {171, 110, 22, 149, 14}, + {11, 85, 233, 119, 10} + }, + { {221, 120, 18, 100, 5}, + {207, 76, 66, 218, 8}, + {162, 100, 129, 235, 11}, + {21, 180, 35, 47, 3} + }, + { {130, 195, 122, 162, 9}, + {36, 70, 31, 19, 7}, + {148, 85, 236, 52, 1}, + {236, 143, 134, 34, 4} + }, + { {151, 25, 250, 15, 4}, + {232, 234, 68, 87, 13}, + {47, 5, 249, 142, 9}, + {190, 162, 37, 113, 7} + }, + { {58, 108, 193, 181, 7}, + {63, 156, 226, 37, 10}, + {234, 216, 51, 101, 12}, + {90, 68, 115, 159, 12} + }, + { {97, 33, 177, 73, 13}, + {21, 162, 97, 220, 4}, + {185, 40, 216, 72, 6}, + {35, 184, 100, 90, 8} + }, + { {208, 123, 6, 29, 14}, + {73, 127, 201, 134, 8}, + {123, 134, 13, 224, 11}, + {22, 25, 63, 233, 2} + }, + { {69, 190, 247, 38, 9}, + {253, 193, 27, 240, 13}, + {150, 78, 247, 218, 2}, + {176, 253, 136, 59, 15} + }, + { {157, 70, 148, 66, 4}, + {130, 141, 72, 122, 1}, + {36, 34, 150, 43, 9}, + {133, 225, 43, 20, 1} + }, + { {77, 251, 186, 241, 4}, + {195, 198, 222, 220, 14}, + {40, 245, 221, 251, 2}, + {115, 183, 182, 60, 3} + }, + { {200, 236, 99, 163, 1}, + {55, 68, 18, 166, 15}, + {140, 92, 99, 113, 3}, + {246, 84, 130, 46, 12} + }, + { {41, 38, 49, 96, 9}, + {23, 0, 43, 120, 4}, + {144, 104, 198, 73, 4}, + {33, 237, 64, 14, 8} + }, + { {34, 234, 181, 80, 10}, + {17, 149, 185, 25, 12}, + {80, 170, 213, 116, 4}, + {57, 137, 218, 152, 8} + }, + { {12, 83, 62, 124, 9}, + {206, 103, 143, 24, 4}, + {147, 231, 204, 163, 0}, + {33, 143, 30, 103, 3} + }, + { {241, 209, 122, 65, 9}, + {100, 78, 53, 222, 4}, + {152, 37, 232, 184, 15}, + {39, 186, 199, 34, 6} + }, + { {190, 41, 45, 21, 12}, + {155, 11, 229, 7, 12}, + {58, 139, 73, 71, 13}, + {62, 10, 125, 13, 9} + }, + { {173, 236, 230, 5, 15}, + {175, 213, 113, 102, 12}, + {250, 6, 115, 123, 5}, + {54, 104, 234, 191, 5} + }, + { {23, 198, 147, 157, 3}, + {156, 252, 152, 117, 2}, + {203, 156, 150, 62, 8}, + {74, 225, 147, 243, 9} + }, + { {76, 195, 212, 88, 9}, + {166, 167, 153, 152, 0}, + {145, 162, 188, 51, 2}, + {1, 153, 158, 86, 5} + }, + { {154, 214, 102, 33, 4}, + {98, 77, 90, 39, 4}, + {40, 70, 102, 181, 9}, + {46, 69, 171, 36, 6} + }, + { {19, 133, 65, 215, 3}, + {60, 26, 144, 109, 3}, + {206, 184, 42, 28, 8}, + {203, 96, 149, 131, 12} + }, + { {86, 15, 151, 244, 5}, + {156, 203, 202, 185, 10}, + {162, 254, 159, 6, 10}, + {89, 213, 61, 51, 9} + }, + { {166, 192, 165, 246, 0}, + {152, 133, 178, 11, 7}, + {6, 250, 80, 54, 5}, + {237, 4, 218, 17, 9} + }, + { {95, 226, 229, 89, 8}, + {179, 173, 153, 205, 4}, + {25, 170, 116, 127, 10}, + {43, 57, 155, 92, 13} + }, + { {67, 190, 136, 118, 5}, + {77, 128, 222, 233, 9}, + {166, 225, 23, 220, 2}, + {153, 119, 176, 27, 2} + }, + { {8, 144, 129, 205, 4}, + {90, 160, 80, 12, 2}, + {43, 56, 16, 145, 0}, + {67, 0, 160, 85, 10} + }, + { {77, 234, 23, 239, 15}, + {159, 117, 91, 220, 11}, + {255, 126, 133, 123, 2}, + {211, 189, 170, 239, 9} + }, + { {124, 58, 173, 18, 0}, + {211, 137, 172, 128, 13}, + {4, 139, 85, 195, 14}, + {176, 19, 89, 28, 11} + }, + { {67, 86, 162, 174, 9}, + {76, 228, 11, 225, 7}, + {151, 84, 86, 172, 2}, + {232, 125, 2, 115, 2} + }, + { {236, 183, 0, 75, 5}, + {199, 34, 120, 174, 1}, + {173, 32, 14, 211, 7}, + {135, 81, 228, 78, 3} + }, + { {247, 97, 178, 178, 7}, + {133, 222, 226, 211, 7}, + {228, 212, 216, 110, 15}, + {236, 180, 119, 186, 1} + }, + { {137, 36, 99, 124, 13}, + {63, 96, 195, 106, 4}, + {179, 236, 98, 73, 1}, + {37, 108, 48, 111, 12} + }, + { {114, 58, 98, 179, 1}, + {101, 72, 170, 133, 15}, + {140, 212, 101, 196, 14}, + {250, 21, 81, 42, 6} + }, + { {41, 180, 125, 69, 1}, + {127, 1, 52, 124, 4}, + {138, 43, 226, 217, 4}, + {35, 226, 200, 15, 14} + }, + { {18, 107, 239, 149, 10}, + {57, 223, 141, 5, 14}, + {90, 159, 125, 100, 8}, + {122, 11, 31, 185, 12} + }, + { {161, 94, 96, 121, 10}, + {96, 52, 171, 110, 12}, + {89, 224, 103, 168, 5}, + {55, 109, 82, 192, 6} + }, + { {126, 76, 172, 58, 13}, + {134, 173, 231, 161, 13}, + {181, 195, 83, 39, 14}, + {184, 94, 123, 86, 1} + }, + { {114, 142, 21, 50, 3}, + {20, 25, 186, 177, 9}, + {196, 202, 135, 20, 14}, + {152, 213, 217, 130, 8} + }, + { {50, 5, 228, 151, 0}, + {40, 139, 160, 37, 7}, + {14, 146, 122, 4, 12}, + {234, 64, 93, 17, 4} + }, + { {20, 99, 87, 142, 15}, + {189, 127, 73, 16, 3}, + {247, 30, 172, 98, 8}, + {192, 137, 47, 235, 13} + }, + { {70, 114, 172, 144, 8}, + {193, 133, 141, 129, 6}, + {16, 147, 84, 230, 2}, + {104, 27, 26, 24, 3} + }, + { {218, 88, 222, 192, 8}, + {98, 205, 5, 155, 10}, + {16, 55, 177, 165, 11}, + {93, 154, 11, 52, 6} + }, + { {201, 31, 228, 54, 4}, + {106, 131, 202, 226, 13}, + {38, 194, 127, 137, 3}, + {180, 117, 60, 21, 6} + }, + { {161, 197, 254, 154, 12}, + {32, 231, 245, 114, 7}, + {53, 151, 250, 56, 5}, + {228, 234, 254, 112, 4} + }, + { {247, 70, 210, 71, 7}, + {172, 220, 104, 255, 1}, + {238, 36, 182, 46, 15}, + {143, 241, 99, 179, 5} + }, + { {56, 73, 248, 205, 2}, + {42, 190, 36, 28, 14}, + {75, 49, 249, 33, 12}, + {115, 130, 71, 213, 4} + }, + { {186, 114, 36, 10, 6}, + {67, 61, 104, 3, 5}, + {101, 2, 68, 229, 13}, + {172, 1, 107, 204, 2} + }, + { {100, 162, 99, 127, 3}, + {189, 112, 186, 140, 5}, + {207, 236, 100, 82, 6}, + {163, 21, 208, 235, 13} + }, + { {132, 80, 221, 43, 3}, + {244, 181, 6, 22, 1}, + {205, 75, 176, 162, 1}, + {134, 134, 10, 210, 15} + }, + { {233, 134, 163, 18, 6}, + {18, 208, 248, 226, 5}, + {100, 140, 86, 25, 7}, + {164, 113, 240, 180, 8} + }, + { {31, 106, 10, 155, 13}, + {135, 108, 205, 69, 11}, + {189, 149, 5, 111, 8}, + {218, 43, 51, 110, 1} + }, + { {193, 131, 19, 193, 9}, + {20, 66, 25, 222, 2}, + {152, 60, 140, 24, 3}, + {71, 185, 132, 34, 8} + }, + { {68, 244, 102, 59, 11}, + {229, 117, 147, 164, 5}, + {221, 198, 98, 242, 2}, + {162, 92, 154, 234, 7} + }, + { {1, 157, 216, 198, 10}, + {104, 146, 21, 120, 11}, + {86, 49, 187, 152, 0}, + {209, 234, 132, 145, 6} + }, + { {207, 106, 252, 4, 8}, + {171, 133, 13, 211, 12}, + {18, 3, 245, 111, 3}, + {60, 187, 10, 29, 5} + }, + { {146, 174, 195, 5, 6}, + {57, 216, 88, 39, 8}, + {106, 12, 55, 84, 9}, + {30, 65, 161, 185, 12} + }, + { {37, 21, 93, 154, 5}, + {244, 35, 228, 112, 3}, + {165, 155, 170, 138, 4}, + {192, 226, 124, 66, 15} + }, + { {22, 10, 92, 69, 6}, + {168, 25, 76, 29, 8}, + {106, 35, 165, 6, 8}, + {27, 131, 41, 129, 5} + }, + { {42, 195, 144, 107, 5}, + {6, 166, 122, 29, 1}, + {173, 96, 156, 53, 4}, + {139, 133, 230, 86, 0} + }, + { {46, 122, 182, 246, 15}, + {207, 213, 235, 25, 15}, + {246, 246, 213, 231, 4}, + {249, 141, 122, 191, 3} + }, + { {158, 250, 147, 214, 12}, + {219, 204, 217, 27, 11}, + {54, 188, 149, 247, 9}, + {221, 137, 179, 61, 11} + }, + { {70, 52, 147, 36, 2}, + {217, 208, 2, 177, 0}, + {66, 76, 146, 198, 2}, + {8, 212, 0, 185, 11} + }, + { {10, 253, 84, 150, 12}, + {107, 7, 209, 49, 11}, + {54, 146, 171, 245, 0}, + {216, 200, 190, 13, 6} + }, + { {176, 98, 61, 37, 0}, + {25, 13, 46, 22, 4}, + {10, 75, 196, 96, 13}, + {38, 135, 75, 9, 8} + }, + { {145, 210, 194, 45, 8}, + {104, 236, 27, 70, 0}, + {27, 68, 52, 184, 9}, + {6, 45, 131, 113, 6} + }, + { {180, 12, 140, 215, 2}, + {136, 153, 164, 46, 11}, + {78, 179, 19, 2, 13}, + {215, 66, 89, 145, 1} + }, + { {159, 137, 21, 197, 9}, + {158, 11, 17, 95, 10}, + {154, 58, 137, 31, 9}, + {95, 168, 141, 7, 9} + }, + { {231, 171, 31, 92, 9}, + {157, 99, 189, 219, 8}, + {147, 175, 141, 94, 7}, + {29, 187, 220, 107, 9} + }, + { {64, 95, 208, 111, 7}, + {108, 182, 74, 188, 9}, + {239, 96, 191, 160, 2}, + {147, 213, 38, 211, 6} + }, + { {107, 30, 197, 224, 6}, + {114, 145, 106, 233, 10}, + {96, 122, 55, 141, 6}, + {89, 117, 104, 148, 14} + }, + { {172, 27, 229, 165, 10}, + {250, 147, 43, 6, 14}, + {90, 90, 125, 131, 5}, + {118, 13, 76, 149, 15} + }, + { {230, 47, 3, 108, 10}, + {153, 114, 43, 171, 8}, + {83, 108, 15, 70, 7}, + {29, 93, 68, 233, 9} + }, + { {127, 229, 102, 248, 8}, + {163, 111, 179, 233, 6}, + {17, 246, 106, 127, 14}, + {105, 124, 223, 108, 5} + }, + { {163, 194, 62, 50, 14}, + {0, 85, 255, 83, 5}, + {116, 199, 196, 60, 5}, + {172, 175, 250, 160, 0} + }, + { {148, 62, 117, 118, 6}, + {249, 25, 202, 58, 13}, + {102, 234, 231, 194, 9}, + {181, 197, 57, 137, 15} + }, + { {52, 186, 30, 90, 15}, + {197, 121, 253, 24, 9}, + {245, 167, 133, 210, 12}, + {145, 139, 249, 234, 3} + }, + { {3, 165, 18, 156, 11}, + {13, 114, 145, 113, 2}, + {211, 148, 138, 92, 0}, + {72, 232, 148, 235, 0} + }, + { {37, 82, 144, 131, 7}, + {196, 148, 104, 84, 3}, + {236, 16, 148, 170, 4}, + {194, 161, 98, 146, 3} + }, + { {152, 56, 107, 83, 15}, + {119, 88, 197, 14, 13}, + {252, 173, 97, 193, 9}, + {183, 10, 49, 174, 14} + }, + { {81, 8, 229, 57, 2}, + {48, 185, 130, 196, 12}, + {73, 202, 113, 8, 10}, + {50, 52, 25, 208, 12} + }, + { {109, 47, 236, 46, 14}, + {171, 179, 111, 224, 13}, + {119, 67, 127, 75, 6}, + {176, 127, 108, 221, 5} + }, + { {82, 188, 16, 202, 10}, + {65, 56, 17, 185, 11}, + {85, 48, 131, 212, 10}, + {217, 216, 129, 200, 2} + }, + { {179, 40, 54, 67, 10}, + {1, 89, 33, 95, 13}, + {92, 38, 193, 76, 13}, + {191, 168, 73, 168, 0} + }, + { {119, 53, 50, 215, 6}, + {201, 90, 224, 253, 7}, + {110, 180, 202, 206, 14}, + {235, 240, 117, 169, 3} + }, + { {60, 107, 123, 81, 7}, + {183, 94, 236, 28, 12}, + {232, 173, 237, 99, 12}, + {51, 131, 119, 174, 13} + }, + { {58, 210, 85, 76, 8}, + {122, 45, 57, 25, 0}, + {19, 42, 164, 181, 12}, + {9, 137, 203, 69, 14} + }, + { {121, 133, 192, 110, 10}, + {42, 186, 51, 232, 1}, + {87, 96, 58, 25, 14}, + {129, 124, 197, 213, 4} + }, + { {197, 187, 154, 199, 7}, + {205, 210, 92, 222, 11}, + {238, 53, 157, 218, 3}, + {215, 179, 164, 187, 3} + }, + { {12, 197, 122, 240, 11}, + {166, 86, 151, 56, 6}, + {208, 245, 234, 51, 0}, + {97, 206, 150, 166, 5} + }, + { {248, 208, 240, 214, 2}, + {106, 156, 176, 154, 7}, + {70, 176, 240, 177, 15}, + {229, 144, 211, 149, 6} + }, + { {129, 63, 211, 226, 11}, + {117, 210, 11, 122, 11}, + {212, 124, 191, 200, 1}, + {213, 237, 4, 186, 14} + }, + { {13, 236, 216, 183, 12}, + {171, 132, 215, 116, 11}, + {62, 209, 179, 123, 0}, + {210, 238, 178, 29, 5} + }, + { {27, 74, 227, 19, 2}, + {50, 220, 136, 69, 13}, + {76, 140, 117, 45, 8}, + {186, 33, 19, 180, 12} + }, + { {210, 32, 64, 67, 5}, + {37, 8, 64, 143, 1}, + {172, 32, 32, 68, 11}, + {143, 16, 33, 10, 4} + }, + { {20, 134, 143, 224, 4}, + {144, 201, 94, 40, 2}, + {32, 127, 22, 18, 8}, + {65, 71, 169, 48, 9} + }, + { {129, 156, 58, 252, 10}, + {72, 112, 151, 122, 14}, + {83, 245, 195, 152, 1}, + {117, 238, 144, 225, 2} + }, + { {69, 204, 88, 112, 14}, + {160, 20, 215, 248, 8}, + {112, 225, 163, 58, 2}, + {17, 254, 178, 128, 5} + }, + { {85, 209, 44, 27, 7}, + {196, 63, 212, 196, 5}, + {237, 131, 72, 186, 10}, + {162, 50, 191, 194, 3} + }, + { {124, 82, 159, 25, 14}, + {210, 253, 237, 148, 0}, + {121, 143, 148, 163, 14}, + {2, 155, 123, 244, 11} + }, + { {112, 145, 54, 145, 1}, + {68, 75, 176, 148, 6}, + {136, 150, 200, 144, 14}, + {98, 144, 221, 34, 2} + }, + { {252, 90, 66, 132, 10}, + {234, 92, 41, 130, 10}, + {82, 20, 37, 163, 15}, + {84, 25, 67, 165, 7} + }, + { {87, 151, 148, 193, 3}, + {196, 155, 24, 253, 2}, + {200, 50, 158, 158, 10}, + {75, 241, 141, 146, 3} + }, + { {165, 209, 196, 248, 2}, + {224, 183, 178, 74, 2}, + {65, 242, 56, 186, 5}, + {69, 36, 222, 208, 7} + }, + { {129, 60, 188, 144, 8}, + {65, 129, 133, 114, 14}, + {16, 147, 211, 200, 1}, + {116, 234, 24, 24, 2} + }, + { {9, 113, 70, 213, 3}, + {111, 87, 128, 76, 2}, + {202, 182, 40, 233, 0}, + {67, 32, 30, 175, 6} + }, + { {106, 207, 210, 216, 5}, + {38, 230, 248, 185, 10}, + {161, 180, 191, 53, 6}, + {89, 209, 246, 118, 4} + }, + { {115, 244, 119, 116, 9}, + {125, 77, 179, 249, 4}, + {146, 238, 226, 252, 14}, + {41, 252, 219, 43, 14} + }, + { {245, 157, 172, 185, 10}, + {192, 187, 183, 230, 14}, + {89, 211, 91, 154, 15}, + {118, 126, 221, 208, 3} + }, + { {202, 249, 118, 232, 4}, + {99, 103, 82, 155, 14}, + {33, 118, 233, 245, 3}, + {125, 148, 174, 108, 6} + }, + { {145, 223, 213, 147, 14}, + {112, 159, 217, 118, 11}, + {124, 154, 191, 184, 9}, + {214, 233, 191, 144, 14} + }, + { {151, 224, 139, 96, 13}, + {149, 204, 87, 75, 0}, + {176, 109, 16, 126, 9}, + {13, 46, 163, 58, 9} + }, + { {171, 24, 195, 41, 7}, + {118, 240, 98, 71, 8}, + {233, 76, 49, 141, 5}, + {30, 36, 96, 246, 14} + }, + { {12, 117, 233, 207, 4}, + {251, 166, 68, 44, 7}, + {47, 57, 122, 227, 0}, + {227, 66, 38, 93, 15} + }, + { {18, 88, 90, 126, 11}, + {108, 124, 135, 25, 9}, + {215, 229, 161, 164, 8}, + {153, 142, 19, 227, 6} + }, + { {226, 61, 82, 49, 9}, + {101, 66, 163, 183, 8}, + {152, 196, 171, 196, 7}, + {30, 220, 84, 42, 6} + }, + { {148, 62, 206, 46, 10}, + {233, 249, 15, 34, 9}, + {87, 71, 55, 194, 9}, + {148, 79, 9, 249, 7} + }, + { {77, 128, 42, 254, 11}, + {142, 112, 151, 200, 7}, + {215, 245, 64, 27, 2}, + {225, 62, 144, 231, 1} + }, + { {99, 222, 29, 74, 1}, + {84, 37, 60, 249, 9}, + {133, 43, 135, 188, 6}, + {153, 243, 202, 66, 10} + }, + { {234, 149, 240, 24, 8}, + {98, 162, 177, 179, 4}, + {17, 128, 250, 149, 7}, + {44, 216, 212, 84, 6} + }, + { {20, 204, 99, 128, 10}, + {176, 92, 17, 32, 14}, + {80, 28, 99, 50, 8}, + {112, 72, 131, 160, 13} + }, + { {200, 176, 49, 75, 15}, + {87, 48, 81, 158, 5}, + {253, 40, 192, 209, 3}, + {167, 152, 160, 206, 10} + }, + { {158, 5, 82, 110, 15}, + {174, 122, 67, 59, 1}, + {247, 100, 170, 7, 9}, + {141, 204, 37, 231, 5} + }, + { {90, 49, 246, 81, 3}, + {103, 219, 128, 157, 4}, + {200, 166, 248, 197, 10}, + {43, 144, 29, 190, 6} + }, + { {2, 64, 141, 174, 9}, + {28, 165, 7, 1, 3}, + {151, 91, 16, 36, 0}, + {200, 14, 10, 83, 8} + }, + { {3, 39, 204, 189, 6}, + {41, 179, 206, 101, 2}, + {107, 211, 62, 76, 0}, + {74, 103, 60, 217, 4} + }, + { {57, 199, 200, 136, 5}, + {38, 174, 124, 96, 2}, + {161, 17, 62, 57, 12}, + {64, 99, 231, 86, 4} + }, + { {29, 241, 95, 61, 5}, + {255, 111, 214, 84, 0}, + {171, 207, 168, 251, 8}, + {2, 166, 191, 111, 15} + }, + { {53, 248, 87, 146, 8}, + {241, 77, 177, 80, 11}, + {20, 158, 161, 250, 12}, + {208, 168, 219, 40, 15} + }, + { {246, 170, 194, 38, 15}, + {173, 216, 123, 131, 9}, + {246, 68, 53, 86, 15}, + {156, 29, 225, 187, 5} + }, + { {231, 224, 91, 154, 9}, + {181, 100, 181, 211, 3}, + {149, 157, 160, 126, 7}, + {204, 186, 210, 106, 13} + }, + { {35, 84, 88, 78, 2}, + {104, 52, 36, 121, 1}, + {71, 33, 162, 172, 4}, + {137, 226, 66, 193, 6} + }, + { {168, 240, 123, 208, 12}, + {115, 68, 245, 26, 6}, + {48, 189, 224, 241, 5}, + {101, 138, 242, 44, 14} + }, + { {31, 123, 242, 62, 3}, + {239, 254, 138, 81, 13}, + {199, 196, 253, 239, 8}, + {184, 165, 23, 255, 7} + }, + { {166, 34, 215, 254, 13}, + {189, 225, 235, 27, 3}, + {183, 254, 180, 70, 5}, + {205, 141, 120, 123, 13} + }, + { {60, 224, 174, 102, 10}, + {139, 221, 55, 8, 5}, + {86, 103, 80, 115, 12}, + {161, 14, 203, 189, 1} + }, + { {157, 234, 252, 246, 9}, + {175, 141, 159, 90, 15}, + {150, 243, 245, 123, 9}, + {245, 175, 155, 31, 5} + }, + { {118, 28, 15, 110, 1}, + {220, 105, 38, 169, 9}, + {135, 111, 3, 134, 14}, + {153, 86, 73, 99, 11} + }, + { {23, 229, 15, 201, 12}, + {145, 111, 85, 109, 2}, + {57, 63, 10, 126, 8}, + {75, 106, 175, 104, 9} + }, + { {107, 215, 2, 212, 12}, + {74, 70, 249, 233, 2}, + {50, 180, 14, 189, 6}, + {73, 121, 246, 37, 2} + }, + { {86, 44, 136, 80, 8}, + {129, 136, 133, 169, 8}, + {16, 161, 19, 70, 10}, + {25, 90, 17, 24, 1} + }, + { {117, 18, 248, 29, 15}, + {236, 184, 237, 212, 4}, + {251, 129, 244, 138, 14}, + {34, 187, 113, 211, 7} + }, + { {126, 225, 73, 21, 0}, + {187, 14, 180, 133, 0}, + {10, 137, 40, 119, 14}, + {10, 18, 215, 13, 13} + }, + { {16, 118, 198, 246, 1}, + {109, 205, 138, 40, 3}, + {134, 246, 54, 224, 8}, + {193, 69, 27, 59, 6} + }, + { {212, 235, 35, 223, 2}, + {153, 126, 152, 142, 15}, + {79, 188, 77, 114, 11}, + {247, 17, 151, 233, 9} + }, + { {40, 207, 171, 134, 10}, + {26, 214, 61, 32, 15}, + {86, 29, 95, 49, 4}, + {240, 75, 198, 181, 8} + }, + { {172, 20, 113, 133, 3}, + {254, 16, 32, 54, 6}, + {202, 24, 226, 131, 5}, + {102, 192, 64, 135, 15} + }, + { {175, 152, 175, 103, 14}, + {218, 209, 119, 79, 13}, + {126, 111, 81, 159, 5}, + {191, 46, 232, 181, 11} + }, + { {193, 158, 114, 157, 1}, + {108, 96, 152, 246, 14}, + {139, 148, 231, 152, 3}, + {118, 241, 144, 99, 6} + }, + { {207, 95, 181, 152, 6}, + {210, 183, 200, 243, 14}, + {97, 154, 223, 175, 3}, + {124, 241, 62, 212, 11} + }, + { {213, 221, 187, 147, 2}, + {208, 222, 148, 246, 15}, + {76, 157, 219, 186, 11}, + {246, 242, 151, 176, 11} + }, + { {79, 134, 37, 109, 3}, + {158, 49, 26, 237, 4}, + {203, 106, 70, 31, 2}, + {43, 117, 136, 199, 9} + }, + { {106, 36, 71, 111, 0}, + {59, 97, 34, 173, 1}, + {15, 110, 34, 69, 6}, + {139, 84, 72, 109, 12} + }, + { {128, 45, 147, 26, 1}, + {21, 226, 128, 50, 9}, + {133, 140, 155, 64, 1}, + {148, 192, 20, 122, 8} + }, + { {29, 205, 131, 180, 9}, + {158, 206, 147, 96, 10}, + {146, 220, 27, 59, 8}, + {80, 108, 151, 55, 9} + }, + { {218, 134, 193, 229, 12}, + {58, 136, 91, 175, 2}, + {58, 120, 54, 21, 11}, + {79, 93, 161, 21, 12} + }, + { {230, 246, 109, 46, 3}, + {253, 53, 62, 163, 5}, + {199, 75, 102, 246, 7}, + {172, 87, 202, 203, 15} + }, + { {61, 137, 16, 149, 7}, + {142, 26, 240, 84, 10}, + {234, 144, 137, 27, 12}, + {82, 160, 245, 135, 1} + }, + { {210, 121, 65, 88, 12}, + {113, 46, 193, 139, 8}, + {49, 168, 41, 228, 11}, + {29, 24, 55, 72, 14} + }, + { {160, 175, 28, 189, 14}, + {9, 51, 255, 54, 10}, + {123, 211, 143, 80, 5}, + {86, 207, 252, 201, 0} + }, + { {1, 87, 110, 34, 13}, + {100, 71, 79, 96, 5}, + {180, 71, 110, 168, 0}, + {160, 111, 46, 34, 6} + }, + { {87, 248, 236, 245, 8}, + {233, 141, 151, 205, 14}, + {26, 243, 113, 254, 10}, + {123, 62, 155, 25, 7} + }, + { {106, 255, 41, 18, 1}, + {87, 6, 188, 161, 13}, + {132, 137, 79, 245, 6}, + {184, 83, 214, 14, 10} + }, + { {251, 33, 174, 202, 0}, + {3, 235, 36, 203, 7}, + {5, 55, 88, 77, 15}, + {237, 50, 77, 124, 0} + }, + { {11, 139, 189, 114, 11}, + {22, 147, 159, 89, 13}, + {212, 235, 221, 29, 0}, + {185, 175, 156, 150, 8} + }, + { {212, 152, 51, 194, 13}, + {212, 72, 81, 154, 15}, + {180, 60, 193, 146, 11}, + {245, 152, 161, 34, 11} + }, + { {225, 75, 0, 230, 14}, + {8, 22, 107, 202, 11}, + {118, 112, 13, 40, 7}, + {213, 61, 102, 129, 0} + }, + { {26, 95, 155, 85, 5}, + {94, 206, 204, 61, 8}, + {170, 173, 159, 165, 8}, + {27, 195, 55, 55, 10} + }, + { {240, 142, 136, 165, 15}, + {12, 152, 127, 166, 10}, + {250, 81, 23, 16, 15}, + {86, 95, 225, 147, 0} + }, + { {167, 153, 98, 167, 6}, + {232, 82, 114, 71, 15}, + {110, 84, 105, 158, 5}, + {254, 36, 228, 161, 7} + }, + { {117, 137, 204, 235, 6}, + {160, 187, 118, 204, 11}, + {109, 115, 57, 26, 14}, + {211, 54, 237, 208, 5} + }, + { {146, 223, 143, 67, 12}, + {80, 207, 93, 47, 9}, + {60, 47, 31, 180, 9}, + {159, 75, 175, 48, 10} + }, + { {174, 86, 155, 223, 2}, + {218, 244, 172, 63, 3}, + {79, 189, 150, 167, 5}, + {207, 195, 82, 245, 11} + }, + { {210, 12, 103, 74, 4}, + {48, 105, 64, 171, 13}, + {37, 46, 99, 4, 11}, + {189, 80, 41, 96, 12} + }, + { {40, 70, 65, 78, 7}, + {62, 52, 104, 40, 1}, + {231, 40, 38, 33, 4}, + {129, 65, 98, 199, 12} + }, + { {108, 201, 54, 127, 1}, + {142, 103, 178, 156, 13}, + {143, 230, 201, 51, 6}, + {179, 148, 222, 103, 1} + }, + { {194, 238, 156, 100, 12}, + {9, 133, 95, 187, 8}, + {50, 99, 151, 116, 3}, + {29, 223, 170, 25, 0} + }, + { {36, 163, 220, 7, 0}, + {169, 131, 60, 20, 1}, + {14, 3, 188, 82, 4}, + {130, 131, 204, 25, 5} + }, + { {41, 169, 53, 183, 11}, + {31, 19, 179, 84, 15}, + {222, 218, 201, 89, 4}, + {242, 172, 220, 143, 8} + }, + { {105, 9, 143, 242, 0}, + {18, 195, 166, 200, 11}, + {4, 255, 25, 9, 6}, + {209, 54, 92, 52, 8} + }, + { {221, 179, 45, 144, 2}, + {211, 27, 156, 194, 6}, + {64, 155, 76, 219, 11}, + {100, 51, 157, 140, 11} + }, + { {17, 24, 73, 168, 13}, + {116, 40, 71, 64, 10}, + {177, 89, 33, 136, 8}, + {80, 46, 33, 66, 14} + }, + { {182, 179, 217, 164, 5}, + {253, 138, 126, 19, 2}, + {162, 89, 188, 214, 13}, + {76, 135, 229, 27, 15} + }, + { {160, 207, 230, 60, 2}, + {40, 247, 186, 34, 12}, + {67, 198, 127, 48, 5}, + {52, 69, 222, 241, 4} + }, + { {55, 59, 166, 59, 8}, + {193, 235, 171, 69, 13}, + {29, 198, 93, 206, 12}, + {186, 45, 93, 120, 3} + }, + { {161, 242, 208, 206, 8}, + {105, 164, 57, 90, 3}, + {23, 48, 180, 248, 5}, + {197, 169, 194, 89, 6} + }, + { {86, 143, 18, 81, 15}, + {132, 90, 217, 189, 8}, + {248, 164, 143, 22, 10}, + {27, 217, 181, 162, 1} + }, + { {194, 231, 12, 94, 10}, + {9, 55, 157, 171, 1}, + {87, 163, 14, 116, 3}, + {141, 91, 158, 201, 0} + }, + { {156, 25, 191, 90, 0}, + {210, 235, 132, 26, 13}, + {5, 175, 217, 131, 9}, + {181, 130, 29, 116, 11} + }, + { {117, 207, 171, 65, 6}, + {144, 222, 124, 236, 12}, + {104, 45, 95, 58, 14}, + {51, 115, 231, 176, 9} + }, + { {126, 204, 41, 109, 0}, + {154, 44, 54, 173, 12}, + {11, 105, 67, 55, 14}, + {59, 86, 195, 69, 9} + }, + { {243, 91, 227, 26, 14}, + {112, 254, 233, 195, 13}, + {117, 140, 125, 172, 15}, + {188, 57, 119, 240, 14} + }, + { {190, 117, 133, 241, 8}, + {211, 143, 163, 47, 2}, + {24, 250, 26, 231, 13}, + {79, 76, 95, 28, 11} + }, + { {33, 154, 217, 93, 10}, + {120, 176, 189, 92, 8}, + {91, 169, 181, 152, 4}, + {19, 171, 208, 209, 14} + }, + { {187, 162, 184, 116, 12}, + {11, 136, 255, 91, 4}, + {50, 225, 212, 93, 13}, + {45, 175, 241, 29, 0} + }, + { {24, 170, 15, 78, 6}, + {27, 121, 92, 8, 9}, + {103, 47, 5, 81, 8}, + {145, 3, 169, 237, 8} + }, + { {141, 163, 255, 65, 12}, + {179, 195, 93, 94, 4}, + {56, 47, 252, 91, 1}, + {39, 171, 172, 60, 13} + }, + { {204, 241, 13, 15, 1}, + {223, 39, 20, 134, 1}, + {143, 11, 8, 243, 3}, + {134, 18, 142, 79, 11} + }, + { {15, 249, 178, 82, 13}, + {199, 198, 209, 89, 13}, + {180, 164, 217, 255, 0}, + {185, 168, 182, 62, 3} + }, + { {1, 159, 5, 220, 10}, + {88, 51, 153, 104, 10}, + {83, 186, 15, 152, 0}, + {81, 105, 156, 193, 10} + }, + { {223, 138, 224, 53, 4}, + {170, 136, 218, 199, 12}, + {42, 192, 117, 31, 11}, + {62, 53, 177, 21, 5} + }, + { {206, 123, 160, 13, 5}, + {207, 166, 72, 135, 12}, + {171, 0, 93, 231, 3}, + {62, 17, 38, 95, 3} + }, + { {217, 125, 237, 88, 6}, + {115, 191, 196, 234, 12}, + {97, 171, 123, 233, 11}, + {53, 114, 63, 220, 14} + }, + { {181, 147, 30, 12, 6}, + {200, 123, 124, 82, 0}, + {99, 7, 140, 154, 13}, + {4, 163, 237, 225, 3} + }, + { {18, 139, 154, 110, 4}, + {8, 234, 94, 25, 9}, + {39, 101, 157, 20, 8}, + {153, 135, 165, 113, 0} + }, + { {242, 22, 60, 166, 3}, + {76, 25, 46, 179, 7}, + {198, 83, 198, 132, 15}, + {236, 215, 73, 131, 2} + }, + { {232, 10, 7, 115, 7}, + {22, 81, 234, 142, 9}, + {236, 238, 5, 1, 7}, + {151, 21, 120, 166, 8} + }, + { {122, 115, 30, 194, 4}, + {67, 79, 108, 153, 3}, + {36, 55, 140, 229, 14}, + {201, 147, 111, 44, 2} + }, + { {53, 218, 126, 240, 11}, + {228, 93, 191, 88, 14}, + {208, 247, 229, 186, 12}, + {113, 175, 219, 162, 7} + }, + { {153, 130, 253, 45, 1}, + {62, 169, 30, 86, 4}, + {139, 75, 244, 25, 9}, + {38, 167, 137, 87, 12} + }, + { {223, 169, 37, 94, 13}, + {159, 43, 209, 203, 13}, + {183, 170, 73, 95, 11}, + {189, 56, 189, 79, 9} + }, + { {165, 30, 214, 188, 15}, + {236, 241, 235, 114, 10}, + {243, 214, 183, 138, 5}, + {84, 237, 120, 243, 7} + }, + { {36, 8, 213, 209, 9}, + {180, 129, 161, 28, 10}, + {152, 186, 177, 2, 4}, + {83, 136, 88, 18, 13} + }, + { {181, 248, 233, 45, 6}, + {249, 188, 118, 70, 12}, + {107, 73, 113, 250, 13}, + {54, 38, 227, 217, 15} + }, + { {145, 54, 188, 78, 0}, + {73, 169, 12, 122, 5}, + {7, 35, 214, 200, 9}, + {165, 227, 9, 89, 2} + }, + { {174, 88, 224, 232, 5}, + {230, 164, 98, 11, 14}, + {161, 112, 113, 167, 5}, + {125, 4, 98, 86, 7} + }, + { {47, 128, 197, 170, 11}, + {182, 177, 51, 65, 3}, + {213, 90, 48, 31, 4}, + {200, 44, 200, 214, 13} + }, + { {218, 73, 123, 17, 9}, + {54, 78, 133, 151, 12}, + {152, 141, 233, 37, 11}, + {62, 154, 23, 38, 12} + }, + { {162, 246, 228, 83, 14}, + {97, 149, 249, 47, 5}, + {124, 162, 118, 244, 5}, + {175, 73, 250, 152, 6} + }, + { {33, 39, 109, 132, 12}, + {57, 3, 109, 96, 6}, + {50, 27, 110, 72, 4}, + {96, 107, 108, 9, 12} + }, + { {107, 121, 40, 104, 12}, + {67, 38, 103, 201, 12}, + {49, 97, 73, 237, 6}, + {57, 62, 102, 76, 2} + }, + { {157, 218, 93, 177, 13}, + {246, 13, 223, 86, 10}, + {184, 219, 165, 187, 9}, + {86, 175, 187, 6, 15} + }, + { {47, 207, 178, 181, 5}, + {142, 198, 250, 117, 14}, + {170, 212, 223, 63, 4}, + {122, 229, 246, 55, 1} + }, + { {175, 32, 71, 30, 8}, + {187, 97, 161, 67, 1}, + {23, 142, 32, 79, 5}, + {140, 40, 88, 109, 13} + }, + { {234, 199, 75, 58, 9}, + {54, 102, 191, 163, 1}, + {149, 205, 46, 53, 7}, + {140, 95, 214, 102, 12} + }, + { {204, 229, 29, 252, 14}, + {155, 55, 215, 186, 2}, + {115, 251, 138, 115, 3}, + {69, 222, 190, 205, 9} + }, + { {97, 48, 133, 50, 1}, + {85, 129, 162, 192, 1}, + {132, 202, 16, 200, 6}, + {128, 52, 88, 26, 10} + }, + { {180, 146, 25, 41, 1}, + {212, 40, 62, 22, 0}, + {137, 73, 132, 146, 13}, + {6, 135, 193, 66, 11} + }, + { {193, 207, 189, 157, 10}, + {24, 183, 157, 246, 14}, + {91, 155, 223, 56, 3}, + {118, 251, 158, 209, 8} + }, + { {8, 221, 26, 54, 6}, + {74, 86, 214, 48, 9}, + {102, 197, 139, 177, 0}, + {144, 198, 182, 165, 2} + }, + { {120, 67, 92, 168, 15}, + {38, 63, 111, 144, 2}, + {241, 83, 172, 33, 14}, + {64, 159, 111, 198, 4} + }, + { {32, 220, 186, 83, 14}, + {64, 212, 245, 60, 13}, + {124, 165, 211, 176, 4}, + {179, 202, 242, 176, 2} + }, + { {227, 132, 165, 236, 6}, + {24, 177, 114, 235, 6}, + {99, 122, 82, 28, 7}, + {109, 116, 232, 209, 8} + }, + { {131, 145, 171, 122, 13}, + {84, 226, 215, 75, 5}, + {181, 237, 88, 156, 1}, + {173, 46, 180, 114, 10} + }, + { {114, 38, 60, 80, 11}, + {5, 25, 173, 185, 4}, + {208, 163, 198, 68, 14}, + {41, 219, 89, 138, 0} + }, + { {242, 174, 11, 171, 12}, + {17, 104, 127, 167, 11}, + {61, 93, 7, 84, 15}, + {222, 95, 225, 104, 8} + }, + { {46, 136, 9, 214, 3}, + {158, 16, 180, 9, 11}, + {198, 185, 1, 23, 4}, + {217, 2, 208, 135, 9} + }, + { {183, 203, 246, 151, 12}, + {168, 207, 249, 87, 15}, + {62, 150, 253, 62, 13}, + {254, 169, 255, 49, 5} + }, + { {49, 251, 244, 57, 14}, + {97, 191, 251, 84, 12}, + {121, 194, 253, 248, 12}, + {50, 173, 255, 216, 6} + }, + { {142, 199, 82, 154, 6}, + {162, 118, 216, 51, 3}, + {101, 148, 174, 55, 1}, + {204, 193, 182, 228, 5} + }, + { {94, 235, 242, 10, 12}, + {163, 238, 89, 145, 13}, + {53, 4, 253, 119, 10}, + {184, 153, 167, 124, 5} + }, + { {227, 59, 129, 212, 12}, + {89, 130, 233, 203, 10}, + {50, 184, 29, 204, 7}, + {93, 57, 116, 25, 10} + }, + { {172, 124, 242, 222, 10}, + {235, 244, 161, 58, 15}, + {87, 180, 243, 227, 5}, + {245, 200, 82, 253, 7} + }, + { {78, 1, 1, 147, 2}, + {146, 18, 128, 133, 3}, + {76, 152, 8, 7, 2}, + {202, 16, 20, 132, 9} + }, + { {201, 144, 131, 96, 5}, + {86, 192, 82, 202, 0}, + {160, 108, 16, 153, 3}, + {5, 52, 160, 54, 10} + }, + { {245, 102, 105, 160, 13}, + {181, 12, 111, 226, 6}, + {176, 89, 102, 106, 15}, + {100, 127, 99, 10, 13} + }, + { {44, 3, 150, 212, 14}, + {138, 211, 233, 24, 2}, + {114, 182, 156, 3, 4}, + {65, 137, 124, 181, 1} + }, + { {118, 193, 117, 204, 7}, + {188, 63, 112, 153, 6}, + {227, 58, 232, 54, 14}, + {105, 144, 239, 195, 13} + }, + { {176, 251, 83, 107, 5}, + {117, 110, 122, 30, 9}, + {173, 108, 173, 240, 13}, + {151, 133, 231, 106, 14} + }, + { {135, 53, 206, 169, 8}, + {225, 227, 7, 103, 2}, + {25, 87, 58, 206, 1}, + {78, 110, 12, 120, 7} + }, + { {231, 139, 244, 106, 11}, + {164, 179, 59, 219, 13}, + {213, 98, 253, 30, 7}, + {189, 189, 204, 210, 5} + }, + { {185, 178, 1, 226, 1}, + {87, 8, 58, 74, 3}, + {132, 120, 4, 217, 13}, + {197, 37, 193, 14, 10} + }, + { {44, 230, 244, 199, 9}, + {175, 133, 57, 60, 7}, + {158, 50, 246, 115, 4}, + {227, 201, 202, 31, 5} + }, + { {240, 26, 58, 172, 4}, + {72, 104, 110, 146, 14}, + {35, 85, 197, 128, 15}, + {116, 151, 97, 97, 2} + }, + { {107, 29, 238, 143, 4}, + {106, 227, 100, 229, 15}, + {47, 23, 123, 141, 6}, + {250, 114, 108, 117, 6} + }, + { {10, 99, 89, 22, 11}, + {63, 22, 141, 17, 1}, + {214, 137, 172, 101, 0}, + {136, 139, 22, 143, 12} + }, + { {61, 80, 237, 142, 0}, + {250, 173, 36, 64, 7}, + {7, 27, 112, 171, 12}, + {224, 34, 75, 85, 15} + }, + { {12, 234, 137, 42, 4}, + {147, 164, 94, 0, 9}, + {37, 73, 21, 115, 0}, + {144, 7, 162, 92, 9} + }, + { {146, 159, 44, 154, 3}, + {68, 59, 156, 35, 15}, + {197, 147, 79, 148, 9}, + {252, 67, 157, 194, 2} + }, + { {144, 185, 171, 44, 8}, + {89, 234, 23, 2, 12}, + {19, 77, 89, 208, 9}, + {52, 14, 133, 121, 10} + }, + { {96, 223, 140, 227, 4}, + {64, 135, 126, 172, 11}, + {44, 115, 31, 176, 6}, + {211, 87, 238, 16, 2} + }, + { {211, 163, 99, 9, 11}, + {53, 122, 25, 199, 4}, + {217, 12, 108, 92, 11}, + {46, 57, 133, 234, 12} + }, + { {37, 61, 159, 195, 3}, + {213, 211, 36, 124, 11}, + {204, 63, 155, 202, 4}, + {211, 226, 76, 186, 11} + }, + { {48, 25, 30, 15, 13}, + {76, 107, 101, 20, 9}, + {191, 7, 137, 128, 12}, + {146, 138, 109, 99, 2} + }, + { {90, 151, 28, 16, 1}, + {70, 11, 156, 177, 0}, + {128, 131, 142, 149, 10}, + {8, 211, 157, 6, 2} + }, + { {42, 15, 107, 51, 10}, + {50, 82, 175, 37, 13}, + {92, 205, 111, 5, 4}, + {186, 79, 84, 164, 12} + }, + { {106, 117, 209, 236, 0}, + {123, 166, 34, 185, 2}, + {3, 120, 186, 229, 6}, + {73, 212, 70, 93, 14} + }, + { {30, 157, 187, 139, 1}, + {214, 234, 20, 53, 15}, + {141, 29, 219, 151, 8}, + {250, 194, 133, 118, 11} + }, + { {47, 58, 225, 220, 9}, + {255, 160, 169, 73, 14}, + {147, 184, 117, 207, 4}, + {121, 41, 80, 95, 15} + }, + { {99, 208, 151, 52, 7}, + {92, 213, 242, 209, 0}, + {226, 206, 144, 188, 6}, + {8, 180, 250, 179, 10} + }, + { {216, 157, 137, 67, 7}, + {86, 154, 84, 174, 9}, + {236, 41, 27, 145, 11}, + {151, 82, 165, 150, 10} + }, + { {131, 189, 136, 80, 11}, + {69, 146, 149, 107, 8}, + {208, 161, 27, 220, 1}, + {29, 106, 148, 154, 2} + }, + { {34, 78, 125, 138, 14}, + {48, 53, 109, 49, 15}, + {117, 27, 231, 36, 4}, + {248, 203, 106, 192, 12} + }, + { {121, 75, 63, 158, 10}, + {26, 127, 173, 208, 15}, + {87, 159, 205, 41, 14}, + {240, 187, 95, 229, 8} + }, + { {141, 51, 167, 110, 15}, + {223, 243, 75, 74, 5}, + {247, 110, 92, 203, 1}, + {165, 45, 44, 255, 11} + }, + { {33, 133, 188, 205, 7}, + {12, 179, 116, 124, 6}, + {235, 51, 218, 24, 4}, + {99, 226, 236, 211, 0} + }, + { {240, 218, 227, 80, 3}, + {116, 220, 184, 138, 12}, + {192, 172, 117, 176, 15}, + {53, 17, 211, 178, 14} + }, + { {204, 223, 42, 42, 8}, + {194, 102, 31, 162, 13}, + {21, 69, 79, 179, 3}, + {180, 95, 134, 100, 3} + }, + { {56, 130, 84, 157, 1}, + {46, 41, 184, 20, 2}, + {139, 146, 164, 17, 12}, + {66, 129, 217, 71, 4} + }, + { {110, 45, 44, 91, 14}, + {131, 51, 229, 173, 13}, + {125, 163, 75, 71, 6}, + {187, 90, 124, 204, 1} + }, + { {122, 43, 200, 47, 2}, + {43, 186, 46, 133, 9}, + {79, 65, 61, 69, 14}, + {154, 23, 69, 221, 4} + }, + { {87, 219, 131, 112, 8}, + {208, 206, 155, 201, 8}, + {16, 236, 29, 190, 10}, + {25, 61, 151, 48, 11} + }, + { {201, 217, 206, 10, 6}, + {98, 247, 84, 194, 9}, + {101, 7, 57, 185, 3}, + {148, 50, 174, 244, 6} + }, + { {140, 81, 7, 168, 12}, + {210, 103, 67, 2, 2}, + {49, 94, 8, 163, 1}, + {68, 12, 46, 100, 11} + }, + { {189, 143, 171, 242, 11}, + {150, 218, 191, 106, 15}, + {212, 253, 95, 27, 13}, + {245, 111, 213, 182, 9} + }, + { {100, 133, 26, 10, 13}, + {132, 98, 117, 176, 1}, + {181, 5, 138, 18, 6}, + {128, 218, 228, 98, 1} + }, + { {220, 109, 58, 176, 4}, + {131, 78, 198, 178, 14}, + {32, 213, 203, 99, 11}, + {116, 214, 55, 44, 1} + }, + { {231, 251, 25, 168, 3}, + {213, 54, 62, 211, 10}, + {193, 89, 141, 254, 7}, + {92, 183, 198, 202, 11} + }, + { {177, 204, 84, 80, 5}, + {36, 13, 240, 122, 8}, + {160, 162, 163, 56, 13}, + {21, 224, 251, 2, 4} + }, + { {97, 110, 245, 148, 7}, + {61, 149, 232, 240, 14}, + {226, 154, 247, 104, 6}, + {112, 241, 122, 155, 12} + }, + { {147, 83, 11, 252, 1}, + {92, 110, 142, 75, 2}, + {131, 253, 12, 172, 9}, + {77, 39, 23, 99, 10} + }, + { {27, 41, 1, 82, 8}, + {19, 10, 129, 73, 9}, + {20, 168, 9, 77, 8}, + {153, 40, 21, 12, 8} + }, + { {197, 245, 7, 104, 1}, + {213, 103, 18, 234, 0}, + {129, 110, 10, 250, 3}, + {5, 116, 142, 106, 11} + }, + { {52, 186, 247, 153, 2}, + {241, 249, 184, 20, 14}, + {73, 158, 245, 210, 12}, + {114, 129, 217, 248, 15} + }, + { {169, 176, 189, 187, 13}, + {87, 161, 247, 86, 7}, + {189, 219, 208, 217, 5}, + {230, 174, 248, 94, 10} + }, + { {149, 57, 193, 12, 5}, + {253, 170, 64, 66, 8}, + {163, 8, 57, 202, 9}, + {20, 32, 37, 91, 15} + }, + { {50, 223, 204, 191, 2}, + {104, 191, 190, 37, 11}, + {79, 211, 63, 180, 12}, + {218, 71, 223, 209, 6} + }, + { {237, 234, 172, 111, 7}, + {143, 181, 126, 206, 13}, + {239, 99, 85, 123, 7}, + {183, 55, 234, 223, 1} + }, + { {77, 86, 15, 172, 10}, + {218, 117, 15, 224, 2}, + {83, 95, 6, 171, 2}, + {64, 127, 10, 229, 11} + }, + { {116, 172, 190, 43, 0}, + {129, 233, 54, 180, 13}, + {13, 71, 211, 82, 14}, + {178, 214, 201, 120, 1} + }, + { {35, 182, 120, 236, 5}, + {109, 32, 126, 121, 6}, + {163, 113, 230, 220, 4}, + {105, 231, 224, 75, 6} + }, + { {232, 210, 137, 53, 1}, + {94, 132, 190, 134, 0}, + {138, 201, 20, 177, 7}, + {6, 23, 210, 23, 10} + }, + { {235, 150, 185, 93, 6}, + {90, 176, 252, 255, 4}, + {107, 169, 214, 157, 7}, + {47, 243, 240, 213, 10} + }, + { {85, 145, 91, 105, 0}, + {240, 106, 22, 220, 0}, + {9, 109, 168, 154, 10}, + {3, 182, 133, 96, 15} + }, + { {40, 186, 189, 218, 6}, + {83, 177, 252, 24, 15}, + {101, 187, 213, 209, 4}, + {241, 131, 248, 220, 10} + }, + { {35, 163, 75, 130, 11}, + {53, 82, 61, 65, 3}, + {212, 29, 44, 92, 4}, + {200, 43, 196, 170, 12} + }, + { {193, 172, 20, 84, 10}, + {9, 17, 145, 250, 8}, + {82, 162, 131, 88, 3}, + {21, 248, 152, 137, 0} + }, + { {100, 182, 175, 131, 2}, + {209, 209, 60, 164, 7}, + {76, 31, 86, 210, 6}, + {226, 83, 200, 184, 11} + }, + { {82, 25, 155, 54, 1}, + {92, 202, 134, 145, 9}, + {134, 205, 153, 132, 10}, + {152, 150, 21, 51, 10} + }, + { {99, 183, 61, 54, 4}, + {89, 3, 254, 241, 5}, + {38, 203, 206, 220, 6}, + {168, 247, 252, 9, 10} + }, + { {192, 85, 112, 85, 8}, + {104, 6, 129, 190, 4}, + {26, 160, 234, 160, 3}, + {39, 216, 22, 1, 6} + }, + { {52, 181, 171, 169, 13}, + {213, 234, 119, 36, 6}, + {185, 93, 90, 210, 12}, + {98, 78, 229, 122, 11} + }, + { {4, 214, 173, 116, 7}, + {220, 149, 222, 40, 4}, + {226, 235, 86, 178, 0}, + {33, 71, 186, 147, 11} + }, + { {58, 185, 185, 47, 15}, + {95, 186, 119, 21, 13}, + {255, 73, 217, 213, 12}, + {186, 142, 229, 223, 10} + }, + { {30, 193, 204, 115, 3}, + {166, 159, 150, 13, 1}, + {204, 227, 56, 55, 8}, + {139, 6, 159, 150, 5} + }, + { {227, 95, 151, 12, 12}, + {88, 231, 105, 243, 8}, + {51, 14, 159, 172, 7}, + {28, 249, 110, 113, 10} + }, + { {82, 233, 202, 244, 7}, + {45, 222, 214, 137, 10}, + {226, 245, 57, 116, 10}, + {89, 22, 183, 187, 4} + }, + { {171, 99, 139, 22, 6}, + {27, 214, 236, 67, 1}, + {102, 141, 28, 109, 5}, + {140, 35, 118, 189, 8} + }, + { {138, 229, 208, 83, 7}, + {39, 150, 208, 63, 1}, + {236, 160, 186, 117, 1}, + {143, 192, 182, 158, 4} + }, + { {103, 173, 81, 43, 1}, + {181, 34, 50, 245, 9}, + {141, 72, 171, 94, 6}, + {154, 244, 196, 74, 13} + }, + { {179, 24, 122, 4, 11}, + {108, 88, 37, 83, 12}, + {210, 5, 225, 140, 13}, + {60, 170, 65, 163, 6} + }, + { {60, 198, 137, 195, 8}, + {146, 140, 61, 44, 3}, + {28, 57, 22, 51, 12}, + {195, 75, 195, 20, 9} + }, + { {250, 116, 11, 175, 7}, + {95, 124, 102, 167, 3}, + {239, 93, 2, 229, 15}, + {206, 86, 99, 239, 10} + }, + { {4, 250, 221, 72, 11}, + {245, 181, 29, 24, 8}, + {209, 43, 181, 242, 0}, + {17, 139, 138, 218, 15} + }, + { {153, 138, 132, 196, 12}, + {10, 137, 89, 74, 10}, + {50, 50, 21, 25, 9}, + {85, 41, 169, 21, 0} + }, + { {63, 252, 218, 241, 1}, + {231, 204, 182, 125, 10}, + {136, 245, 179, 255, 12}, + {91, 230, 211, 62, 7} + }, + { {148, 250, 34, 140, 9}, + {205, 108, 25, 2, 14}, + {147, 20, 69, 242, 9}, + {116, 9, 131, 107, 3} + }, + { {228, 237, 60, 145, 13}, + {133, 7, 245, 182, 14}, + {184, 147, 203, 114, 7}, + {118, 218, 254, 10, 1} + }, + { {3, 55, 138, 20, 12}, + {73, 194, 205, 97, 0}, + {50, 133, 30, 204, 0}, + {8, 107, 52, 57, 2} + }, + { {216, 27, 34, 208, 1}, + {70, 74, 136, 138, 14}, + {128, 180, 77, 129, 11}, + {117, 17, 21, 38, 2} + }, + { {25, 91, 18, 85, 10}, + {74, 94, 137, 92, 8}, + {90, 164, 141, 169, 8}, + {19, 169, 23, 165, 2} + }, + { {33, 72, 120, 182, 2}, + {40, 20, 166, 80, 15}, + {70, 209, 225, 40, 4}, + {240, 166, 82, 129, 4} + }, + { {40, 93, 236, 19, 2}, + {98, 151, 164, 36, 13}, + {76, 131, 123, 161, 4}, + {178, 66, 94, 148, 6} + }, + { {123, 100, 52, 77, 7}, + {15, 61, 96, 253, 4}, + {235, 34, 194, 109, 14}, + {43, 240, 107, 207, 0} + }, + { {98, 75, 151, 138, 7}, + {20, 247, 104, 145, 11}, + {229, 30, 157, 36, 6}, + {216, 145, 110, 242, 8} + }, + { {103, 119, 188, 11, 1}, + {197, 167, 44, 245, 5}, + {141, 3, 222, 238, 6}, + {170, 243, 78, 90, 3} + }, + { {144, 144, 113, 245, 10}, + {120, 24, 147, 30, 6}, + {90, 248, 224, 144, 9}, + {103, 140, 145, 129, 14} + }, + { {37, 206, 233, 123, 13}, + {180, 164, 255, 108, 13}, + {189, 233, 119, 58, 4}, + {179, 111, 242, 82, 13} + }, + { {253, 83, 136, 111, 13}, + {206, 174, 111, 206, 1}, + {191, 97, 28, 171, 15}, + {135, 63, 103, 87, 3} + }, + { {231, 105, 53, 36, 11}, + {157, 23, 35, 211, 12}, + {210, 74, 201, 110, 7}, + {60, 188, 78, 139, 9} + }, + { {24, 201, 137, 127, 9}, + {30, 174, 151, 12, 9}, + {159, 233, 25, 49, 8}, + {147, 14, 151, 87, 8} + }, + { {101, 96, 64, 176, 10}, + {161, 20, 163, 192, 2}, + {80, 208, 32, 106, 6}, + {64, 60, 82, 136, 5} + }, + { {56, 46, 157, 194, 13}, + {23, 137, 109, 56, 11}, + {180, 59, 151, 65, 12}, + {209, 203, 105, 30, 8} + }, + { {205, 242, 177, 37, 13}, + {223, 132, 91, 214, 4}, + {186, 72, 212, 251, 3}, + {38, 189, 162, 31, 11} + }, + { {9, 78, 179, 61, 6}, + {26, 244, 202, 116, 12}, + {107, 204, 215, 41, 0}, + {50, 229, 50, 245, 8} + }, + { {57, 223, 214, 61, 1}, + {110, 239, 186, 116, 8}, + {139, 198, 191, 185, 12}, + {18, 229, 223, 119, 6} + }, + { {249, 54, 192, 90, 7}, + {103, 184, 232, 234, 1}, + {229, 160, 54, 201, 15}, + {133, 113, 113, 222, 6} + }, + { {119, 243, 69, 1, 5}, + {245, 15, 120, 197, 0}, + {168, 10, 44, 254, 14}, + {10, 49, 239, 10, 15} + }, + { {229, 170, 108, 152, 3}, + {165, 49, 188, 194, 14}, + {193, 147, 101, 90, 7}, + {116, 51, 216, 202, 5} + }, + { {176, 232, 167, 19, 14}, + {17, 221, 241, 6, 13}, + {124, 142, 81, 112, 13}, + {182, 8, 251, 184, 8} + }, + { {221, 219, 120, 40, 6}, + {226, 62, 94, 210, 12}, + {97, 65, 237, 187, 11}, + {52, 183, 167, 196, 7} + }, + { {59, 9, 157, 107, 2}, + {18, 187, 38, 93, 9}, + {77, 107, 153, 13, 12}, + {155, 166, 77, 212, 8} + }, + { {64, 234, 217, 35, 3}, + {53, 148, 30, 148, 9}, + {204, 73, 181, 112, 2}, + {146, 151, 130, 154, 12} + }, + { {26, 247, 252, 44, 4}, + {107, 175, 94, 49, 4}, + {35, 67, 254, 245, 8}, + {40, 199, 175, 93, 6} + }, + { {60, 154, 211, 10, 11}, + {246, 248, 57, 16, 9}, + {213, 12, 181, 147, 12}, + {144, 137, 193, 246, 15} + }, + { {179, 6, 81, 36, 2}, + {56, 24, 42, 115, 0}, + {66, 72, 166, 12, 13}, + {12, 229, 65, 129, 12} + }, + { {50, 189, 243, 128, 9}, + {117, 202, 49, 49, 14}, + {144, 28, 251, 212, 12}, + {120, 200, 197, 58, 14} + }, + { {108, 225, 50, 82, 6}, + {131, 86, 240, 152, 5}, + {100, 164, 200, 115, 6}, + {161, 144, 246, 172, 1} + }, + { {87, 93, 59, 77, 8}, + {216, 110, 5, 253, 12}, + {27, 45, 203, 174, 10}, + {59, 250, 7, 97, 11} + }, + { {50, 250, 120, 150, 11}, + {109, 28, 189, 17, 15}, + {214, 145, 229, 244, 12}, + {248, 139, 211, 139, 6} + }, + { {201, 37, 74, 207, 11}, + {47, 114, 5, 238, 3}, + {223, 53, 42, 73, 3}, + {199, 122, 4, 239, 4} + }, + { {91, 227, 88, 153, 8}, + {35, 46, 157, 213, 2}, + {25, 145, 172, 125, 10}, + {74, 187, 151, 76, 4} + }, + { {162, 249, 225, 35, 2}, + {113, 150, 50, 7, 13}, + {76, 72, 121, 244, 5}, + {190, 4, 198, 152, 14} + }, + { {119, 181, 82, 205, 9}, + {237, 106, 49, 253, 2}, + {155, 52, 170, 222, 14}, + {75, 248, 197, 107, 7} + }, + { {176, 219, 205, 212, 10}, + {120, 159, 189, 10, 10}, + {82, 187, 61, 176, 13}, + {85, 11, 223, 145, 14} + }, + { {203, 147, 21, 157, 14}, + {90, 51, 217, 215, 2}, + {123, 154, 140, 157, 3}, + {78, 185, 188, 197, 10} + }, + { {126, 39, 103, 62, 11}, + {191, 123, 171, 161, 5}, + {215, 206, 110, 71, 14}, + {168, 93, 93, 239, 13} + }, + { {27, 68, 157, 3, 7}, + {22, 157, 68, 117, 1}, + {236, 11, 146, 45, 8}, + {138, 226, 43, 150, 8} + }, + { {214, 213, 57, 224, 1}, + {212, 14, 22, 187, 6}, + {128, 121, 202, 182, 11}, + {109, 214, 135, 2, 11} + }, + { {153, 17, 59, 93, 5}, + {94, 106, 196, 94, 4}, + {171, 173, 200, 137, 9}, + {39, 162, 53, 103, 10} + }, + { {237, 201, 222, 175, 0}, + {170, 231, 54, 214, 11}, + {15, 87, 185, 59, 7}, + {214, 182, 206, 117, 5} + }, + { {152, 79, 114, 58, 0}, + {34, 110, 138, 50, 13}, + {5, 196, 239, 33, 9}, + {180, 197, 23, 100, 4} + }, + { {146, 177, 194, 94, 11}, + {109, 250, 145, 11, 1}, + {215, 164, 56, 212, 9}, + {141, 8, 149, 251, 6} + }, + { {233, 83, 55, 170, 15}, + {86, 119, 107, 210, 7}, + {245, 94, 204, 169, 7}, + {228, 189, 110, 230, 10} + }, + { {184, 249, 145, 224, 11}, + {87, 158, 51, 26, 10}, + {208, 120, 153, 241, 13}, + {85, 140, 199, 158, 10} + }, + { {54, 190, 41, 6, 4}, + {217, 8, 124, 33, 13}, + {38, 9, 71, 214, 12}, + {184, 67, 225, 9, 11} + }, + { {109, 88, 75, 223, 15}, + {254, 116, 229, 204, 11}, + {255, 189, 33, 171, 6}, + {211, 58, 114, 231, 15} + }, + { {124, 73, 255, 32, 11}, + {182, 223, 39, 144, 12}, + {208, 79, 249, 35, 14}, + {48, 158, 79, 182, 13} + }, + { {204, 4, 237, 38, 9}, + {190, 129, 7, 162, 5}, + {150, 75, 114, 3, 3}, + {164, 94, 8, 23, 13} + }, + { {126, 191, 195, 88, 12}, + {243, 234, 249, 169, 8}, + {49, 172, 63, 215, 14}, + {25, 89, 245, 124, 15} + }, + { {35, 71, 74, 52, 11}, + {44, 86, 175, 97, 0}, + {210, 197, 46, 44, 4}, + {8, 111, 86, 163, 4} + }, + { {247, 2, 101, 153, 14}, + {176, 57, 233, 199, 6}, + {121, 154, 100, 14, 15}, + {110, 57, 121, 192, 13} + }, + { {183, 134, 11, 177, 9}, + {148, 72, 191, 103, 2}, + {152, 221, 6, 30, 13}, + {78, 111, 209, 34, 9} + }, + { {29, 237, 84, 211, 10}, + {163, 31, 145, 124, 11}, + {92, 178, 171, 123, 8}, + {211, 232, 159, 140, 5} + }, + { {19, 129, 28, 244, 14}, + {8, 27, 215, 89, 2}, + {114, 243, 136, 28, 8}, + {73, 174, 189, 129, 0} + }, + { {38, 136, 183, 166, 7}, + {156, 209, 114, 17, 15}, + {230, 94, 209, 22, 4}, + {248, 132, 232, 179, 9} + }, + { {218, 189, 5, 242, 12}, + {83, 11, 211, 171, 11}, + {52, 250, 11, 213, 11}, + {221, 92, 189, 12, 10} + }, + { {179, 196, 25, 242, 6}, + {16, 28, 246, 123, 3}, + {100, 249, 130, 60, 13}, + {205, 230, 243, 128, 8} + }, + { {3, 88, 126, 158, 12}, + {104, 101, 197, 81, 15}, + {55, 151, 225, 172, 0}, + {248, 170, 58, 97, 6} + }, + { {138, 226, 81, 223, 4}, + {59, 36, 216, 31, 3}, + {47, 184, 164, 117, 1}, + {207, 129, 178, 77, 12} + }, + { {116, 44, 226, 207, 6}, + {169, 248, 96, 172, 15}, + {111, 52, 115, 66, 14}, + {243, 80, 97, 249, 5} + }, + { {80, 87, 235, 180, 4}, + {120, 206, 206, 160, 6}, + {34, 221, 126, 160, 10}, + {96, 87, 55, 49, 14} + }, + { {234, 122, 109, 152, 13}, + {119, 37, 237, 131, 14}, + {177, 155, 101, 229, 7}, + {124, 27, 122, 78, 14} + }, + { {81, 124, 23, 242, 2}, + {81, 93, 130, 248, 11}, + {68, 254, 131, 232, 10}, + {209, 244, 27, 168, 10} + }, + { {116, 206, 76, 238, 0}, + {168, 45, 62, 168, 11}, + {7, 115, 39, 50, 14}, + {209, 87, 203, 65, 5} + }, + { {235, 112, 71, 178, 6}, + {115, 85, 226, 195, 3}, + {100, 222, 32, 237, 7}, + {204, 52, 122, 172, 14} + }, + { {44, 21, 121, 54, 0}, + {250, 2, 166, 48, 5}, + {6, 201, 234, 131, 4}, + {160, 198, 84, 5, 15} + }, + { {10, 102, 96, 115, 1}, + {39, 4, 138, 45, 5}, + {140, 224, 102, 101, 0}, + {171, 69, 18, 14, 4} + }, + { {15, 250, 22, 84, 3}, + {207, 85, 152, 89, 8}, + {194, 166, 133, 255, 0}, + {25, 161, 154, 175, 3} + }, + { {248, 13, 152, 242, 6}, + {2, 154, 230, 186, 11}, + {100, 241, 155, 1, 15}, + {213, 214, 117, 148, 0} + }, + { {17, 81, 213, 137, 2}, + {112, 191, 0, 84, 2}, + {73, 26, 184, 168, 8}, + {66, 160, 15, 208, 14} + }, + { {90, 127, 72, 72, 2}, + {99, 62, 12, 169, 8}, + {65, 33, 47, 229, 10}, + {25, 83, 7, 204, 6} + }, + { {220, 246, 137, 159, 6}, + {219, 188, 220, 166, 3}, + {111, 153, 22, 243, 11}, + {198, 83, 179, 221, 11} + }, + { {142, 116, 239, 41, 6}, + {243, 245, 70, 39, 4}, + {105, 79, 114, 231, 1}, + {46, 70, 42, 252, 15} + }, + { {122, 205, 165, 235, 5}, + {22, 175, 114, 173, 15}, + {173, 122, 91, 53, 14}, + {251, 84, 239, 86, 8} + }, + { {73, 74, 158, 70, 15}, + {14, 213, 77, 216, 9}, + {246, 39, 149, 41, 2}, + {145, 187, 42, 183, 0} + }, + { {92, 188, 59, 49, 3}, + {215, 88, 150, 180, 12}, + {200, 205, 195, 211, 10}, + {50, 214, 145, 174, 11} + }, + { {24, 172, 214, 61, 15}, + {47, 249, 211, 52, 8}, + {251, 198, 179, 81, 8}, + {18, 204, 185, 255, 4} + }, + { {128, 152, 198, 222, 6}, + {104, 241, 208, 10, 11}, + {103, 182, 49, 144, 1}, + {213, 0, 184, 241, 6} + }, + { {171, 158, 93, 173, 10}, + {122, 49, 63, 119, 10}, + {91, 91, 167, 157, 5}, + {94, 239, 200, 197, 14} + }, + { {187, 215, 181, 242, 15}, + {86, 159, 251, 123, 7}, + {244, 250, 222, 189, 13}, + {237, 237, 255, 150, 10} + }, + { {231, 64, 19, 227, 5}, + {148, 68, 98, 223, 3}, + {172, 124, 128, 46, 7}, + {207, 180, 98, 34, 9} + }, + { {185, 180, 142, 161, 12}, + {67, 201, 119, 102, 2}, + {56, 87, 18, 217, 13}, + {70, 110, 233, 60, 2} + }, + { {45, 174, 203, 210, 6}, + {179, 208, 252, 104, 11}, + {100, 189, 55, 91, 4}, + {209, 99, 240, 188, 13} + }, + { {14, 251, 136, 89, 3}, + {199, 182, 156, 13, 8}, + {201, 161, 29, 247, 0}, + {27, 3, 150, 222, 3} + }, + { {140, 253, 170, 193, 1}, + {199, 198, 20, 46, 14}, + {136, 53, 91, 243, 1}, + {119, 66, 134, 62, 3} + }, + { {80, 221, 255, 96, 6}, + {112, 223, 86, 184, 12}, + {96, 111, 251, 176, 10}, + {49, 214, 175, 176, 14} + }, + { {171, 67, 201, 91, 13}, + {54, 166, 237, 79, 1}, + {189, 169, 60, 45, 5}, + {143, 43, 118, 86, 12} + }, + { {80, 242, 159, 163, 14}, + {81, 221, 95, 148, 3}, + {124, 95, 148, 240, 10}, + {194, 159, 171, 184, 10} + }, + { {128, 166, 71, 246, 14}, + {57, 81, 219, 42, 3}, + {118, 254, 38, 80, 1}, + {197, 77, 184, 169, 12} + }, + { {60, 168, 157, 27, 10}, + {147, 185, 181, 20, 9}, + {93, 139, 145, 83, 12}, + {146, 138, 217, 220, 9} + }, + { {24, 77, 221, 97, 1}, + {54, 143, 6, 60, 8}, + {136, 107, 187, 33, 8}, + {19, 198, 15, 22, 12} + }, + { {83, 127, 71, 115, 5}, + {117, 79, 202, 237, 9}, + {172, 238, 47, 236, 10}, + {155, 117, 63, 42, 14} + }, + { {164, 149, 152, 181, 4}, + {200, 130, 246, 54, 2}, + {42, 209, 154, 146, 5}, + {70, 198, 244, 17, 3} + }, + { {58, 161, 233, 220, 1}, + {63, 170, 180, 9, 6}, + {131, 185, 120, 85, 12}, + {105, 2, 213, 95, 12} + }, + { {72, 193, 6, 108, 11}, + {14, 119, 19, 136, 0}, + {211, 102, 8, 49, 2}, + {1, 28, 142, 231, 0} + }, + { {227, 219, 6, 53, 1}, + {76, 71, 186, 199, 8}, + {138, 198, 13, 188, 7}, + {30, 53, 222, 35, 2} + }, + { {26, 241, 58, 17, 0}, + {67, 78, 148, 21, 4}, + {8, 133, 200, 245, 8}, + {42, 130, 151, 44, 2} + }, + { {137, 31, 189, 213, 1}, + {94, 131, 140, 126, 14}, + {138, 187, 223, 137, 1}, + {119, 227, 28, 23, 10} + }, + { {142, 68, 6, 133, 7}, + {142, 85, 64, 39, 2}, + {234, 22, 2, 39, 1}, + {78, 64, 42, 167, 1} + }, + { {135, 127, 42, 107, 5}, + {197, 102, 78, 111, 13}, + {173, 101, 79, 238, 1}, + {191, 103, 38, 106, 3} + }, + { {122, 152, 134, 35, 6}, + {66, 217, 114, 133, 9}, + {108, 70, 17, 149, 14}, + {154, 20, 233, 180, 2} + }, + { {228, 197, 141, 119, 10}, + {152, 151, 183, 174, 1}, + {94, 235, 26, 50, 7}, + {135, 94, 222, 145, 9} + }, + { {70, 156, 46, 177, 8}, + {192, 65, 151, 165, 14}, + {24, 215, 67, 150, 2}, + {122, 94, 152, 32, 3} + }, + { {165, 170, 62, 63, 8}, + {137, 97, 191, 86, 13}, + {31, 199, 197, 90, 5}, + {182, 175, 216, 105, 1} + }, + { {14, 105, 46, 93, 5}, + {143, 103, 196, 13, 12}, + {171, 167, 73, 103, 0}, + {59, 2, 62, 111, 1} + }, + { {6, 57, 165, 147, 9}, + {213, 131, 129, 5, 15}, + {156, 154, 89, 198, 0}, + {250, 8, 28, 26, 11} + }, + { {96, 63, 93, 167, 6}, + {121, 19, 110, 180, 11}, + {110, 91, 175, 192, 6}, + {210, 215, 108, 137, 14} + }, + { {72, 219, 238, 150, 9}, + {110, 199, 157, 128, 15}, + {150, 151, 125, 177, 2}, + {240, 27, 158, 55, 6} + }, + { {95, 215, 109, 233, 5}, + {246, 47, 94, 237, 6}, + {169, 123, 110, 191, 10}, + {107, 119, 175, 70, 15} + }, + { {101, 185, 180, 188, 6}, + {201, 179, 242, 208, 14}, + {99, 210, 217, 218, 6}, + {112, 180, 252, 217, 3} + }, + { {6, 179, 0, 213, 15}, + {205, 18, 217, 13, 2}, + {250, 176, 12, 214, 0}, + {75, 9, 180, 139, 3} + }, + { {76, 98, 251, 222, 11}, + {191, 244, 141, 152, 7}, + {215, 189, 244, 99, 2}, + {225, 155, 18, 255, 13} + }, + { {154, 177, 225, 193, 15}, + {119, 154, 81, 15, 6}, + {248, 56, 120, 213, 9}, + {111, 8, 165, 158, 14} + }, + { {116, 98, 52, 56, 13}, + {133, 45, 235, 144, 4}, + {177, 194, 196, 98, 14}, + {32, 157, 123, 74, 1} + }, + { {229, 34, 138, 114, 8}, + {129, 192, 175, 202, 1}, + {20, 229, 20, 74, 7}, + {133, 63, 80, 56, 1} + }, + { {20, 217, 9, 82, 15}, + {212, 30, 213, 8, 9}, + {244, 169, 9, 178, 8}, + {145, 10, 183, 130, 11} + }, + { {218, 24, 89, 233, 7}, + {118, 56, 70, 159, 10}, + {233, 121, 161, 133, 11}, + {95, 150, 33, 198, 14} + }, + { {38, 80, 144, 16, 9}, + {196, 132, 161, 17, 0}, + {144, 128, 144, 166, 4}, + {8, 136, 82, 18, 3} + }, + { {25, 235, 51, 121, 9}, + {23, 110, 155, 92, 12}, + {153, 236, 205, 121, 8}, + {51, 173, 151, 110, 8} + }, + { {105, 80, 30, 18, 12}, + {66, 69, 229, 208, 1}, + {52, 135, 128, 169, 6}, + {128, 186, 122, 36, 2} + }, + { {150, 139, 94, 59, 15}, + {164, 123, 223, 23, 9}, + {253, 199, 173, 22, 9}, + {158, 143, 189, 226, 5} + }, + { {218, 116, 48, 6, 5}, + {79, 12, 64, 179, 5}, + {166, 0, 194, 229, 11}, + {172, 208, 35, 15, 2} + }, + { {25, 179, 22, 144, 13}, + {71, 75, 217, 80, 2}, + {176, 150, 140, 217, 8}, + {64, 169, 189, 46, 2} + }, + { {25, 3, 122, 37, 8}, + {42, 74, 15, 84, 4}, + {26, 69, 236, 9, 8}, + {34, 175, 5, 37, 4} + }, + { {45, 131, 24, 137, 9}, + {134, 34, 61, 84, 2}, + {153, 17, 140, 27, 4}, + {66, 171, 196, 70, 1} + }, + { {90, 179, 165, 245, 1}, + {95, 139, 154, 141, 6}, + {138, 250, 92, 213, 10}, + {107, 21, 157, 31, 10} + }, + { {122, 166, 126, 219, 8}, + {35, 105, 189, 189, 7}, + {29, 183, 230, 85, 14}, + {235, 219, 217, 108, 4} + }, + { {74, 39, 236, 111, 9}, + {47, 163, 15, 173, 5}, + {159, 99, 126, 69, 2}, + {171, 95, 12, 95, 4} + }, + { {199, 32, 161, 106, 9}, + {149, 160, 3, 203, 5}, + {149, 104, 80, 78, 3}, + {173, 60, 0, 90, 9} + }, + { {70, 101, 77, 162, 0}, + {177, 7, 6, 161, 3}, + {4, 91, 42, 102, 2}, + {200, 86, 14, 8, 13} + }, + { {69, 170, 40, 84, 15}, + {141, 16, 221, 200, 12}, + {242, 161, 69, 90, 2}, + {49, 59, 176, 139, 1} + }, + { {174, 121, 83, 131, 6}, + {243, 86, 96, 23, 11}, + {108, 28, 169, 231, 5}, + {222, 128, 102, 172, 15} + }, + { {156, 98, 82, 80, 12}, + {163, 76, 201, 26, 0}, + {48, 164, 164, 99, 9}, + {5, 137, 51, 44, 5} + }, + { {12, 88, 52, 54, 3}, + {206, 21, 130, 16, 13}, + {198, 194, 193, 163, 0}, + {176, 132, 26, 135, 3} + }, + { {74, 45, 0, 174, 1}, + {15, 34, 2, 161, 11}, + {135, 80, 11, 69, 2}, + {216, 84, 4, 79, 0} + }, + { {116, 221, 28, 199, 10}, + {200, 31, 53, 188, 11}, + {94, 51, 139, 178, 14}, + {211, 218, 207, 129, 3} + }, + { {255, 184, 139, 185, 11}, + {215, 248, 183, 199, 10}, + {217, 221, 17, 223, 15}, + {94, 62, 209, 254, 11} + }, + { {72, 11, 173, 35, 1}, + {22, 131, 14, 132, 13}, + {140, 75, 93, 1, 2}, + {178, 23, 12, 22, 8} + }, + { {7, 41, 136, 152, 1}, + {133, 162, 132, 65, 10}, + {129, 145, 25, 78, 0}, + {88, 34, 20, 90, 1} + }, + { {230, 29, 184, 139, 11}, + {196, 178, 37, 183, 15}, + {221, 17, 219, 134, 7}, + {254, 218, 68, 210, 3} + }, + { {115, 138, 131, 230, 3}, + {28, 216, 58, 201, 11}, + {198, 124, 21, 28, 14}, + {217, 53, 193, 179, 8} + }, + { {39, 0, 88, 211, 10}, + {160, 16, 165, 93, 3}, + {92, 177, 160, 14, 4}, + {203, 170, 80, 128, 5} + }, + { {99, 133, 237, 25, 0}, + {48, 163, 180, 229, 4}, + {9, 139, 122, 28, 6}, + {42, 114, 220, 80, 12} + }, + { {43, 58, 207, 150, 12}, + {123, 193, 237, 65, 11}, + {54, 159, 53, 205, 4}, + {216, 43, 120, 61, 14} + }, + { {219, 208, 152, 69, 3}, + {78, 156, 20, 223, 0}, + {202, 33, 144, 189, 11}, + {15, 178, 131, 151, 2} + }, + { {157, 205, 210, 135, 6}, + {170, 222, 80, 118, 11}, + {110, 20, 187, 59, 9}, + {214, 224, 167, 181, 5} + }, + { {49, 244, 150, 171, 1}, + {69, 237, 50, 116, 3}, + {141, 86, 146, 248, 12}, + {194, 228, 203, 122, 2} + }, + { {31, 47, 77, 37, 3}, + {191, 27, 14, 101, 8}, + {202, 75, 47, 79, 8}, + {26, 103, 13, 143, 13} + }, + { {238, 22, 15, 225, 14}, + {210, 81, 111, 175, 2}, + {120, 127, 6, 135, 7}, + {79, 95, 104, 164, 11} + }, + { {10, 161, 221, 53, 1}, + {63, 131, 150, 21, 0}, + {138, 203, 184, 85, 0}, + {10, 134, 156, 31, 12} + }, + { {86, 173, 47, 172, 10}, + {153, 123, 23, 161, 14}, + {83, 95, 75, 86, 10}, + {120, 94, 141, 233, 9} + }, + { {211, 126, 245, 50, 15}, + {117, 157, 203, 243, 13}, + {244, 202, 247, 236, 11}, + {188, 253, 59, 154, 14} + }, + { {26, 77, 168, 98, 11}, + {6, 158, 7, 41, 13}, + {212, 97, 91, 37, 8}, + {185, 78, 7, 150, 0} + }, + { {14, 14, 83, 226, 12}, + {178, 64, 75, 57, 11}, + {52, 124, 167, 7, 0}, + {217, 205, 32, 36, 13} + }, + { {186, 19, 25, 227, 12}, + {82, 10, 111, 31, 3}, + {60, 121, 140, 133, 13}, + {207, 143, 101, 4, 10} + }, + { {166, 38, 10, 6, 2}, + {137, 80, 44, 35, 1}, + {70, 5, 6, 70, 5}, + {140, 67, 64, 169, 1} + }, + { {59, 165, 11, 14, 1}, + {31, 106, 52, 97, 1}, + {135, 13, 10, 93, 12}, + {136, 98, 197, 111, 8} + }, + { {38, 36, 208, 138, 6}, + {161, 176, 96, 49, 3}, + {101, 16, 178, 70, 4}, + {200, 192, 96, 216, 5} + }, + { {34, 170, 93, 184, 11}, + {53, 49, 191, 17, 10}, + {209, 219, 165, 84, 4}, + {88, 143, 216, 202, 12} + }, + { {204, 89, 30, 234, 11}, + {198, 119, 7, 154, 11}, + {213, 119, 137, 163, 3}, + {213, 158, 14, 230, 3} + }, + { {93, 163, 232, 36, 7}, + {175, 154, 94, 192, 4}, + {226, 65, 124, 91, 10}, + {32, 55, 165, 159, 5} + }, + { {49, 70, 31, 250, 13}, + {20, 109, 239, 120, 3}, + {181, 255, 134, 40, 12}, + {193, 239, 123, 98, 8} + }, + { {225, 228, 77, 220, 5}, + {61, 37, 244, 234, 2}, + {163, 187, 34, 120, 7}, + {69, 114, 250, 75, 12} + }, + { {44, 242, 109, 104, 5}, + {247, 37, 126, 8, 4}, + {161, 107, 100, 243, 4}, + {33, 7, 234, 78, 15} + }, + { {188, 184, 23, 132, 2}, + {219, 89, 48, 18, 10}, + {66, 30, 129, 211, 13}, + {84, 128, 201, 173, 11} + }, + { {229, 105, 162, 218, 12}, + {129, 230, 225, 202, 15}, + {53, 180, 89, 106, 7}, + {245, 56, 118, 120, 1} + }, + { {126, 187, 69, 235, 1}, + {247, 43, 58, 141, 11}, + {141, 122, 45, 215, 14}, + {219, 21, 205, 78, 15} + }, + { {95, 193, 180, 208, 10}, + {130, 159, 145, 217, 6}, + {80, 178, 216, 63, 10}, + {105, 184, 159, 148, 1} + }, + { {67, 231, 225, 87, 9}, + {61, 134, 153, 237, 5}, + {158, 168, 126, 124, 2}, + {171, 121, 150, 27, 12} + }, + { {74, 231, 170, 10, 11}, + {7, 246, 29, 161, 5}, + {213, 5, 94, 117, 2}, + {168, 91, 134, 254, 0} + }, + { {165, 185, 85, 105, 15}, + {245, 51, 115, 94, 8}, + {249, 106, 169, 218, 5}, + {23, 172, 236, 202, 15} + }, + { {224, 84, 118, 144, 13}, + {100, 69, 225, 178, 6}, + {176, 150, 226, 160, 7}, + {100, 216, 122, 34, 6} + }, + { {210, 45, 14, 153, 2}, + {1, 123, 132, 167, 10}, + {73, 151, 11, 68, 11}, + {94, 82, 29, 232, 0} + }, + { {24, 168, 223, 230, 1}, + {63, 201, 22, 24, 11}, + {134, 127, 177, 81, 8}, + {209, 134, 137, 63, 12} + }, + { {69, 219, 175, 11, 10}, + {208, 247, 29, 196, 13}, + {93, 15, 93, 186, 2}, + {178, 59, 142, 240, 11} + }, + { {120, 139, 137, 141, 8}, + {26, 170, 61, 132, 10}, + {27, 25, 29, 17, 14}, + {82, 27, 197, 85, 8} + }, + { {241, 221, 156, 23, 1}, + {76, 143, 180, 246, 9}, + {142, 131, 155, 184, 15}, + {150, 242, 223, 19, 2} + }, + { {168, 83, 82, 179, 8}, + {98, 70, 171, 22, 3}, + {28, 212, 172, 161, 5}, + {198, 141, 86, 36, 6} + }, + { {168, 106, 55, 142, 9}, + {31, 101, 41, 18, 15}, + {151, 30, 197, 97, 5}, + {244, 137, 74, 111, 8} + }, + { {189, 126, 84, 235, 14}, + {227, 61, 107, 126, 11}, + {125, 114, 167, 235, 13}, + {215, 237, 107, 204, 7} + }, + { {223, 220, 164, 141, 1}, + {206, 173, 16, 231, 14}, + {139, 18, 83, 191, 11}, + {126, 112, 139, 87, 3} + }, + { {164, 184, 123, 136, 7}, + {245, 112, 116, 18, 14}, + {225, 29, 225, 210, 5}, + {116, 130, 224, 234, 15} + }, + { {156, 22, 106, 229, 0}, + {234, 72, 14, 46, 6}, + {10, 117, 102, 131, 9}, + {103, 71, 1, 37, 7} + }, + { {7, 66, 172, 242, 15}, + {132, 149, 207, 73, 7}, + {244, 243, 84, 46, 0}, + {233, 47, 58, 146, 1} + }, + { {76, 120, 158, 87, 8}, + {203, 197, 133, 156, 9}, + {30, 167, 145, 227, 2}, + {147, 154, 26, 61, 3} + }, + { {154, 239, 33, 84, 12}, + {27, 14, 217, 43, 12}, + {50, 168, 79, 117, 9}, + {61, 73, 183, 13, 8} + }, + { {114, 33, 143, 98, 6}, + {17, 219, 102, 137, 1}, + {100, 111, 24, 68, 14}, + {137, 22, 109, 184, 8} + }, + { {144, 22, 79, 84, 2}, + {120, 89, 140, 42, 0}, + {66, 175, 38, 128, 9}, + {5, 67, 25, 161, 14} + }, + { {255, 38, 45, 47, 8}, + {155, 41, 47, 231, 5}, + {31, 75, 70, 79, 15}, + {174, 127, 73, 77, 9} + }, + { {155, 75, 33, 101, 11}, + {30, 30, 11, 79, 12}, + {218, 104, 77, 45, 9}, + {63, 45, 7, 135, 8} + }, + { {157, 250, 242, 161, 11}, + {231, 220, 27, 86, 14}, + {216, 84, 245, 251, 9}, + {118, 173, 131, 190, 7} + }, + { {88, 151, 203, 43, 2}, + {114, 250, 30, 164, 1}, + {77, 77, 62, 145, 10}, + {130, 87, 133, 244, 14} + }, + { {71, 47, 124, 245, 10}, + {169, 19, 143, 253, 14}, + {90, 243, 239, 78, 2}, + {123, 255, 28, 137, 5} + }, + { {231, 58, 247, 133, 1}, + {253, 193, 40, 215, 14}, + {138, 30, 245, 206, 7}, + {126, 177, 72, 59, 15} + }, + { {207, 215, 242, 172, 4}, + {234, 230, 90, 243, 6}, + {35, 84, 254, 191, 3}, + {108, 245, 166, 117, 7} + }, + { {34, 145, 90, 60, 13}, + {108, 98, 247, 17, 0}, + {179, 197, 168, 148, 4}, + {8, 142, 244, 99, 6} + }, + { {34, 133, 70, 209, 9}, + {36, 67, 177, 45, 2}, + {152, 182, 42, 20, 4}, + {75, 72, 220, 34, 4} + }, + { {114, 208, 93, 189, 9}, + {124, 45, 183, 149, 2}, + {155, 219, 160, 180, 14}, + {74, 158, 219, 67, 14} + }, + { {66, 217, 149, 82, 0}, + {80, 135, 144, 153, 9}, + {4, 170, 153, 180, 2}, + {153, 144, 158, 16, 10} + }, + { {138, 133, 121, 144, 1}, + {54, 2, 148, 51, 6}, + {128, 153, 234, 21, 1}, + {108, 194, 148, 6, 12} + }, + { {197, 198, 42, 24, 4}, + {128, 100, 220, 226, 4}, + {33, 133, 70, 58, 3}, + {36, 115, 178, 96, 1} + }, + { {233, 151, 88, 80, 13}, + {102, 2, 253, 250, 0}, + {176, 161, 174, 153, 7}, + {5, 251, 244, 6, 6} + }, + { {14, 234, 177, 70, 7}, + {159, 148, 88, 25, 13}, + {230, 40, 213, 119, 0}, + {185, 129, 162, 159, 9} + }, + { {145, 53, 173, 2, 13}, + {85, 139, 69, 98, 5}, + {180, 11, 90, 200, 9}, + {164, 106, 45, 26, 10} + }, + { {190, 141, 200, 78, 4}, + {170, 170, 116, 43, 9}, + {39, 33, 59, 23, 13}, + {157, 66, 229, 85, 5} + }, + { {173, 173, 184, 75, 2}, + {131, 178, 52, 126, 13}, + {77, 33, 219, 91, 5}, + {183, 226, 196, 220, 1} + }, + { {239, 18, 224, 176, 8}, + {226, 128, 171, 195, 6}, + {16, 208, 116, 143, 7}, + {108, 61, 80, 20, 7} + }, + { {110, 30, 54, 228, 4}, + {202, 65, 106, 185, 14}, + {34, 118, 199, 135, 6}, + {121, 213, 104, 37, 3} + }, + { {182, 167, 125, 25, 13}, + {181, 43, 253, 55, 4}, + {185, 139, 238, 86, 13}, + {46, 203, 253, 74, 13} + }, + { {23, 86, 83, 139, 13}, + {244, 108, 73, 117, 3}, + {189, 28, 166, 174, 8}, + {202, 233, 35, 98, 15} + }, + { {95, 18, 213, 36, 1}, + {254, 137, 10, 209, 0}, + {130, 74, 180, 143, 10}, + {8, 181, 9, 23, 15} + }, + { {70, 117, 172, 246, 3}, + {205, 151, 134, 169, 7}, + {198, 243, 90, 230, 2}, + {233, 86, 30, 155, 3} + }, + { {60, 106, 94, 42, 12}, + {163, 109, 111, 16, 9}, + {53, 71, 165, 99, 12}, + {144, 143, 107, 108, 5} + }, + { {36, 27, 3, 248, 15}, + {212, 114, 235, 8, 10}, + {241, 252, 13, 130, 4}, + {81, 13, 116, 226, 11} + }, + { {174, 183, 170, 241, 10}, + {195, 210, 191, 47, 6}, + {88, 245, 94, 215, 5}, + {111, 79, 212, 188, 3} + }, + { {128, 123, 11, 149, 5}, + {93, 70, 204, 6, 10}, + {170, 157, 13, 224, 1}, + {86, 3, 54, 43, 10} + }, + { {6, 123, 185, 142, 2}, + {217, 182, 12, 17, 15}, + {71, 25, 221, 230, 0}, + {248, 131, 6, 217, 11} + }, + { {187, 205, 97, 202, 11}, + {54, 62, 49, 107, 15}, + {213, 56, 107, 61, 13}, + {253, 104, 199, 198, 12} + }, + { {102, 111, 155, 27, 2}, + {145, 246, 172, 181, 9}, + {77, 141, 159, 102, 6}, + {154, 211, 86, 248, 9} + }, + { {220, 128, 108, 119, 5}, + {174, 9, 214, 142, 5}, + {174, 227, 96, 19, 11}, + {167, 22, 185, 7, 5} + }, + { {244, 76, 119, 37, 13}, + {188, 77, 99, 182, 12}, + {186, 78, 227, 34, 15}, + {54, 220, 107, 35, 13} + }, + { {114, 219, 75, 25, 1}, + {116, 110, 188, 133, 8}, + {137, 141, 45, 180, 14}, + {26, 19, 215, 98, 14} + }, + { {16, 18, 108, 127, 9}, + {108, 41, 143, 12, 5}, + {159, 227, 100, 128, 8}, + {163, 15, 25, 67, 6} + }, + { {162, 1, 124, 141, 13}, + {44, 35, 101, 23, 6}, + {187, 19, 232, 4, 5}, + {110, 138, 108, 67, 4} + }, + { {92, 43, 98, 199, 13}, + {175, 74, 73, 140, 15}, + {190, 52, 109, 67, 10}, + {243, 25, 37, 47, 5} + }, + { {246, 17, 249, 50, 13}, + {244, 138, 231, 147, 5}, + {180, 201, 248, 134, 15}, + {172, 158, 117, 18, 15} + }, + { {29, 202, 246, 89, 3}, + {166, 253, 152, 92, 12}, + {201, 166, 245, 59, 8}, + {51, 161, 155, 246, 5} + }, + { {246, 222, 90, 183, 0}, + {232, 76, 190, 183, 11}, + {14, 213, 167, 182, 15}, + {222, 215, 211, 33, 7} + }, + { {2, 227, 95, 133, 4}, + {57, 71, 92, 21, 2}, + {42, 31, 172, 116, 0}, + {74, 131, 174, 41, 12} + }, + { {154, 82, 200, 49, 13}, + {102, 140, 207, 7, 0}, + {184, 193, 52, 165, 9}, + {14, 15, 51, 22, 6} + }, + { {201, 156, 238, 248, 13}, + {102, 225, 215, 234, 14}, + {177, 247, 115, 153, 3}, + {117, 126, 184, 118, 6} + }, + { {164, 0, 185, 178, 3}, + {148, 144, 166, 18, 7}, + {196, 217, 208, 2, 5}, + {228, 134, 80, 146, 9} + }, + { {83, 189, 160, 149, 3}, + {77, 154, 144, 229, 14}, + {202, 144, 91, 220, 10}, + {122, 112, 149, 155, 2} + }, + { {229, 156, 80, 168, 12}, + {224, 32, 115, 242, 10}, + {49, 80, 163, 154, 7}, + {84, 252, 224, 64, 7} + }, + { {171, 95, 148, 193, 7}, + {70, 151, 104, 127, 10}, + {232, 50, 159, 173, 5}, + {95, 225, 110, 150, 2} + }, + { {154, 1, 159, 43, 13}, + {22, 235, 71, 23, 1}, + {189, 79, 152, 5, 9}, + {142, 142, 45, 118, 8} + }, + { {179, 111, 58, 61, 12}, + {9, 110, 239, 119, 12}, + {59, 197, 207, 108, 13}, + {62, 239, 119, 105, 0} + }, + { {192, 187, 244, 25, 3}, + {101, 179, 152, 150, 12}, + {201, 130, 253, 208, 3}, + {54, 145, 156, 218, 6} + }, + { {20, 123, 78, 197, 0}, + {233, 79, 12, 12, 10}, + {10, 55, 45, 226, 8}, + {83, 3, 15, 41, 7} + }, + { {90, 199, 71, 252, 14}, + {58, 127, 219, 169, 2}, + {115, 254, 46, 53, 10}, + {73, 93, 191, 229, 12} + }, + { {55, 33, 111, 157, 0}, + {185, 107, 164, 69, 6}, + {11, 159, 104, 78, 12}, + {106, 34, 93, 105, 13} + }, + { {1, 111, 252, 42, 3}, + {37, 183, 14, 112, 13}, + {197, 67, 255, 104, 0}, + {176, 231, 14, 218, 4} + }, + { {89, 15, 175, 61, 8}, + {26, 235, 143, 228, 12}, + {27, 207, 95, 9, 10}, + {50, 127, 29, 117, 8} + }, + { {153, 1, 84, 216, 10}, + {34, 59, 129, 90, 2}, + {81, 178, 168, 9, 9}, + {69, 168, 29, 196, 4} + }, + { {67, 238, 76, 167, 7}, + {45, 21, 94, 229, 11}, + {238, 83, 39, 124, 2}, + {218, 119, 170, 139, 4} + }, + { {54, 90, 193, 205, 0}, + {248, 172, 40, 13, 10}, + {11, 56, 53, 166, 12}, + {91, 1, 67, 81, 15} + }, + { {131, 166, 230, 183, 5}, + {45, 193, 218, 103, 7}, + {174, 214, 118, 92, 1}, + {238, 101, 184, 59, 4} + }, + { {19, 38, 191, 52, 9}, + {29, 201, 143, 113, 4}, + {146, 207, 214, 76, 8}, + {40, 239, 25, 59, 8} + }, + { {75, 112, 99, 4, 11}, + {127, 84, 1, 193, 4}, + {210, 12, 96, 237, 2}, + {40, 56, 2, 175, 14} + }, + { {138, 249, 189, 185, 10}, + {83, 183, 151, 23, 14}, + {89, 219, 217, 245, 1}, + {126, 142, 158, 220, 10} + }, + { {210, 150, 211, 86, 13}, + {124, 200, 217, 187, 1}, + {182, 172, 182, 148, 11}, + {141, 217, 177, 51, 14} + }, + { {191, 171, 59, 228, 2}, + {155, 90, 62, 91, 14}, + {66, 125, 205, 95, 13}, + {125, 167, 197, 173, 9} + }, + { {174, 86, 204, 98, 6}, + {226, 149, 110, 43, 1}, + {100, 99, 54, 167, 5}, + {141, 71, 106, 148, 7} + }, + { {178, 87, 177, 0, 1}, + {84, 142, 40, 51, 4}, + {128, 8, 222, 164, 13}, + {44, 193, 71, 18, 10} + }, + { {62, 59, 212, 245, 0}, + {235, 139, 170, 29, 10}, + {10, 242, 189, 199, 12}, + {91, 133, 93, 29, 7} + }, + { {81, 226, 98, 81, 13}, + {37, 76, 217, 204, 4}, + {184, 164, 100, 120, 10}, + {35, 57, 179, 42, 4} + }, + { {109, 172, 163, 64, 13}, + {151, 192, 113, 232, 12}, + {176, 44, 83, 91, 6}, + {49, 120, 224, 62, 9} + }, + { {140, 97, 186, 38, 3}, + {143, 214, 6, 18, 5}, + {198, 69, 216, 99, 1}, + {164, 134, 6, 191, 1} + }, + { {188, 245, 45, 34, 0}, + {211, 15, 54, 34, 5}, + {4, 75, 74, 243, 13}, + {164, 70, 207, 12, 11} + }, + { {211, 103, 251, 121, 3}, + {53, 254, 142, 255, 4}, + {201, 237, 254, 108, 11}, + {47, 247, 23, 250, 12} + }, + { {84, 87, 19, 84, 12}, + {216, 78, 201, 184, 0}, + {50, 172, 142, 162, 10}, + {1, 217, 55, 33, 11} + }, + { {252, 69, 102, 73, 14}, + {162, 127, 97, 174, 4}, + {121, 38, 106, 35, 15}, + {39, 88, 111, 228, 5} + }, + { {201, 116, 9, 180, 3}, + {95, 20, 134, 226, 2}, + {194, 217, 2, 233, 3}, + {68, 118, 18, 143, 10} + }, + { {219, 223, 58, 117, 6}, + {74, 94, 222, 255, 12}, + {106, 229, 207, 189, 11}, + {63, 247, 183, 165, 2} + }, + { {124, 198, 54, 99, 4}, + {130, 77, 122, 188, 5}, + {44, 102, 198, 51, 14}, + {163, 213, 235, 36, 1} + }, + { {199, 37, 21, 237, 7}, + {157, 51, 66, 255, 2}, + {235, 122, 138, 78, 3}, + {79, 244, 44, 203, 9} + }, + { {243, 129, 78, 141, 3}, + {44, 123, 52, 199, 2}, + {203, 23, 40, 28, 15}, + {78, 50, 205, 227, 4} + }, + { {1, 97, 236, 83, 14}, + {33, 151, 197, 76, 5}, + {124, 163, 120, 104, 0}, + {163, 42, 62, 152, 4} + }, + { {185, 10, 129, 53, 14}, + {26, 152, 235, 70, 8}, + {122, 200, 21, 9, 13}, + {22, 45, 113, 149, 8} + }, + { {206, 10, 5, 154, 5}, + {150, 33, 200, 131, 11}, + {165, 154, 5, 7, 3}, + {220, 17, 56, 70, 9} + }, + { {177, 143, 157, 57, 3}, + {20, 187, 190, 118, 8}, + {201, 203, 159, 24, 13}, + {22, 231, 221, 210, 8} + }, + { {148, 176, 8, 30, 13}, + {205, 40, 213, 2, 1}, + {183, 129, 0, 210, 9}, + {132, 10, 177, 75, 3} + }, + { {80, 66, 146, 213, 15}, + {12, 220, 201, 156, 2}, + {250, 180, 148, 32, 10}, + {67, 153, 51, 179, 0} + }, + { {35, 150, 114, 80, 11}, + {100, 80, 185, 121, 4}, + {208, 164, 230, 156, 4}, + {41, 233, 208, 162, 6} + }, + { {68, 239, 204, 54, 11}, + {173, 151, 159, 160, 9}, + {214, 195, 63, 114, 2}, + {144, 95, 158, 155, 5} + }, + { {31, 253, 132, 177, 6}, + {195, 159, 210, 101, 10}, + {104, 210, 27, 255, 8}, + {90, 100, 191, 156, 3} + }, + { {197, 49, 181, 191, 8}, + {217, 163, 131, 214, 7}, + {31, 218, 216, 202, 3}, + {230, 188, 28, 89, 11} + }, + { {22, 150, 178, 17, 4}, + {192, 200, 216, 53, 4}, + {40, 132, 214, 150, 8}, + {42, 193, 177, 48, 3} + }, + { {38, 79, 248, 144, 13}, + {164, 134, 237, 49, 14}, + {176, 145, 255, 38, 4}, + {120, 203, 118, 18, 5} + }, + { {246, 196, 223, 173, 7}, + {188, 253, 118, 183, 2}, + {235, 95, 178, 54, 15}, + {78, 214, 235, 243, 13} + }, + { {156, 24, 84, 188, 5}, + {238, 41, 194, 18, 10}, + {163, 210, 161, 131, 9}, + {84, 132, 57, 71, 7} + }, + { {19, 208, 219, 228, 10}, + {120, 220, 23, 89, 2}, + {82, 125, 176, 188, 8}, + {73, 174, 131, 177, 14} + }, + { {249, 179, 22, 59, 4}, + {67, 107, 250, 214, 1}, + {45, 198, 140, 217, 15}, + {134, 181, 253, 108, 2} + }, + { {81, 149, 114, 252, 7}, + {108, 122, 210, 248, 6}, + {227, 244, 234, 152, 10}, + {97, 244, 181, 227, 6} + }, + { {242, 39, 249, 70, 5}, + {61, 138, 108, 187, 5}, + {166, 41, 254, 68, 15}, + {173, 211, 101, 27, 12} + }, + { {102, 190, 189, 206, 13}, + {221, 161, 125, 185, 15}, + {183, 59, 215, 214, 6}, + {249, 219, 232, 91, 11} + }, + { {214, 222, 191, 213, 4}, + {216, 205, 220, 191, 14}, + {42, 191, 215, 182, 11}, + {127, 211, 187, 49, 11} + }, + { {42, 6, 160, 45, 8}, + {10, 160, 43, 37, 4}, + {27, 64, 86, 5, 4}, + {42, 77, 64, 85, 0} + }, + { {20, 53, 95, 118, 11}, + {253, 91, 135, 56, 1}, + {214, 239, 170, 194, 8}, + {129, 206, 29, 171, 15} + }, + { {10, 147, 60, 139, 0}, + {66, 35, 28, 21, 7}, + {13, 19, 204, 149, 0}, + {234, 131, 140, 68, 2} + }, + { {41, 181, 114, 31, 7}, + {111, 114, 240, 116, 5}, + {239, 132, 234, 217, 4}, + {162, 224, 244, 239, 6} + }, + { {10, 176, 119, 109, 10}, + {123, 113, 19, 29, 4}, + {91, 110, 224, 213, 0}, + {43, 140, 136, 237, 14} + }, + { {73, 99, 125, 47, 0}, + {59, 39, 14, 212, 5}, + {15, 75, 236, 105, 2}, + {162, 183, 14, 77, 12} + }, + { {129, 17, 157, 135, 11}, + {92, 147, 5, 86, 3}, + {222, 27, 152, 136, 1}, + {198, 170, 12, 147, 10} + }, + { {140, 179, 51, 58, 10}, + {211, 114, 155, 18, 5}, + {85, 204, 204, 211, 1}, + {164, 141, 148, 236, 11} + }, + { {178, 29, 155, 97, 0}, + {80, 202, 38, 63, 8}, + {8, 109, 155, 132, 13}, + {31, 198, 69, 48, 10} + }, + { {188, 118, 206, 213, 10}, + {235, 221, 173, 46, 2}, + {90, 183, 54, 227, 13}, + {71, 75, 91, 189, 7} + }, + { {160, 15, 75, 230, 8}, + {56, 66, 47, 42, 11}, + {22, 125, 47, 0, 5}, + {213, 79, 68, 33, 12} + }, + { {68, 168, 183, 24, 8}, + {145, 225, 145, 144, 12}, + {17, 142, 209, 82, 2}, + {48, 152, 152, 120, 9} + }, + { {192, 147, 189, 209, 12}, + {80, 131, 221, 158, 6}, + {56, 187, 220, 144, 3}, + {103, 155, 188, 16, 10} + }, + { {67, 88, 203, 57, 12}, + {112, 228, 199, 197, 8}, + {57, 205, 49, 172, 2}, + {26, 62, 50, 112, 14} + }, + { {138, 61, 137, 8, 12}, + {83, 162, 69, 35, 8}, + {49, 9, 27, 197, 1}, + {28, 74, 36, 92, 10} + }, + { {252, 190, 21, 102, 11}, + {223, 25, 59, 186, 9}, + {214, 106, 135, 211, 15}, + {149, 221, 201, 143, 11} + }, + { {203, 103, 181, 191, 5}, + {31, 167, 202, 247, 7}, + {175, 218, 222, 109, 3}, + {238, 245, 62, 95, 8} + }, + { {233, 7, 248, 189, 4}, + {42, 162, 238, 246, 6}, + {43, 209, 254, 9, 7}, + {102, 247, 116, 85, 4} + }, + { {75, 186, 42, 243, 10}, + {67, 80, 159, 205, 15}, + {92, 245, 69, 221, 2}, + {251, 63, 144, 172, 2} + }, + { {113, 234, 245, 7, 10}, + {57, 157, 57, 212, 13}, + {94, 10, 245, 120, 14}, + {178, 185, 203, 153, 12} + }, + { {147, 43, 182, 127, 6}, + {9, 251, 202, 95, 13}, + {111, 230, 221, 76, 9}, + {191, 165, 61, 249, 0} + }, + { {83, 241, 61, 70, 1}, + {93, 15, 20, 217, 5}, + {134, 43, 200, 252, 10}, + {169, 178, 143, 11, 10} + }, + { {99, 45, 7, 61, 3}, + {29, 115, 162, 229, 8}, + {203, 206, 11, 76, 6}, + {26, 116, 92, 235, 8} + }, + { {187, 21, 254, 209, 0}, + {98, 203, 164, 127, 6}, + {8, 183, 250, 141, 13}, + {111, 226, 93, 52, 6} + }, + { {249, 117, 16, 139, 12}, + {67, 46, 97, 246, 3}, + {61, 16, 138, 233, 15}, + {198, 248, 103, 76, 2} + }, + { {82, 137, 180, 183, 15}, + {12, 155, 211, 149, 15}, + {254, 210, 217, 20, 10}, + {250, 156, 189, 147, 0} + }, + { {27, 210, 12, 213, 13}, + {78, 13, 221, 77, 2}, + {186, 179, 4, 189, 8}, + {75, 43, 187, 7, 2} + }, + { {177, 242, 231, 215, 11}, + {125, 221, 185, 78, 7}, + {222, 190, 116, 248, 13}, + {231, 41, 219, 187, 14} + }, + { {59, 199, 234, 23, 8}, + {42, 206, 189, 101, 5}, + {30, 133, 126, 61, 12}, + {170, 107, 215, 53, 4} + }, + { {129, 160, 44, 14, 10}, + {9, 49, 21, 66, 5}, + {87, 3, 64, 88, 1}, + {164, 42, 136, 201, 0} + }, + { {175, 47, 221, 40, 8}, + {179, 163, 47, 115, 8}, + {17, 75, 191, 79, 5}, + {28, 239, 76, 92, 13} + }, + { {102, 160, 63, 60, 5}, + {157, 97, 246, 145, 4}, + {163, 207, 192, 86, 6}, + {40, 150, 248, 107, 9} + }, + { {97, 107, 54, 64, 2}, + {1, 87, 40, 216, 12}, + {64, 38, 205, 104, 6}, + {49, 177, 78, 168, 0} + }, + { {179, 110, 223, 102, 4}, + {57, 205, 110, 123, 9}, + {38, 111, 183, 108, 13}, + {157, 231, 107, 57, 12} + }, + { {221, 206, 234, 142, 13}, + {174, 236, 93, 226, 15}, + {183, 21, 119, 59, 11}, + {244, 123, 163, 119, 5} + }, + { {191, 161, 228, 191, 4}, + {171, 171, 242, 71, 7}, + {47, 210, 120, 95, 13}, + {238, 36, 253, 93, 5} + }, + { {181, 50, 205, 129, 2}, + {241, 153, 44, 70, 2}, + {72, 27, 52, 202, 13}, + {70, 35, 73, 152, 15} + }, + { {93, 75, 26, 56, 8}, + {130, 110, 143, 208, 8}, + {17, 197, 141, 43, 10}, + {16, 191, 23, 100, 1} + }, + { {178, 246, 24, 165, 12}, + {73, 12, 127, 55, 2}, + {58, 81, 134, 244, 13}, + {78, 207, 227, 9, 2} + }, + { {56, 237, 90, 165, 11}, + {47, 94, 55, 52, 10}, + {218, 85, 171, 113, 12}, + {82, 206, 199, 175, 4} + }, + { {73, 219, 225, 19, 5}, + {118, 134, 216, 196, 13}, + {172, 136, 125, 185, 2}, + {178, 49, 182, 22, 14} + }, + { {73, 170, 182, 189, 9}, + {15, 225, 155, 212, 14}, + {155, 214, 213, 89, 2}, + {114, 189, 152, 127, 0} + }, + { {84, 139, 234, 26, 2}, + {160, 250, 156, 128, 13}, + {69, 133, 125, 18, 10}, + {176, 19, 149, 240, 5} + }, + { {157, 15, 70, 19, 1}, + {166, 75, 136, 102, 9}, + {140, 134, 47, 11, 9}, + {150, 97, 29, 38, 5} + }, + { {102, 97, 19, 14, 1}, + {157, 102, 32, 145, 1}, + {135, 12, 136, 102, 6}, + {136, 144, 70, 107, 9} + }, + { {248, 9, 47, 37, 2}, + {26, 91, 38, 134, 12}, + {74, 79, 73, 1, 15}, + {54, 22, 77, 165, 8} + }, + { {11, 201, 149, 46, 14}, + {26, 183, 83, 81, 9}, + {119, 74, 153, 61, 0}, + {152, 172, 174, 213, 8} + }, + { {184, 19, 104, 249, 3}, + {102, 58, 174, 14, 6}, + {201, 241, 108, 129, 13}, + {103, 7, 85, 198, 6} + }, + { {247, 151, 168, 45, 7}, + {204, 186, 126, 231, 4}, + {235, 65, 94, 158, 15}, + {46, 119, 229, 211, 3} + }, + { {64, 198, 85, 224, 12}, + {48, 5, 91, 184, 2}, + {48, 122, 166, 48, 2}, + {65, 221, 170, 0, 12} + }, + { {119, 132, 246, 36, 5}, + {172, 201, 114, 241, 4}, + {162, 70, 242, 30, 14}, + {40, 244, 233, 51, 5} + }, + { {67, 149, 31, 37, 8}, + {88, 67, 23, 245, 0}, + {26, 79, 138, 156, 2}, + {10, 254, 140, 33, 10} + }, + { {45, 159, 144, 243, 0}, + {194, 130, 186, 124, 11}, + {12, 240, 159, 155, 4}, + {211, 229, 212, 20, 3} + }, + { {146, 221, 176, 205, 11}, + {76, 190, 17, 63, 14}, + {219, 48, 219, 180, 9}, + {127, 200, 135, 211, 2} + }, + { {246, 199, 216, 26, 12}, + {160, 174, 253, 179, 1}, + {53, 129, 190, 54, 15}, + {140, 219, 247, 80, 5} + }, + { {24, 194, 89, 137, 6}, + {50, 60, 92, 20, 2}, + {105, 25, 164, 49, 8}, + {66, 131, 163, 196, 12} + }, + { {191, 94, 230, 119, 1}, + {238, 205, 170, 111, 13}, + {142, 230, 119, 175, 13}, + {191, 101, 91, 55, 7} + }, + { {154, 185, 210, 41, 10}, + {99, 250, 19, 23, 8}, + {89, 68, 185, 213, 9}, + {30, 140, 133, 252, 6} + }, + { {85, 134, 127, 171, 1}, + {180, 105, 30, 244, 7}, + {141, 95, 230, 26, 10}, + {226, 247, 137, 98, 13} + }, + { {184, 210, 232, 62, 14}, + {106, 188, 255, 2, 5}, + {119, 193, 116, 177, 13}, + {164, 15, 243, 213, 6} + }, + { {8, 95, 31, 243, 10}, + {82, 87, 143, 60, 11}, + {92, 255, 143, 161, 0}, + {211, 207, 30, 164, 10} + }, + { {18, 244, 23, 56, 3}, + {85, 125, 146, 49, 0}, + {193, 206, 130, 244, 8}, + {8, 196, 155, 234, 10} + }, + { {166, 113, 141, 223, 14}, + {217, 183, 229, 15, 3}, + {127, 187, 24, 230, 5}, + {207, 10, 126, 217, 11} + }, + { {192, 142, 206, 109, 0}, + {40, 225, 30, 174, 8}, + {11, 103, 55, 16, 3}, + {23, 87, 136, 113, 4} + }, + { {109, 149, 12, 180, 7}, + {206, 19, 246, 224, 2}, + {226, 211, 10, 155, 6}, + {64, 118, 252, 135, 3} + }, + { {150, 47, 248, 18, 6}, + {161, 154, 204, 51, 13}, + {100, 129, 255, 70, 9}, + {188, 195, 53, 152, 5} + }, + { {100, 178, 210, 193, 10}, + {225, 208, 57, 156, 2}, + {88, 52, 180, 210, 6}, + {67, 153, 192, 184, 7} + }, + { {171, 232, 165, 221, 12}, + {27, 165, 241, 79, 14}, + {59, 186, 81, 125, 5}, + {127, 40, 250, 93, 8} + }, + { {1, 1, 118, 119, 7}, + {44, 83, 194, 92, 5}, + {238, 230, 232, 8, 0}, + {163, 164, 60, 163, 4} + }, + { {122, 87, 242, 127, 10}, + {106, 254, 171, 189, 5}, + {95, 228, 254, 165, 14}, + {171, 221, 87, 245, 6} + }, + { {49, 50, 151, 33, 7}, + {85, 217, 106, 84, 0}, + {232, 78, 148, 200, 12}, + {2, 165, 105, 186, 10} + }, + { {84, 80, 181, 69, 13}, + {220, 141, 65, 156, 4}, + {186, 42, 208, 162, 10}, + {35, 152, 43, 19, 11} + }, + { {11, 87, 84, 254, 5}, + {110, 39, 202, 121, 3}, + {167, 242, 174, 173, 0}, + {201, 229, 62, 71, 6} + }, + { {182, 45, 55, 60, 6}, + {153, 123, 226, 51, 12}, + {99, 206, 203, 70, 13}, + {60, 196, 125, 233, 9} + }, + { {148, 78, 188, 41, 1}, + {132, 173, 14, 54, 12}, + {137, 67, 215, 34, 9}, + {54, 199, 11, 82, 1} + }, + { {212, 209, 225, 203, 8}, + {240, 174, 17, 142, 7}, + {29, 56, 120, 178, 11}, + {231, 24, 135, 80, 15} + }, + { {41, 127, 114, 212, 15}, + {111, 86, 233, 120, 14}, + {242, 180, 239, 233, 4}, + {113, 233, 118, 175, 6} + }, + { {185, 41, 143, 89, 11}, + {23, 251, 165, 78, 8}, + {217, 175, 25, 73, 13}, + {23, 42, 93, 254, 8} + }, + { {156, 178, 65, 173, 13}, + {255, 40, 91, 6, 2}, + {187, 88, 36, 211, 9}, + {70, 13, 161, 79, 15} + }, + { {9, 10, 241, 177, 13}, + {54, 128, 203, 84, 14}, + {184, 216, 245, 9, 0}, + {114, 173, 48, 22, 12} + }, + { {16, 185, 135, 171, 7}, + {85, 251, 82, 4, 11}, + {237, 94, 25, 208, 8}, + {210, 4, 173, 250, 10} + }, + { {126, 194, 225, 64, 6}, + {178, 156, 120, 137, 4}, + {96, 40, 116, 55, 14}, + {41, 17, 227, 148, 13} + }, + { {67, 167, 86, 127, 1}, + {45, 99, 154, 253, 1}, + {143, 230, 174, 92, 2}, + {139, 245, 156, 107, 4} + }, + { {26, 3, 90, 19, 6}, + {34, 90, 204, 21, 1}, + {108, 133, 172, 5, 8}, + {138, 131, 53, 164, 4} + }, + { {29, 135, 214, 9, 13}, + {166, 235, 89, 116, 0}, + {185, 6, 190, 27, 8}, + {2, 233, 173, 118, 5} + }, + { {166, 252, 66, 185, 15}, + {229, 116, 243, 39, 10}, + {249, 212, 35, 246, 5}, + {94, 76, 242, 234, 7} + }, + { {107, 129, 183, 247, 6}, + {26, 211, 242, 221, 7}, + {110, 254, 216, 29, 6}, + {235, 180, 252, 181, 8} + }, + { {69, 254, 109, 129, 14}, + {241, 21, 93, 228, 14}, + {120, 27, 103, 250, 2}, + {114, 123, 170, 136, 15} + }, + { {53, 236, 43, 184, 7}, + {149, 124, 246, 96, 14}, + {225, 221, 67, 122, 12}, + {112, 102, 243, 234, 9} + }, + { {49, 88, 4, 149, 14}, + {72, 29, 225, 68, 10}, + {122, 146, 1, 168, 12}, + {82, 40, 123, 129, 2} + }, + { {152, 19, 34, 45, 15}, + {78, 122, 75, 6, 4}, + {251, 68, 76, 129, 9}, + {38, 13, 37, 231, 2} + }, + { {154, 159, 90, 231, 3}, + {110, 90, 30, 63, 11}, + {206, 117, 175, 149, 9}, + {223, 199, 133, 167, 6} + }, + { {212, 231, 111, 182, 1}, + {189, 79, 158, 162, 7}, + {134, 223, 110, 114, 11}, + {228, 87, 159, 43, 13} + }, + { {229, 90, 150, 106, 14}, + {192, 245, 107, 218, 9}, + {117, 102, 149, 170, 7}, + {149, 189, 106, 240, 3} + }, + { {79, 187, 212, 222, 13}, + {239, 163, 217, 217, 11}, + {183, 178, 189, 223, 2}, + {217, 185, 188, 95, 7} + }, + { {121, 157, 133, 152, 15}, + {86, 187, 241, 224, 10}, + {241, 154, 27, 153, 14}, + {80, 120, 253, 214, 10} + }, + { {9, 112, 158, 165, 9}, + {79, 197, 7, 84, 2}, + {154, 87, 144, 233, 0}, + {66, 174, 10, 63, 2} + }, + { {3, 87, 205, 203, 8}, + {112, 167, 13, 109, 3}, + {29, 59, 62, 172, 0}, + {203, 107, 14, 80, 14} + }, + { {212, 185, 186, 58, 5}, + {197, 234, 214, 146, 13}, + {165, 197, 217, 210, 11}, + {180, 150, 181, 122, 3} + }, + { {24, 64, 161, 74, 8}, + {18, 172, 1, 8, 5}, + {21, 40, 80, 33, 8}, + {161, 8, 3, 84, 8} + }, + { {118, 93, 35, 164, 0}, + {216, 78, 34, 161, 14}, + {2, 92, 75, 166, 14}, + {120, 84, 71, 33, 11} + }, + { {215, 210, 250, 82, 11}, + {228, 220, 157, 219, 5}, + {212, 165, 244, 190, 11}, + {173, 187, 147, 178, 7} + }, + { {112, 131, 115, 144, 14}, + {48, 90, 249, 144, 6}, + {112, 156, 236, 16, 14}, + {96, 153, 245, 160, 12} + }, + { {54, 219, 219, 178, 9}, + {244, 206, 191, 17, 11}, + {148, 221, 189, 182, 12}, + {216, 143, 215, 50, 15} + }, + { {238, 65, 132, 139, 9}, + {134, 167, 33, 135, 3}, + {157, 18, 24, 39, 7}, + {206, 24, 78, 86, 1} + }, + { {82, 197, 48, 152, 6}, + {0, 62, 208, 177, 6}, + {97, 144, 202, 52, 10}, + {104, 208, 183, 192, 0} + }, + { {252, 86, 183, 22, 2}, + {218, 221, 168, 178, 5}, + {70, 142, 214, 163, 15}, + {164, 209, 91, 181, 11} + }, + { {116, 165, 189, 128, 1}, + {149, 139, 52, 176, 6}, + {128, 27, 218, 82, 14}, + {96, 210, 205, 26, 9} + }, + { {149, 143, 205, 134, 4}, + {184, 139, 92, 98, 11}, + {38, 27, 63, 26, 9}, + {212, 99, 173, 17, 13} + }, + { {217, 178, 146, 205, 0}, + {75, 232, 24, 222, 2}, + {11, 52, 148, 217, 11}, + {71, 177, 129, 125, 2} + }, + { {167, 235, 71, 193, 10}, + {177, 87, 57, 79, 10}, + {88, 62, 45, 126, 5}, + {95, 41, 206, 168, 13} + }, + { {79, 1, 94, 2, 9}, + {166, 67, 5, 209, 1}, + {148, 7, 168, 15, 2}, + {136, 186, 12, 38, 5} + }, + { {106, 36, 9, 113, 10}, + {19, 16, 167, 173, 0}, + {88, 233, 2, 69, 6}, + {11, 94, 80, 140, 8} + }, + { {206, 68, 49, 8, 9}, + {150, 36, 1, 179, 4}, + {145, 8, 194, 39, 3}, + {44, 216, 2, 70, 9} + }, + { {49, 255, 30, 233, 10}, + {65, 127, 63, 124, 10}, + {89, 119, 143, 248, 12}, + {83, 239, 207, 232, 2} + }, + { {20, 10, 7, 54, 11}, + {156, 89, 139, 0, 9}, + {214, 206, 5, 2, 8}, + {144, 13, 25, 163, 9} + }, + { {70, 59, 86, 20, 0}, + {233, 67, 136, 145, 8}, + {2, 134, 173, 198, 2}, + {24, 145, 28, 41, 7} + }, + { {59, 6, 12, 183, 14}, + {10, 25, 239, 101, 3}, + {126, 211, 6, 13, 12}, + {202, 111, 121, 133, 0} + }, + { {183, 192, 126, 222, 10}, + {168, 125, 181, 91, 7}, + {87, 183, 224, 62, 13}, + {237, 170, 219, 225, 5} + }, + { {131, 167, 59, 199, 12}, + {25, 66, 93, 127, 7}, + {62, 61, 206, 92, 1}, + {239, 235, 164, 41, 8} + }, + { {169, 68, 61, 125, 8}, + {26, 37, 167, 126, 4}, + {27, 235, 194, 41, 5}, + {39, 238, 90, 69, 8} + }, + { {89, 62, 133, 40, 0}, + {83, 169, 10, 224, 8}, + {1, 74, 23, 201, 10}, + {16, 117, 9, 92, 10} + }, + { {91, 223, 9, 190, 3}, + {94, 62, 158, 225, 11}, + {199, 217, 15, 189, 10}, + {216, 119, 151, 199, 10} + }, + { {90, 199, 81, 168, 1}, + {54, 46, 26, 177, 2}, + {129, 88, 174, 53, 10}, + {72, 213, 135, 70, 12} + }, + { {172, 197, 14, 185, 6}, + {130, 119, 246, 38, 2}, + {105, 215, 10, 51, 5}, + {70, 70, 254, 228, 1} + }, + { {140, 158, 159, 124, 11}, + {222, 241, 159, 58, 8}, + {211, 239, 151, 147, 1}, + {21, 207, 152, 247, 11} + }, + { {94, 223, 124, 158, 10}, + {234, 63, 157, 177, 15}, + {87, 147, 239, 183, 10}, + {248, 219, 159, 197, 7} + }, + { {194, 247, 145, 150, 7}, + {93, 150, 216, 179, 3}, + {230, 152, 158, 244, 3}, + {204, 209, 182, 155, 10} + }, + { {104, 128, 250, 139, 3}, + {38, 240, 52, 148, 7}, + {205, 21, 240, 17, 6}, + {226, 146, 192, 246, 4} + }, + { {164, 118, 61, 185, 15}, + {213, 53, 239, 54, 6}, + {249, 219, 198, 226, 5}, + {102, 207, 122, 202, 11} + }, + { {232, 136, 149, 37, 11}, + {30, 145, 51, 150, 8}, + {218, 74, 145, 17, 7}, + {22, 156, 200, 151, 8} + }, + { {94, 137, 11, 42, 0}, + {146, 106, 22, 129, 9}, + {5, 77, 9, 23, 10}, + {152, 22, 133, 100, 9} + }, + { {168, 252, 52, 240, 6}, + {67, 21, 242, 58, 14}, + {96, 242, 195, 241, 5}, + {117, 196, 250, 140, 2} + }, + { {102, 80, 220, 156, 14}, + {232, 181, 229, 145, 2}, + {115, 147, 176, 166, 6}, + {72, 154, 122, 209, 7} + }, + { {217, 235, 112, 198, 6}, + {43, 30, 88, 218, 15}, + {102, 48, 237, 121, 11}, + {245, 177, 167, 141, 4} + }, + { {6, 57, 26, 27, 7}, + {197, 114, 196, 21, 9}, + {237, 133, 137, 198, 0}, + {154, 130, 52, 234, 3} + }, + { {100, 213, 236, 219, 14}, + {224, 183, 245, 172, 7}, + {125, 179, 122, 178, 6}, + {227, 90, 254, 208, 7} + }, + { {80, 23, 140, 98, 3}, + {68, 155, 14, 168, 1}, + {196, 99, 30, 128, 10}, + {129, 87, 13, 146, 2} + }, + { {128, 175, 91, 93, 8}, + {57, 98, 157, 62, 8}, + {27, 173, 175, 80, 1}, + {23, 203, 148, 105, 12} + }, + { {178, 110, 247, 189, 9}, + {61, 237, 171, 55, 14}, + {155, 222, 247, 100, 13}, + {126, 205, 91, 123, 12} + }, + { {210, 217, 41, 11, 11}, + {84, 62, 21, 135, 13}, + {221, 9, 73, 180, 11}, + {190, 26, 135, 194, 10} + }, + { {142, 230, 136, 246, 1}, + {143, 132, 158, 43, 3}, + {134, 241, 22, 119, 1}, + {205, 71, 146, 31, 1} + }, + { {144, 133, 126, 179, 5}, + {36, 75, 214, 54, 7}, + {172, 215, 234, 16, 9}, + {230, 198, 189, 34, 4} + }, + { {214, 244, 185, 51, 14}, + {209, 156, 215, 183, 5}, + {124, 201, 210, 246, 11}, + {174, 222, 179, 152, 11} + }, + { {68, 17, 154, 175, 12}, + {200, 226, 71, 148, 3}, + {63, 85, 152, 130, 2}, + {194, 158, 36, 113, 3} + }, + { {48, 177, 90, 93, 2}, + {105, 122, 180, 28, 0}, + {75, 165, 168, 208, 12}, + {3, 130, 213, 233, 6} + }, + { {66, 57, 254, 118, 6}, + {105, 211, 198, 153, 13}, + {102, 231, 249, 196, 2}, + {185, 150, 60, 185, 6} + }, + { {108, 223, 228, 140, 12}, + {234, 167, 121, 160, 14}, + {51, 18, 127, 179, 6}, + {112, 89, 238, 85, 7} + }, + { {110, 159, 250, 19, 0}, + {226, 194, 188, 181, 13}, + {12, 133, 255, 151, 6}, + {186, 211, 212, 52, 7} + }, + { {251, 180, 205, 219, 9}, + {119, 169, 181, 239, 3}, + {157, 187, 50, 221, 15}, + {207, 122, 217, 94, 14} + }, + { {106, 94, 54, 11, 12}, + {66, 101, 105, 181, 13}, + {61, 6, 199, 165, 6}, + {186, 217, 106, 100, 2} + }, + { {187, 128, 110, 3, 5}, + {38, 73, 116, 71, 5}, + {172, 7, 96, 29, 13}, + {174, 34, 233, 38, 4} + }, + { {151, 235, 197, 191, 10}, + {185, 191, 155, 71, 11}, + {95, 218, 61, 126, 9}, + {222, 45, 159, 217, 13} + }, + { {15, 117, 226, 80, 2}, + {227, 214, 128, 105, 4}, + {64, 164, 122, 239, 0}, + {41, 96, 22, 188, 7} + }, + { {11, 49, 73, 156, 8}, + {123, 34, 133, 65, 2}, + {19, 153, 40, 205, 0}, + {72, 42, 20, 77, 14} + }, + { {215, 174, 213, 193, 8}, + {177, 137, 25, 255, 10}, + {24, 58, 183, 94, 11}, + {95, 249, 137, 24, 13} + }, + { {210, 248, 182, 187, 9}, + {69, 237, 147, 151, 15}, + {157, 214, 209, 244, 11}, + {254, 156, 155, 122, 2} + }, + { {34, 55, 245, 42, 9}, + {117, 163, 43, 49, 5}, + {149, 74, 254, 196, 4}, + {168, 205, 76, 90, 14} + }, + { {68, 127, 202, 153, 4}, + {225, 230, 204, 164, 10}, + {41, 149, 63, 226, 2}, + {82, 83, 54, 120, 7} + }, + { {141, 146, 126, 41, 15}, + {230, 113, 95, 86, 4}, + {249, 71, 228, 155, 1}, + {38, 175, 168, 230, 7} + }, + { {13, 54, 100, 46, 5}, + {239, 33, 74, 96, 5}, + {167, 66, 102, 203, 0}, + {160, 101, 40, 79, 7} + }, + { {213, 191, 237, 221, 12}, + {249, 171, 221, 238, 14}, + {59, 187, 127, 218, 11}, + {119, 123, 189, 89, 15} + }, + { {139, 198, 212, 229, 1}, + {46, 133, 26, 127, 2}, + {138, 114, 182, 61, 1}, + {79, 229, 138, 23, 4} + }, + { {221, 244, 124, 110, 1}, + {239, 45, 22, 250, 5}, + {135, 99, 226, 251, 11}, + {165, 246, 139, 79, 7} + }, + { {69, 159, 45, 147, 1}, + {212, 3, 156, 228, 15}, + {140, 155, 79, 154, 2}, + {242, 115, 156, 2, 11} + }, + { {134, 21, 175, 172, 5}, + {220, 227, 70, 35, 6}, + {163, 95, 90, 134, 1}, + {108, 70, 44, 115, 11} + }, + { {52, 174, 42, 121, 9}, + {133, 104, 191, 44, 12}, + {153, 229, 71, 82, 12}, + {51, 79, 209, 106, 1} + }, + { {246, 135, 29, 191, 4}, + {152, 43, 254, 183, 3}, + {47, 219, 142, 22, 15}, + {206, 215, 253, 65, 9} + }, + { {22, 183, 193, 11, 4}, + {241, 170, 88, 37, 1}, + {45, 8, 62, 214, 8}, + {138, 65, 165, 88, 15} + }, + { {122, 54, 203, 160, 5}, + {119, 200, 110, 161, 2}, + {160, 93, 54, 197, 14}, + {72, 87, 97, 62, 14} + }, + { {127, 66, 16, 98, 10}, + {130, 28, 43, 217, 1}, + {84, 96, 132, 47, 14}, + {137, 189, 67, 132, 1} + }, + { {103, 112, 117, 206, 8}, + {249, 37, 33, 217, 7}, + {23, 58, 224, 238, 6}, + {233, 184, 74, 73, 15} + }, + { {167, 70, 60, 209, 12}, + {128, 5, 237, 127, 6}, + {56, 179, 198, 46, 5}, + {111, 235, 122, 0, 1} + }, + { {153, 117, 131, 110, 2}, + {91, 254, 2, 106, 1}, + {71, 108, 26, 233, 9}, + {133, 100, 7, 253, 10} + }, + { {81, 145, 211, 21, 9}, + {124, 202, 145, 212, 0}, + {154, 140, 184, 152, 10}, + {2, 184, 149, 51, 14} + }, + { {53, 76, 2, 185, 8}, + {128, 108, 163, 100, 10}, + {25, 212, 3, 42, 12}, + {82, 108, 83, 96, 1} + }, + { {208, 27, 128, 57, 5}, + {68, 170, 202, 134, 8}, + {169, 192, 29, 128, 11}, + {22, 21, 53, 82, 2} + }, + { {123, 39, 171, 229, 3}, + {31, 218, 46, 237, 6}, + {202, 125, 94, 77, 14}, + {107, 119, 69, 191, 8} + }, + { {168, 140, 171, 169, 4}, + {18, 224, 118, 38, 14}, + {41, 93, 83, 17, 5}, + {118, 70, 224, 116, 8} + }, + { {138, 112, 46, 100, 13}, + {79, 69, 71, 11, 4}, + {178, 103, 64, 229, 1}, + {45, 14, 42, 47, 2} + }, + { {17, 144, 216, 79, 13}, + {108, 168, 85, 92, 1}, + {191, 33, 176, 152, 8}, + {131, 170, 161, 83, 6} + }, + { {168, 56, 156, 230, 8}, + {75, 129, 39, 26, 11}, + {22, 115, 145, 193, 5}, + {213, 142, 72, 29, 2} + }, + { {99, 172, 245, 8, 11}, + {53, 177, 49, 241, 12}, + {209, 10, 243, 92, 6}, + {56, 248, 200, 218, 12} + }, + { {153, 224, 174, 41, 11}, + {7, 253, 23, 70, 4}, + {217, 71, 80, 121, 9}, + {38, 46, 139, 254, 0} + }, + { {232, 139, 97, 157, 3}, + {62, 50, 184, 134, 14}, + {203, 152, 109, 17, 7}, + {118, 17, 212, 199, 12} + }, + { {17, 175, 187, 184, 0}, + {17, 234, 158, 112, 14}, + {1, 221, 223, 88, 8}, + {112, 231, 149, 120, 8} + }, + { {98, 250, 27, 120, 14}, + {81, 116, 255, 153, 8}, + {113, 237, 133, 244, 6}, + {25, 159, 242, 232, 10} + }, + { {241, 171, 236, 42, 5}, + {37, 171, 126, 194, 13}, + {165, 67, 125, 88, 15}, + {180, 55, 237, 90, 4} + }, + { {164, 120, 185, 64, 2}, + {209, 148, 36, 26, 12}, + {64, 41, 209, 226, 5}, + {53, 130, 66, 152, 11} + }, + { {78, 147, 112, 26, 3}, + {230, 50, 152, 145, 5}, + {197, 128, 236, 151, 2}, + {168, 145, 148, 198, 7} + }, + { {93, 31, 190, 33, 6}, + {194, 219, 78, 244, 12}, + {104, 71, 223, 139, 10}, + {50, 247, 45, 180, 3} + }, + { {222, 153, 178, 148, 7}, + {206, 218, 208, 147, 14}, + {226, 148, 217, 151, 11}, + {124, 144, 181, 183, 3} + }, + { {119, 8, 209, 116, 7}, + {188, 152, 226, 217, 8}, + {226, 232, 177, 14, 14}, + {25, 180, 113, 147, 13} + }, + { {187, 192, 140, 154, 8}, + {2, 173, 181, 67, 3}, + {21, 147, 16, 61, 13}, + {204, 42, 219, 84, 0} + }, + { {234, 28, 8, 68, 4}, + {74, 0, 100, 171, 8}, + {34, 33, 3, 133, 7}, + {29, 82, 96, 5, 2} + }, + { {184, 154, 172, 7, 8}, + {74, 137, 61, 6, 13}, + {30, 3, 85, 145, 13}, + {182, 11, 201, 21, 2} + }, + { {151, 79, 121, 58, 13}, + {180, 46, 207, 115, 13}, + {181, 201, 239, 46, 9}, + {188, 239, 55, 66, 13} + }, + { {124, 55, 168, 31, 9}, + {207, 170, 173, 164, 5}, + {159, 129, 94, 195, 14}, + {162, 91, 85, 95, 3} + }, + { {132, 5, 69, 184, 11}, + {180, 51, 131, 34, 2}, + {209, 218, 42, 2, 1}, + {68, 76, 28, 194, 13} + }, + { {137, 136, 232, 242, 10}, + {34, 144, 151, 74, 15}, + {84, 241, 113, 25, 1}, + {245, 46, 144, 148, 4} + }, + { {122, 5, 47, 138, 7}, + {22, 123, 100, 161, 7}, + {229, 31, 74, 5, 14}, + {232, 82, 109, 230, 8} + }, + { {192, 107, 231, 168, 3}, + {53, 247, 10, 130, 14}, + {193, 94, 125, 96, 3}, + {116, 21, 14, 250, 12} + }, + { {225, 214, 143, 229, 13}, + {92, 197, 127, 238, 2}, + {186, 127, 22, 184, 7}, + {71, 127, 234, 51, 10} + }, + { {135, 193, 94, 208, 1}, + {164, 71, 148, 91, 2}, + {128, 183, 168, 62, 1}, + {77, 162, 158, 34, 5} + }, + { {177, 141, 71, 4, 14}, + {56, 91, 113, 98, 8}, + {114, 14, 43, 24, 13}, + {20, 104, 237, 161, 12} + }, + { {185, 96, 48, 55, 10}, + {11, 28, 163, 86, 5}, + {94, 192, 192, 105, 13}, + {166, 172, 83, 141, 0} + }, + { {57, 22, 249, 242, 5}, + {118, 136, 238, 120, 7}, + {164, 249, 246, 137, 12}, + {225, 231, 113, 22, 14} + }, + { {91, 245, 56, 225, 10}, + {67, 30, 23, 253, 6}, + {88, 113, 202, 253, 10}, + {107, 254, 135, 140, 2} + }, + { {69, 2, 140, 137, 3}, + {132, 177, 12, 196, 2}, + {201, 19, 20, 10, 2}, + {66, 51, 8, 210, 1} + }, + { {139, 247, 79, 21, 14}, + {123, 87, 221, 103, 0}, + {122, 143, 46, 253, 1}, + {14, 107, 190, 173, 14} + }, + { {52, 169, 38, 14, 14}, + {137, 123, 113, 0, 13}, + {119, 6, 73, 82, 12}, + {176, 8, 237, 233, 1} + }, + { {254, 161, 180, 87, 2}, + {139, 155, 176, 159, 5}, + {78, 162, 216, 87, 15}, + {175, 144, 221, 157, 1} + }, + { {170, 172, 74, 33, 2}, + {35, 80, 54, 39, 8}, + {72, 69, 35, 85, 5}, + {30, 70, 192, 172, 4} + }, + { {199, 149, 142, 146, 10}, + {192, 211, 149, 227, 3}, + {84, 151, 26, 158, 3}, + {204, 122, 156, 176, 3} + }, + { {77, 114, 150, 13, 7}, + {207, 245, 72, 212, 0}, + {235, 6, 148, 235, 2}, + {2, 177, 42, 255, 3} + }, + { {198, 113, 60, 138, 13}, + {197, 39, 69, 147, 7}, + {181, 19, 200, 230, 3}, + {236, 154, 46, 74, 3} + }, + { {26, 219, 8, 147, 10}, + {66, 30, 157, 5, 11}, + {92, 145, 13, 181, 8}, + {218, 11, 151, 132, 2} + }, + { {235, 178, 94, 21, 3}, + {111, 81, 188, 215, 0}, + {202, 135, 164, 221, 7}, + {14, 179, 216, 175, 6} + }, + { {26, 140, 101, 49, 13}, + {54, 9, 211, 37, 12}, + {184, 202, 99, 21, 8}, + {58, 76, 185, 6, 12} + }, + { {184, 194, 144, 210, 13}, + {6, 140, 249, 26, 3}, + {180, 176, 148, 49, 13}, + {197, 137, 243, 22, 0} + }, + { {237, 10, 185, 59, 12}, + {146, 160, 239, 214, 13}, + {61, 201, 213, 11, 7}, + {182, 191, 112, 84, 9} + }, + { {53, 64, 254, 171, 14}, + {160, 253, 103, 84, 7}, + {125, 87, 240, 42, 12}, + {226, 174, 107, 240, 5} + }, + { {77, 141, 169, 163, 10}, + {146, 146, 23, 228, 15}, + {92, 89, 91, 27, 2}, + {242, 126, 132, 148, 9} + }, + { {25, 111, 83, 7, 9}, + {63, 78, 9, 116, 9}, + {158, 12, 175, 105, 8}, + {146, 233, 7, 47, 12} + }, + { {170, 161, 45, 108, 8}, + {27, 35, 55, 11, 4}, + {19, 107, 72, 85, 5}, + {45, 14, 204, 77, 8} + }, + { {212, 51, 219, 225, 6}, + {241, 218, 78, 158, 2}, + {104, 125, 188, 194, 11}, + {71, 151, 37, 184, 15} + }, + { {41, 68, 117, 210, 10}, + {50, 21, 161, 120, 7}, + {84, 186, 226, 41, 4}, + {225, 232, 90, 132, 12} + }, + { {222, 189, 217, 145, 6}, + {243, 154, 212, 183, 10}, + {104, 153, 187, 215, 11}, + {94, 210, 181, 156, 15} + }, + { {123, 98, 241, 8, 1}, + {55, 172, 40, 209, 4}, + {129, 8, 244, 109, 14}, + {40, 177, 67, 94, 12} + }, + { {16, 243, 23, 219, 8}, + {81, 111, 153, 28, 3}, + {29, 190, 140, 240, 8}, + {195, 137, 159, 104, 10} + }, + { {184, 117, 135, 64, 5}, + {87, 207, 96, 42, 0}, + {160, 46, 26, 225, 13}, + {5, 64, 111, 62, 10} + }, + { {154, 182, 79, 175, 0}, + {123, 105, 30, 39, 3}, + {15, 95, 38, 213, 9}, + {206, 71, 137, 109, 14} + }, + { {212, 4, 243, 160, 1}, + {180, 200, 2, 178, 6}, + {128, 92, 242, 2, 11}, + {100, 212, 1, 50, 13} + }, + { {225, 106, 123, 127, 5}, + {61, 100, 238, 222, 13}, + {175, 237, 229, 104, 7}, + {183, 183, 114, 107, 12} + }, + { {12, 218, 188, 26, 8}, + {194, 165, 157, 16, 13}, + {21, 131, 213, 179, 0}, + {176, 139, 154, 84, 3} + }, + { {4, 96, 183, 149, 11}, + {157, 213, 129, 20, 6}, + {218, 158, 208, 98, 0}, + {98, 136, 26, 187, 9} + }, + { {28, 206, 196, 143, 3}, + {174, 189, 24, 36, 11}, + {207, 18, 55, 51, 8}, + {210, 65, 139, 215, 5} + }, + { {35, 222, 229, 165, 12}, + {120, 133, 123, 101, 14}, + {58, 90, 119, 188, 4}, + {122, 109, 234, 17, 14} + }, + { {99, 5, 255, 232, 5}, + {52, 227, 102, 249, 6}, + {161, 127, 250, 12, 6}, + {105, 246, 108, 114, 12} + }, + { {225, 195, 39, 185, 4}, + {16, 103, 250, 198, 6}, + {41, 222, 76, 56, 7}, + {102, 53, 254, 96, 8} + }, + { {140, 43, 164, 30, 0}, + {139, 163, 136, 2, 13}, + {7, 130, 93, 67, 1}, + {180, 1, 28, 93, 1} + }, + { {28, 217, 222, 180, 0}, + {234, 207, 150, 16, 10}, + {2, 215, 185, 179, 8}, + {80, 134, 159, 53, 7} + }, + { {85, 100, 240, 183, 1}, + {173, 140, 130, 244, 7}, + {142, 208, 242, 106, 10}, + {226, 244, 19, 27, 5} + }, + { {176, 199, 42, 118, 10}, + {8, 94, 191, 42, 5}, + {86, 229, 78, 48, 13}, + {165, 79, 215, 161, 0} + }, + { {231, 13, 80, 70, 9}, + {172, 2, 33, 251, 9}, + {150, 32, 171, 14, 7}, + {157, 248, 68, 3, 5} + }, + { {50, 149, 124, 126, 3}, + {108, 59, 182, 57, 5}, + {199, 227, 234, 148, 12}, + {169, 198, 221, 195, 6} + }, + { {71, 130, 81, 20, 11}, + {188, 16, 153, 209, 0}, + {210, 136, 164, 30, 2}, + {8, 185, 144, 131, 13} + }, + { {120, 242, 17, 22, 15}, + {95, 28, 249, 144, 1}, + {246, 136, 132, 241, 14}, + {128, 153, 243, 143, 10} + }, + { {144, 251, 64, 119, 10}, + {105, 30, 155, 14, 9}, + {94, 224, 45, 240, 9}, + {151, 13, 151, 137, 6} + }, + { {162, 111, 219, 64, 3}, + {53, 214, 44, 59, 8}, + {192, 45, 191, 100, 5}, + {29, 195, 70, 186, 12} + }, + { {36, 36, 50, 53, 7}, + {141, 80, 226, 52, 4}, + {234, 196, 194, 66, 4}, + {34, 196, 112, 171, 1} + }, + { {135, 138, 180, 213, 3}, + {140, 145, 152, 95, 14}, + {202, 178, 213, 30, 1}, + {127, 161, 152, 147, 1} + }, + { {238, 169, 194, 236, 0}, + {171, 226, 50, 139, 10}, + {3, 116, 57, 87, 7}, + {93, 20, 196, 125, 5} + }, + { {194, 91, 81, 134, 10}, + {120, 22, 9, 147, 11}, + {86, 24, 173, 164, 3}, + {220, 153, 6, 129, 14} + }, + { {154, 26, 220, 22, 4}, + {106, 137, 204, 19, 9}, + {38, 131, 181, 133, 9}, + {156, 131, 57, 21, 6} + }, + { {115, 131, 224, 101, 1}, + {44, 138, 58, 205, 4}, + {138, 96, 124, 28, 14}, + {43, 53, 197, 19, 4} + }, + { {25, 137, 191, 169, 14}, + {18, 251, 87, 84, 14}, + {121, 95, 217, 25, 8}, + {114, 174, 173, 244, 8} + }, + { {29, 136, 219, 124, 13}, + {190, 232, 215, 88, 8}, + {179, 237, 177, 27, 8}, + {17, 174, 177, 119, 13} + }, + { {58, 57, 201, 98, 1}, + {119, 138, 38, 9, 9}, + {132, 105, 57, 197, 12}, + {153, 6, 69, 30, 14} + }, + { {196, 95, 111, 141, 1}, + {252, 103, 12, 166, 14}, + {139, 31, 111, 162, 3}, + {118, 83, 14, 99, 15} + }, + { {127, 212, 244, 88, 13}, + {230, 173, 241, 249, 4}, + {177, 162, 242, 191, 14}, + {41, 248, 251, 86, 7} + }, + { {235, 94, 5, 198, 13}, + {94, 5, 105, 235, 11}, + {182, 58, 7, 173, 7}, + {221, 121, 106, 7, 10} + }, + { {0, 222, 87, 31, 10}, + {120, 117, 153, 52, 9}, + {95, 142, 167, 176, 0}, + {146, 201, 154, 225, 14} + }, + { {48, 182, 57, 119, 2}, + {89, 24, 190, 60, 5}, + {78, 233, 198, 208, 12}, + {163, 199, 209, 137, 10} + }, + { {217, 17, 79, 9, 9}, + {118, 107, 5, 198, 0}, + {153, 15, 40, 137, 11}, + {6, 58, 13, 102, 14} + }, + { {11, 138, 78, 254, 13}, + {46, 97, 223, 73, 11}, + {183, 247, 37, 29, 0}, + {217, 47, 184, 103, 4} + }, + { {150, 200, 148, 195, 5}, + {132, 141, 80, 31, 11}, + {172, 50, 145, 54, 9}, + {223, 128, 171, 18, 1} + }, + { {222, 86, 207, 27, 9}, + {246, 237, 141, 167, 1}, + {157, 143, 54, 167, 11}, + {142, 91, 27, 118, 15} + }, + { {233, 20, 44, 234, 11}, + {70, 49, 39, 234, 7}, + {213, 115, 66, 137, 7}, + {229, 126, 72, 198, 2} + }, + { {166, 193, 149, 63, 7}, + {156, 183, 242, 23, 1}, + {239, 202, 152, 54, 5}, + {142, 132, 254, 211, 9} + }, + { {112, 249, 99, 53, 11}, + {125, 94, 179, 132, 12}, + {218, 204, 105, 240, 14}, + {50, 28, 215, 171, 14} + }, + { {60, 210, 112, 229, 12}, + {234, 12, 123, 28, 6}, + {58, 112, 228, 179, 12}, + {99, 141, 227, 5, 7} + }, + { {67, 44, 58, 151, 8}, + {9, 64, 133, 245, 15}, + {30, 149, 195, 76, 2}, + {250, 250, 16, 41, 0} + }, + { {96, 54, 213, 83, 2}, + {113, 145, 168, 188, 1}, + {76, 170, 182, 192, 6}, + {131, 209, 88, 152, 14} + }, + { {135, 118, 248, 196, 14}, + {233, 148, 77, 123, 6}, + {114, 49, 246, 238, 1}, + {109, 235, 34, 153, 7} + }, + { {32, 85, 144, 158, 12}, + {72, 166, 225, 48, 3}, + {55, 144, 154, 160, 4}, + {192, 200, 118, 81, 2} + }, + { {51, 149, 147, 18, 0}, + {80, 202, 176, 113, 1}, + {4, 140, 154, 156, 12}, + {136, 224, 213, 48, 10} + }, + { {61, 129, 11, 57, 10}, + {146, 122, 183, 68, 0}, + {89, 205, 8, 27, 12}, + {2, 46, 213, 228, 9} + }, + { {39, 67, 30, 187, 2}, + {128, 119, 174, 85, 3}, + {77, 215, 140, 46, 4}, + {202, 167, 94, 224, 1} + }, + { {240, 133, 185, 233, 2}, + {16, 186, 54, 190, 6}, + {73, 121, 218, 16, 15}, + {103, 214, 197, 208, 8} + }, + { {163, 11, 146, 226, 0}, + {0, 194, 42, 91, 11}, + {4, 116, 157, 12, 5}, + {221, 165, 68, 48, 0} + }, + { {220, 20, 37, 232, 5}, + {214, 41, 66, 170, 6}, + {161, 122, 66, 131, 11}, + {101, 84, 41, 70, 11} + }, + { {180, 171, 135, 231, 11}, + {157, 219, 59, 14, 11}, + {222, 126, 29, 82, 13}, + {215, 13, 205, 187, 9} + }, + { {143, 183, 62, 39, 0}, + {203, 67, 30, 119, 5}, + {14, 71, 206, 223, 1}, + {174, 231, 140, 45, 3} + }, + { {255, 211, 18, 95, 2}, + {202, 126, 184, 223, 1}, + {79, 164, 140, 191, 15}, + {143, 177, 215, 229, 3} + }, + { {5, 76, 149, 162, 10}, + {144, 149, 3, 112, 11}, + {84, 90, 147, 42, 0}, + {208, 236, 10, 144, 9} + }, + { {29, 121, 189, 67, 6}, + {211, 159, 68, 92, 13}, + {108, 43, 217, 235, 8}, + {179, 162, 47, 156, 11} + }, + { {49, 220, 189, 155, 11}, + {84, 189, 181, 116, 15}, + {221, 155, 211, 184, 12}, + {242, 234, 219, 210, 10} + }, + { {93, 131, 76, 204, 2}, + {170, 59, 28, 200, 2}, + {67, 51, 44, 27, 10}, + {65, 51, 141, 197, 5} + }, + { {184, 110, 95, 128, 10}, + {51, 93, 45, 50, 10}, + {80, 31, 167, 97, 13}, + {84, 203, 75, 172, 12} + }, + { {221, 181, 2, 55, 14}, + {203, 90, 211, 230, 1}, + {126, 196, 10, 219, 11}, + {134, 124, 181, 173, 3} + }, + { {204, 212, 249, 32, 2}, + {242, 148, 22, 178, 4}, + {64, 73, 242, 179, 3}, + {36, 214, 130, 148, 15} + }, + { {222, 175, 20, 123, 3}, + {135, 59, 154, 191, 9}, + {205, 226, 143, 87, 11}, + {159, 213, 157, 206, 1} + }, + { {157, 32, 19, 22, 15}, + {159, 88, 193, 82, 1}, + {246, 140, 128, 75, 9}, + {132, 168, 49, 175, 9} + }, + { {124, 189, 240, 175, 1}, + {239, 170, 50, 180, 15}, + {143, 80, 251, 211, 14}, + {242, 212, 197, 95, 7} + }, + { {34, 195, 216, 114, 10}, + {32, 150, 191, 25, 1}, + {84, 225, 188, 52, 4}, + {137, 143, 214, 144, 4} + }, + { {69, 199, 100, 214, 12}, + {168, 7, 217, 232, 7}, + {54, 178, 110, 58, 2}, + {225, 121, 190, 1, 5} + }, + { {141, 147, 90, 116, 0}, + {234, 66, 158, 90, 0}, + {2, 229, 172, 155, 1}, + {5, 167, 148, 37, 7} + }, + { {177, 161, 236, 16, 8}, + {33, 139, 181, 66, 4}, + {16, 131, 120, 88, 13}, + {36, 42, 221, 24, 4} + }, + { {208, 121, 53, 51, 6}, + {81, 31, 194, 150, 13}, + {108, 202, 201, 224, 11}, + {182, 148, 63, 136, 10} + }, + { {74, 81, 93, 164, 6}, + {122, 23, 70, 145, 2}, + {98, 91, 168, 165, 2}, + {72, 150, 46, 133, 14} + }, + { {58, 45, 19, 35, 5}, + {23, 74, 98, 53, 9}, + {172, 76, 139, 69, 12}, + {154, 196, 101, 46, 8} + }, + { {236, 65, 230, 22, 1}, + {174, 199, 160, 130, 5}, + {134, 134, 120, 35, 7}, + {164, 16, 94, 55, 5} + }, + { {127, 172, 78, 24, 2}, + {163, 121, 180, 225, 8}, + {65, 135, 35, 95, 14}, + {24, 114, 217, 236, 5} + }, + { {115, 198, 100, 107, 13}, + {36, 45, 123, 237, 5}, + {189, 98, 102, 60, 14}, + {171, 125, 235, 66, 4} + }, + { {77, 251, 76, 21, 7}, + {239, 23, 220, 196, 8}, + {234, 131, 45, 251, 2}, + {18, 51, 190, 143, 7} + }, + { {229, 15, 78, 90, 11}, + {164, 115, 173, 234, 9}, + {213, 167, 47, 10, 7}, + {149, 123, 92, 226, 5} + }, + { {122, 161, 57, 166, 0}, + {27, 10, 54, 145, 7}, + {6, 89, 200, 85, 14}, + {232, 150, 197, 13, 8} + }, + { {18, 122, 254, 87, 13}, + {109, 205, 205, 29, 13}, + {190, 167, 245, 228, 8}, + {187, 139, 59, 59, 6} + }, + { {168, 230, 106, 159, 15}, + {47, 116, 253, 38, 7}, + {255, 149, 102, 113, 5}, + {230, 75, 242, 239, 4} + }, + { {89, 155, 96, 189, 8}, + {106, 42, 155, 196, 14}, + {27, 208, 109, 153, 10}, + {114, 61, 149, 69, 6} + }, + { {49, 234, 54, 102, 9}, + {13, 77, 59, 88, 13}, + {150, 102, 197, 120, 12}, + {177, 173, 203, 43, 0} + }, + { {55, 203, 228, 166, 3}, + {172, 159, 58, 65, 15}, + {198, 82, 125, 62, 12}, + {248, 37, 207, 147, 5} + }, + { {134, 60, 248, 42, 13}, + {229, 160, 71, 51, 13}, + {181, 65, 243, 198, 1}, + {188, 206, 32, 90, 7} + }, + { {14, 181, 149, 30, 6}, + {219, 179, 208, 49, 1}, + {103, 138, 154, 215, 0}, + {136, 192, 188, 221, 11} + }, + { {197, 251, 85, 118, 12}, + {249, 7, 219, 218, 9}, + {54, 234, 173, 250, 3}, + {149, 189, 190, 9, 15} + }, + { {123, 128, 210, 239, 5}, + {46, 232, 114, 221, 3}, + {175, 116, 176, 29, 14}, + {203, 180, 225, 119, 4} + }, + { {195, 179, 195, 50, 10}, + {113, 210, 155, 195, 1}, + {84, 204, 60, 220, 3}, + {140, 61, 148, 184, 14} + }, + { {103, 62, 26, 116, 11}, + {205, 80, 175, 249, 8}, + {210, 229, 135, 206, 6}, + {25, 255, 80, 171, 3} + }, + { {146, 201, 210, 150, 1}, + {44, 206, 144, 19, 11}, + {134, 148, 185, 52, 9}, + {220, 128, 151, 51, 4} + }, + { {127, 25, 25, 12, 11}, + {222, 58, 37, 209, 8}, + {211, 9, 137, 143, 14}, + {24, 186, 69, 199, 11} + }, + { {144, 112, 164, 195, 10}, + {65, 157, 1, 14, 7}, + {92, 50, 80, 224, 9}, + {231, 8, 11, 152, 2} + }, + { {113, 67, 83, 178, 1}, + {52, 78, 170, 208, 3}, + {132, 220, 172, 40, 14}, + {192, 181, 87, 34, 12} + }, + { {251, 75, 181, 78, 15}, + {30, 191, 105, 219, 13}, + {247, 42, 221, 45, 15}, + {189, 185, 111, 215, 8} + }, + { {197, 129, 104, 9, 8}, + {160, 34, 21, 198, 4}, + {25, 1, 104, 26, 3}, + {38, 58, 132, 64, 5} + }, + { {43, 241, 86, 121, 0}, + {99, 103, 178, 93, 0}, + {9, 230, 168, 253, 4}, + {11, 164, 222, 108, 6} + }, + { {139, 50, 134, 244, 8}, + {75, 193, 139, 75, 2}, + {18, 246, 20, 205, 1}, + {77, 45, 24, 61, 2} + }, + { {35, 88, 1, 235, 6}, + {80, 52, 98, 77, 11}, + {109, 120, 1, 172, 4}, + {219, 36, 98, 192, 10} + }, + { {42, 83, 143, 50, 8}, + {82, 199, 175, 1, 1}, + {20, 207, 28, 165, 4}, + {136, 15, 94, 52, 10} + }, + { {16, 109, 126, 84, 8}, + {41, 79, 133, 56, 12}, + {18, 167, 235, 96, 8}, + {49, 202, 31, 41, 4} + }, + { {70, 253, 197, 231, 10}, + {249, 151, 19, 173, 11}, + {94, 122, 59, 246, 2}, + {219, 92, 142, 153, 15} + }, + { {69, 106, 10, 93, 0}, + {137, 100, 140, 204, 8}, + {11, 165, 5, 106, 2}, + {19, 51, 18, 105, 1} + }, + { {217, 50, 39, 125, 10}, + {91, 121, 139, 206, 4}, + {91, 238, 68, 201, 11}, + {39, 61, 25, 237, 10} + }, + { {97, 59, 235, 152, 1}, + {117, 226, 172, 192, 14}, + {129, 157, 125, 200, 6}, + {112, 51, 84, 122, 14} + }, + { {191, 19, 181, 70, 0}, + {218, 139, 40, 91, 5}, + {6, 42, 220, 143, 13}, + {173, 161, 77, 21, 11} + }, + { {130, 186, 168, 203, 13}, + {69, 160, 93, 15, 15}, + {189, 49, 85, 212, 1}, + {255, 11, 160, 90, 2} + }, + { {81, 205, 99, 27, 3}, + {52, 126, 144, 228, 13}, + {205, 140, 107, 56, 10}, + {178, 112, 151, 226, 12} + }, + { {112, 171, 240, 76, 7}, + {45, 186, 120, 152, 12}, + {227, 32, 253, 80, 14}, + {49, 145, 229, 219, 4} + }, + { {170, 102, 185, 24, 7}, + {23, 180, 236, 51, 4}, + {225, 137, 214, 101, 5}, + {44, 195, 114, 222, 8} + }, + { {140, 20, 60, 97, 6}, + {194, 17, 70, 62, 4}, + {104, 99, 194, 131, 1}, + {39, 198, 40, 132, 3} + }, + { {241, 160, 157, 75, 0}, + {17, 169, 52, 222, 1}, + {13, 43, 144, 88, 15}, + {135, 178, 201, 88, 8} + }, + { {95, 27, 95, 220, 4}, + {250, 107, 204, 217, 10}, + {35, 191, 173, 143, 10}, + {89, 179, 61, 101, 15} + }, + { {104, 72, 189, 153, 8}, + {18, 165, 165, 148, 14}, + {25, 155, 209, 33, 6}, + {114, 154, 90, 84, 8} + }, + { {235, 245, 28, 134, 10}, + {75, 23, 53, 243, 3}, + {86, 19, 138, 253, 7}, + {204, 250, 206, 141, 2} + }, + { {201, 92, 23, 53, 15}, + {94, 85, 195, 246, 8}, + {250, 206, 131, 169, 3}, + {22, 252, 58, 167, 10} + }, + { {164, 229, 178, 170, 9}, + {133, 230, 51, 50, 7}, + {149, 84, 218, 114, 5}, + {228, 204, 198, 122, 1} + }, + { {137, 187, 40, 109, 9}, + {79, 34, 31, 78, 12}, + {155, 97, 77, 217, 1}, + {55, 47, 132, 79, 2} + }, + { {252, 83, 211, 81, 9}, + {246, 206, 169, 158, 0}, + {152, 172, 188, 163, 15}, + {7, 153, 87, 54, 15} + }, + { {132, 250, 176, 250, 11}, + {197, 180, 155, 26, 15}, + {213, 240, 213, 242, 1}, + {245, 141, 146, 218, 3} + }, + { {135, 156, 9, 173, 7}, + {220, 48, 86, 103, 10}, + {235, 89, 3, 158, 1}, + {94, 102, 160, 195, 11} + }, + { {84, 19, 120, 184, 10}, + {224, 58, 143, 144, 6}, + {81, 209, 236, 130, 10}, + {96, 159, 21, 192, 7} + }, + { {93, 207, 118, 108, 5}, + {174, 111, 90, 248, 12}, + {163, 102, 239, 59, 10}, + {49, 245, 175, 103, 5} + }, + { {15, 128, 112, 181, 8}, + {170, 0, 147, 85, 6}, + {26, 208, 224, 31, 0}, + {106, 172, 144, 5, 5} + }, + { {208, 181, 28, 15, 14}, + {73, 59, 85, 182, 1}, + {127, 3, 138, 208, 11}, + {134, 218, 173, 201, 2} + }, + { {132, 202, 160, 119, 12}, + {136, 132, 219, 14, 13}, + {62, 224, 85, 50, 1}, + {183, 13, 178, 17, 1} + }, + { {237, 211, 180, 253, 12}, + {202, 167, 251, 222, 6}, + {59, 242, 220, 187, 7}, + {103, 189, 254, 85, 3} + }, + { {54, 161, 151, 8, 4}, + {145, 235, 112, 17, 0}, + {33, 14, 152, 86, 12}, + {8, 128, 237, 120, 9} + }, + { {140, 65, 87, 242, 7}, + {182, 87, 194, 26, 3}, + {228, 254, 168, 35, 1}, + {197, 132, 62, 166, 13} + }, + { {236, 146, 34, 145, 13}, + {198, 64, 249, 134, 6}, + {184, 148, 68, 147, 7}, + {102, 25, 240, 38, 3} + }, + { {44, 51, 74, 17, 10}, + {227, 82, 173, 4, 0}, + {88, 133, 44, 195, 4}, + {2, 11, 84, 172, 7} + }, + { {149, 119, 148, 119, 8}, + {201, 143, 139, 126, 1}, + {30, 226, 158, 234, 9}, + {135, 237, 31, 25, 3} + }, + { {79, 158, 57, 137, 13}, + {214, 32, 93, 245, 14}, + {185, 25, 199, 159, 2}, + {122, 251, 160, 70, 11} + }, + { {181, 203, 34, 59, 11}, + {132, 126, 187, 70, 13}, + {221, 196, 77, 58, 13}, + {182, 45, 215, 226, 1} + }, + { {238, 226, 189, 59, 11}, + {151, 181, 191, 151, 5}, + {221, 203, 212, 119, 7}, + {174, 159, 218, 222, 9} + }, + { {133, 212, 25, 195, 12}, + {208, 4, 85, 126, 3}, + {60, 57, 130, 186, 1}, + {199, 234, 162, 0, 11} + }, + { {186, 35, 215, 19, 11}, + {55, 219, 169, 23, 1}, + {220, 142, 188, 69, 13}, + {142, 137, 93, 190, 12} + }, + { {141, 72, 238, 157, 1}, + {174, 229, 132, 70, 14}, + {139, 151, 113, 43, 1}, + {118, 34, 26, 119, 5} + }, + { {120, 69, 116, 14, 9}, + {46, 47, 33, 176, 5}, + {151, 2, 234, 33, 14}, + {160, 216, 79, 71, 4} + }, + { {100, 231, 234, 47, 8}, + {169, 230, 63, 164, 5}, + {31, 69, 126, 114, 6}, + {162, 95, 198, 121, 5} + }, + { {100, 118, 246, 62, 4}, + {233, 229, 234, 176, 5}, + {39, 198, 246, 226, 6}, + {160, 213, 122, 121, 7} + }, + { {14, 238, 158, 149, 0}, + {139, 197, 156, 53, 10}, + {10, 151, 151, 119, 0}, + {90, 195, 154, 61, 1} + }, + { {220, 175, 168, 110, 11}, + {143, 186, 31, 170, 13}, + {215, 97, 95, 83, 11}, + {181, 95, 133, 223, 1} + }, + { {135, 109, 56, 242, 14}, + {129, 22, 199, 123, 15}, + {116, 241, 203, 110, 1}, + {253, 238, 54, 136, 1} + }, + { {59, 235, 39, 76, 5}, + {31, 111, 120, 73, 12}, + {163, 46, 77, 125, 12}, + {57, 33, 239, 111, 8} + }, + { {184, 191, 89, 23, 4}, + {123, 10, 252, 54, 9}, + {46, 137, 175, 209, 13}, + {150, 195, 245, 13, 14} + }, + { {171, 32, 236, 13, 0}, + {43, 161, 36, 71, 4}, + {11, 3, 112, 77, 5}, + {46, 34, 72, 93, 4} + }, + { {13, 85, 26, 83, 3}, + {198, 86, 132, 124, 1}, + {204, 165, 138, 171, 0}, + {131, 226, 22, 166, 3} + }, + { {164, 73, 46, 167, 8}, + {136, 71, 39, 6, 15}, + {30, 87, 73, 34, 5}, + {246, 14, 78, 33, 1} + }, + { {93, 108, 92, 103, 6}, + {171, 29, 70, 252, 9}, + {110, 99, 163, 107, 10}, + {147, 246, 43, 141, 5} + }, + { {153, 4, 153, 70, 10}, + {26, 152, 5, 122, 1}, + {86, 41, 146, 9, 9}, + {133, 234, 1, 149, 8} + }, + { {10, 157, 128, 226, 6}, + {66, 146, 82, 41, 11}, + {100, 112, 27, 149, 0}, + {217, 68, 164, 148, 2} + }, + { {105, 134, 183, 41, 15}, + {22, 241, 123, 244, 4}, + {249, 78, 214, 25, 6}, + {34, 253, 232, 246, 8} + }, + { {143, 173, 130, 44, 13}, + {143, 226, 83, 99, 8}, + {179, 68, 27, 95, 1}, + {28, 108, 164, 127, 1} + }, + { {226, 67, 95, 38, 3}, + {60, 87, 46, 147, 1}, + {198, 79, 172, 36, 7}, + {140, 151, 78, 163, 12} + }, + { {209, 155, 215, 78, 11}, + {124, 251, 25, 218, 9}, + {215, 46, 189, 152, 11}, + {149, 185, 141, 243, 14} + }, + { {196, 218, 25, 133, 15}, + {220, 20, 93, 150, 10}, + {250, 25, 133, 178, 3}, + {86, 155, 162, 131, 11} + }, + { {26, 193, 30, 251, 4}, + {2, 111, 214, 29, 3}, + {45, 247, 136, 53, 8}, + {203, 134, 191, 100, 0} + }, + { {17, 45, 28, 17, 1}, + {5, 11, 132, 116, 8}, + {136, 131, 139, 72, 8}, + {18, 226, 29, 10, 0} + }, + { {45, 76, 8, 144, 6}, + {130, 20, 228, 96, 10}, + {96, 145, 3, 43, 4}, + {80, 98, 114, 132, 1} + }, + { {108, 235, 79, 250, 4}, + {179, 103, 254, 136, 11}, + {37, 255, 45, 115, 6}, + {209, 23, 254, 108, 13} + }, + { {171, 203, 198, 115, 11}, + {38, 215, 187, 79, 9}, + {220, 230, 61, 61, 5}, + {159, 45, 222, 182, 4} + }, + { {40, 32, 233, 173, 13}, + {63, 160, 103, 4, 6}, + {187, 89, 112, 65, 4}, + {98, 14, 96, 95, 12} + }, + { {127, 250, 209, 178, 2}, + {243, 156, 186, 209, 11}, + {68, 216, 181, 255, 14}, + {216, 181, 211, 156, 15} + }, + { {27, 102, 106, 88, 7}, + {39, 124, 204, 105, 4}, + {225, 165, 102, 109, 8}, + {41, 99, 51, 238, 4} + }, + { {164, 137, 72, 44, 7}, + {172, 50, 118, 2, 8}, + {227, 65, 41, 18, 5}, + {20, 6, 228, 195, 5} + }, + { {141, 42, 28, 71, 0}, + {139, 1, 12, 94, 9}, + {14, 35, 133, 75, 1}, + {151, 163, 8, 13, 1} + }, + { {125, 182, 84, 216, 0}, + {227, 41, 184, 248, 2}, + {1, 178, 166, 219, 14}, + {65, 241, 217, 76, 7} + }, + { {179, 222, 10, 136, 11}, + {68, 124, 61, 99, 10}, + {209, 21, 7, 188, 13}, + {92, 107, 195, 226, 2} + }, + { {214, 120, 202, 34, 1}, + {229, 204, 6, 131, 9}, + {132, 69, 49, 230, 11}, + {156, 22, 3, 58, 7} + }, + { {136, 97, 82, 97, 11}, + {39, 86, 3, 30, 0}, + {216, 100, 168, 97, 1}, + {7, 140, 6, 174, 4} + }, + { {0, 238, 90, 84, 7}, + {45, 84, 220, 56, 8}, + {226, 165, 167, 112, 0}, + {17, 195, 178, 171, 4} + }, + { {84, 105, 30, 139, 4}, + {129, 111, 68, 148, 11}, + {45, 23, 137, 98, 10}, + {210, 146, 47, 104, 1} + }, + { {128, 254, 13, 106, 8}, + {81, 37, 31, 42, 9}, + {21, 107, 7, 240, 1}, + {149, 79, 138, 72, 10} + }, + { {195, 127, 154, 209, 2}, + {65, 214, 140, 255, 10}, + {72, 181, 159, 236, 3}, + {95, 243, 22, 184, 2} + }, + { {57, 235, 155, 203, 12}, + {19, 238, 125, 92, 11}, + {61, 61, 157, 121, 12}, + {211, 171, 231, 124, 8} + }, + { {156, 205, 246, 33, 8}, + {162, 207, 19, 54, 12}, + {24, 70, 251, 51, 9}, + {54, 204, 143, 52, 5} + }, + { {83, 192, 184, 251, 11}, + {4, 188, 151, 221, 7}, + {221, 241, 208, 60, 10}, + {235, 190, 147, 210, 0} + }, + { {159, 93, 81, 153, 0}, + {242, 46, 128, 119, 10}, + {9, 152, 171, 175, 9}, + {94, 224, 23, 68, 15} + }, + { {208, 152, 110, 185, 3}, + {100, 121, 150, 134, 14}, + {201, 215, 97, 144, 11}, + {118, 22, 153, 226, 6} + }, + { {21, 133, 42, 206, 6}, + {136, 122, 84, 104, 7}, + {103, 53, 74, 26, 8}, + {225, 98, 165, 225, 1} + }, + { {144, 212, 121, 19, 3}, + {116, 28, 148, 54, 5}, + {204, 137, 226, 176, 9}, + {166, 194, 147, 130, 14} + }, + { {44, 22, 172, 202, 12}, + {194, 161, 109, 40, 7}, + {53, 51, 86, 131, 4}, + {225, 75, 104, 84, 3} + }, + { {112, 226, 233, 181, 14}, + {57, 156, 255, 132, 6}, + {122, 217, 116, 112, 14}, + {98, 31, 243, 153, 12} + }, + { {36, 160, 106, 178, 15}, + {165, 80, 247, 0, 7}, + {244, 213, 96, 82, 4}, + {224, 14, 240, 170, 5} + }, + { {114, 1, 237, 44, 10}, + {56, 187, 39, 129, 4}, + {83, 75, 120, 4, 14}, + {40, 30, 77, 209, 12} + }, + { {76, 99, 9, 66, 8}, + {147, 6, 13, 136, 1}, + {20, 41, 12, 99, 2}, + {129, 27, 6, 12, 9} + }, + { {6, 185, 203, 230, 13}, + {253, 194, 87, 9, 11}, + {182, 125, 57, 214, 0}, + {217, 14, 164, 59, 15} + }, + { {215, 246, 104, 218, 12}, + {225, 44, 221, 235, 7}, + {53, 177, 102, 254, 11}, + {237, 123, 179, 72, 7} + }, + { {129, 109, 246, 113, 1}, + {37, 199, 130, 126, 12}, + {136, 230, 251, 104, 1}, + {55, 228, 30, 58, 4} + }, + { {88, 117, 65, 240, 5}, + {119, 14, 194, 168, 2}, + {160, 248, 42, 225, 10}, + {65, 84, 55, 14, 14} + }, + { {74, 115, 6, 33, 0}, + {67, 71, 10, 133, 0}, + {8, 70, 12, 229, 2}, + {10, 21, 14, 44, 2} + }, + { {72, 9, 86, 13, 1}, + {46, 99, 0, 148, 8}, + {139, 6, 169, 1, 2}, + {18, 144, 12, 103, 4} + }, + { {59, 153, 72, 245, 11}, + {110, 26, 183, 77, 10}, + {218, 241, 41, 157, 12}, + {91, 46, 213, 135, 6} + }, + { {54, 150, 213, 148, 6}, + {248, 153, 248, 49, 2}, + {98, 154, 182, 150, 12}, + {72, 193, 249, 145, 15} + }, + { {81, 34, 220, 136, 12}, + {33, 169, 77, 208, 2}, + {49, 19, 180, 72, 10}, + {64, 187, 41, 88, 4} + }, + { {168, 69, 58, 219, 0}, + {2, 102, 164, 62, 7}, + {13, 181, 202, 33, 5}, + {231, 194, 86, 100, 0} + }, + { {180, 120, 208, 7, 12}, + {233, 140, 97, 22, 9}, + {62, 0, 177, 226, 13}, + {150, 136, 99, 25, 7} + }, + { {213, 175, 225, 90, 1}, + {181, 170, 152, 234, 13}, + {133, 168, 127, 90, 11}, + {181, 113, 149, 90, 13} + }, + { {4, 255, 169, 156, 13}, + {221, 166, 221, 32, 14}, + {179, 153, 95, 242, 0}, + {112, 75, 182, 91, 11} + }, + { {178, 170, 134, 106, 1}, + {5, 233, 58, 11, 9}, + {133, 102, 21, 84, 13}, + {157, 5, 201, 122, 0} + }, + { {33, 202, 57, 14, 7}, + {28, 52, 124, 80, 13}, + {231, 9, 197, 56, 4}, + {176, 163, 226, 195, 8} + }, + { {217, 149, 56, 136, 0}, + {66, 42, 20, 242, 6}, + {1, 17, 202, 153, 11}, + {100, 242, 133, 68, 2} + }, + { {158, 66, 134, 121, 2}, + {130, 253, 138, 15, 0}, + {73, 230, 20, 39, 9}, + {15, 5, 27, 244, 1} + }, + { {35, 126, 230, 40, 15}, + {101, 245, 107, 97, 12}, + {241, 70, 119, 236, 4}, + {56, 109, 106, 250, 6} + }, + { {223, 217, 194, 111, 1}, + {238, 238, 18, 207, 9}, + {143, 100, 57, 191, 11}, + {159, 52, 135, 119, 7} + }, + { {134, 37, 95, 146, 14}, + {177, 83, 197, 51, 3}, + {116, 159, 170, 70, 1}, + {204, 202, 60, 168, 13} + }, + { {89, 24, 159, 35, 8}, + {82, 201, 7, 212, 9}, + {28, 79, 145, 137, 10}, + {146, 190, 9, 52, 10} + }, + { {83, 168, 9, 113, 7}, + {21, 24, 214, 205, 8}, + {232, 233, 1, 92, 10}, + {27, 54, 177, 138, 8} + }, + { {8, 171, 22, 118, 15}, + {15, 83, 219, 24, 9}, + {246, 230, 141, 81, 0}, + {145, 141, 188, 175, 0} + }, + { {249, 140, 152, 28, 13}, + {14, 168, 245, 242, 8}, + {179, 129, 147, 25, 15}, + {20, 250, 241, 87, 0} + }, + { {249, 136, 239, 149, 11}, + {62, 217, 181, 198, 14}, + {218, 159, 113, 25, 15}, + {118, 58, 217, 183, 12} + }, + { {31, 2, 254, 30, 14}, + {170, 249, 205, 81, 5}, + {119, 135, 244, 15, 8}, + {168, 171, 57, 245, 5} + }, + { {104, 234, 254, 163, 13}, + {39, 197, 127, 148, 15}, + {188, 87, 245, 113, 6}, + {242, 159, 234, 62, 4} + }, + { {126, 145, 79, 240, 6}, + {242, 91, 246, 137, 2}, + {96, 255, 40, 151, 14}, + {73, 22, 253, 164, 15} + }, + { {44, 149, 20, 92, 13}, + {206, 35, 241, 56, 0}, + {179, 162, 138, 147, 4}, + {1, 200, 252, 71, 3} + }, + { {62, 147, 44, 208, 8}, + {194, 11, 189, 9, 6}, + {16, 179, 76, 151, 12}, + {105, 11, 221, 4, 3} + }, + { {183, 92, 175, 40, 3}, + {212, 253, 38, 99, 12}, + {193, 79, 83, 174, 13}, + {60, 102, 75, 242, 11} + }, + { {186, 245, 114, 60, 11}, + {111, 126, 179, 51, 4}, + {211, 196, 234, 245, 13}, + {44, 204, 215, 239, 6} + }, + { {121, 203, 18, 140, 13}, + {14, 110, 121, 208, 10}, + {179, 20, 141, 57, 14}, + {80, 185, 231, 103, 0} + }, + { {64, 87, 46, 30, 0}, + {72, 103, 140, 160, 5}, + {7, 135, 78, 160, 2}, + {160, 83, 30, 97, 2} + }, + { {82, 212, 150, 97, 15}, + {68, 221, 83, 189, 0}, + {248, 102, 146, 180, 10}, + {11, 220, 171, 178, 2} + }, + { {177, 112, 92, 13, 10}, + {105, 61, 37, 86, 0}, + {91, 3, 160, 232, 13}, + {6, 170, 75, 201, 6} + }, + { {99, 49, 219, 103, 3}, + {125, 210, 38, 221, 1}, + {206, 109, 184, 204, 6}, + {139, 182, 68, 187, 14} + }, + { {16, 154, 188, 196, 1}, + {76, 137, 28, 24, 14}, + {130, 51, 213, 144, 8}, + {113, 131, 137, 19, 2} + }, + { {96, 60, 124, 104, 15}, + {101, 49, 103, 184, 12}, + {241, 99, 227, 192, 6}, + {49, 222, 104, 202, 6} + }, + { {115, 50, 161, 255, 11}, + {93, 184, 171, 205, 7}, + {223, 248, 84, 204, 14}, + {235, 61, 81, 219, 10} + }, + { {61, 143, 96, 230, 4}, + {170, 10, 122, 104, 15}, + {38, 112, 111, 27, 12}, + {241, 101, 229, 5, 5} + }, + { {202, 97, 172, 50, 9}, + {7, 135, 135, 131, 5}, + {148, 195, 88, 101, 3}, + {172, 30, 30, 30, 0} + }, + { {132, 25, 50, 28, 14}, + {200, 114, 193, 18, 12}, + {115, 132, 201, 130, 1}, + {52, 136, 52, 225, 3} + }, + { {171, 167, 29, 138, 7}, + {23, 51, 124, 115, 3}, + {229, 27, 142, 93, 5}, + {204, 227, 236, 206, 8} + }, + { {220, 105, 76, 91, 7}, + {167, 63, 196, 142, 9}, + {237, 163, 41, 99, 11}, + {151, 18, 63, 206, 5} + }, + { {244, 156, 252, 224, 5}, + {228, 137, 118, 186, 14}, + {160, 115, 243, 146, 15}, + {117, 214, 233, 18, 7} + }, + { {200, 69, 243, 60, 3}, + {62, 246, 130, 178, 4}, + {195, 204, 250, 33, 3}, + {36, 212, 22, 247, 12} + }, + { {104, 74, 251, 82, 12}, + {50, 196, 237, 152, 13}, + {52, 173, 245, 33, 6}, + {177, 155, 114, 52, 12} + }, + { {214, 20, 91, 29, 3}, + {252, 120, 132, 183, 0}, + {203, 141, 162, 134, 11}, + {14, 210, 17, 227, 15} + }, + { {16, 253, 223, 173, 12}, + {121, 239, 87, 52, 10}, + {59, 95, 187, 240, 8}, + {82, 206, 175, 121, 14} + }, + { {31, 139, 72, 20, 8}, + {170, 10, 157, 65, 8}, + {18, 129, 45, 31, 8}, + {24, 43, 149, 5, 5} + }, + { {156, 50, 163, 233, 3}, + {215, 248, 10, 14, 6}, + {201, 124, 84, 195, 9}, + {103, 5, 1, 254, 11} + }, + { {157, 108, 124, 56, 7}, + {167, 61, 198, 114, 12}, + {225, 195, 227, 107, 9}, + {52, 230, 59, 206, 5} + }, + { {183, 3, 177, 221, 5}, + {156, 170, 232, 95, 6}, + {171, 184, 220, 14, 13}, + {111, 161, 117, 83, 9} + }, + { {111, 221, 59, 152, 0}, + {210, 102, 180, 241, 14}, + {1, 157, 203, 191, 6}, + {120, 242, 214, 100, 11} + }, + { {178, 62, 176, 218, 4}, + {65, 168, 232, 59, 15}, + {37, 176, 215, 196, 13}, + {253, 193, 113, 88, 2} + }, + { {115, 173, 103, 162, 0}, + {49, 75, 50, 225, 15}, + {4, 94, 107, 92, 14}, + {248, 116, 205, 40, 12} + }, + { {83, 97, 3, 190, 8}, + {25, 110, 131, 193, 3}, + {23, 220, 8, 108, 10}, + {200, 60, 23, 105, 8} + }, + { {214, 122, 66, 252, 3}, + {237, 124, 138, 139, 10}, + {195, 244, 37, 230, 11}, + {93, 21, 19, 235, 7} + }, + { {31, 40, 119, 191, 6}, + {187, 121, 194, 85, 15}, + {111, 222, 225, 79, 8}, + {250, 164, 57, 237, 13} + }, + { {45, 103, 170, 137, 12}, + {131, 230, 109, 100, 6}, + {57, 21, 94, 107, 4}, + {98, 107, 102, 124, 1} + }, + { {168, 55, 137, 133, 0}, + {91, 130, 44, 38, 2}, + {10, 25, 30, 193, 5}, + {70, 67, 68, 29, 10} + }, + { {47, 235, 63, 25, 12}, + {147, 103, 253, 85, 12}, + {57, 143, 205, 127, 4}, + {58, 171, 254, 108, 9} + }, + { {41, 160, 212, 118, 5}, + {47, 129, 242, 88, 1}, + {166, 226, 176, 89, 4}, + {129, 164, 248, 31, 4} + }, + { {65, 177, 230, 167, 15}, + {109, 211, 83, 196, 7}, + {254, 86, 120, 216, 2}, + {226, 60, 172, 187, 6} + }, + { {248, 93, 126, 140, 7}, + {110, 127, 100, 178, 14}, + {227, 23, 235, 161, 15}, + {116, 210, 111, 231, 6} + }, + { {133, 48, 143, 35, 4}, + {209, 193, 70, 70, 1}, + {44, 79, 16, 202, 1}, + {134, 38, 40, 56, 11} + }, + { {121, 131, 156, 71, 5}, + {14, 139, 124, 220, 1}, + {174, 35, 156, 25, 14}, + {131, 179, 237, 23, 0} + }, + { {75, 124, 102, 113, 8}, + {99, 69, 131, 237, 12}, + {24, 230, 99, 237, 2}, + {59, 124, 26, 44, 6} + }, + { {231, 237, 2, 5, 2}, + {137, 86, 48, 231, 8}, + {74, 4, 11, 126, 7}, + {30, 112, 198, 169, 1} + }, + { {57, 33, 5, 61, 13}, + {31, 43, 227, 68, 0}, + {187, 202, 8, 73, 12}, + {2, 44, 125, 79, 8} + }, + { {46, 92, 218, 7, 3}, + {238, 212, 36, 53, 9}, + {206, 5, 179, 167, 4}, + {154, 194, 66, 183, 7} + }, + { {191, 65, 151, 115, 14}, + {146, 223, 227, 95, 1}, + {124, 238, 152, 47, 13}, + {143, 172, 127, 180, 9} + }, + { {229, 242, 167, 113, 14}, + {209, 213, 251, 206, 4}, + {120, 238, 84, 250, 7}, + {39, 61, 250, 184, 11} + }, + { {189, 13, 222, 45, 14}, + {170, 251, 103, 118, 8}, + {123, 71, 187, 11, 13}, + {22, 238, 109, 245, 5} + }, + { {104, 73, 143, 23, 14}, + {26, 215, 229, 132, 9}, + {126, 143, 25, 33, 6}, + {146, 26, 126, 181, 8} + }, + { {183, 56, 155, 230, 11}, + {221, 216, 39, 91, 11}, + {214, 125, 145, 206, 13}, + {221, 174, 65, 187, 11} + }, + { {41, 127, 247, 175, 7}, + {127, 247, 106, 116, 15}, + {239, 94, 255, 233, 4}, + {242, 229, 110, 255, 14} + }, + { {177, 201, 139, 131, 7}, + {20, 222, 116, 70, 11}, + {236, 29, 25, 56, 13}, + {214, 34, 231, 178, 8} + }, + { {112, 239, 122, 3, 2}, + {33, 94, 60, 180, 13}, + {76, 5, 239, 112, 14}, + {178, 211, 199, 168, 4} + }, + { {155, 66, 34, 76, 0}, + {10, 108, 8, 75, 4}, + {3, 36, 68, 45, 9}, + {45, 33, 3, 101, 0} + }, + { {45, 220, 24, 13, 15}, + {206, 52, 117, 116, 8}, + {251, 1, 131, 187, 4}, + {18, 234, 226, 199, 3} + }, + { {10, 189, 5, 237, 15}, + {95, 51, 83, 45, 10}, + {251, 122, 11, 213, 0}, + {91, 76, 172, 207, 10} + }, + { {225, 190, 133, 89, 1}, + {85, 161, 184, 238, 8}, + {137, 170, 23, 216, 7}, + {23, 113, 216, 90, 10} + }, + { {145, 183, 86, 114, 7}, + {101, 91, 218, 122, 1}, + {228, 230, 174, 216, 9}, + {133, 229, 189, 170, 6} + }, + { {45, 22, 243, 125, 11}, + {254, 240, 171, 124, 4}, + {219, 236, 246, 139, 4}, + {35, 237, 80, 247, 15} + }, + { {39, 176, 238, 193, 6}, + {225, 209, 116, 77, 6}, + {104, 55, 112, 222, 4}, + {107, 34, 232, 184, 7} + }, + { {108, 133, 130, 249, 13}, + {134, 226, 243, 172, 2}, + {185, 244, 26, 19, 6}, + {67, 92, 244, 118, 1} + }, + { {115, 83, 40, 110, 3}, + {76, 62, 46, 201, 5}, + {199, 97, 76, 172, 14}, + {169, 55, 71, 195, 2} + }, + { {97, 193, 148, 57, 1}, + {4, 167, 178, 212, 0}, + {137, 194, 152, 56, 6}, + {2, 180, 222, 82, 0} + }, + { {123, 144, 243, 154, 9}, + {118, 232, 177, 209, 7}, + {149, 156, 240, 157, 14}, + {232, 184, 209, 118, 14} + }, + { {199, 152, 208, 205, 5}, + {236, 160, 80, 223, 10}, + {171, 48, 177, 158, 3}, + {95, 176, 160, 83, 7} + }, + { {40, 109, 39, 77, 11}, + {31, 119, 33, 44, 12}, + {219, 46, 75, 97, 4}, + {51, 72, 78, 239, 8} + }, + { {217, 203, 12, 182, 4}, + {10, 15, 222, 194, 11}, + {38, 211, 13, 57, 11}, + {212, 55, 191, 5, 0} + }, + { {74, 39, 247, 51, 6}, + {51, 211, 202, 181, 5}, + {108, 206, 254, 69, 2}, + {170, 213, 60, 188, 12} + }, + { {58, 192, 163, 225, 10}, + {18, 220, 51, 13, 6}, + {88, 124, 80, 53, 12}, + {107, 12, 195, 180, 8} + }, + { {7, 221, 220, 229, 7}, + {236, 151, 86, 125, 10}, + {234, 115, 187, 190, 0}, + {91, 230, 174, 147, 7} + }, + { {136, 88, 155, 155, 11}, + {86, 244, 133, 22, 11}, + {221, 157, 145, 161, 1}, + {214, 138, 18, 246, 10} + }, + { {138, 23, 163, 27, 11}, + {86, 242, 137, 39, 5}, + {221, 140, 94, 133, 1}, + {174, 73, 20, 246, 10} + }, + { {238, 5, 82, 209, 7}, + {166, 82, 224, 191, 2}, + {232, 180, 170, 7, 7}, + {79, 208, 116, 166, 5} + }, + { {187, 95, 31, 51, 1}, + {86, 79, 174, 119, 9}, + {140, 207, 143, 173, 13}, + {158, 231, 95, 38, 10} + }, + { {208, 60, 187, 190, 2}, + {89, 248, 134, 178, 15}, + {71, 221, 211, 192, 11}, + {244, 214, 17, 249, 10} + }, + { {152, 244, 149, 172, 6}, + {91, 189, 82, 50, 2}, + {99, 90, 146, 241, 9}, + {68, 196, 171, 221, 10} + }, + { {197, 139, 251, 112, 15}, + {180, 210, 223, 218, 12}, + {240, 237, 253, 26, 3}, + {53, 191, 180, 178, 13} + }, + { {122, 253, 6, 239, 8}, + {75, 111, 51, 173, 11}, + {31, 118, 11, 245, 14}, + {219, 92, 207, 109, 2} + }, + { {81, 109, 127, 193, 5}, + {53, 79, 68, 252, 14}, + {168, 63, 235, 104, 10}, + {115, 242, 47, 42, 12} + }, + { {187, 149, 178, 239, 2}, + {74, 250, 50, 127, 7}, + {79, 116, 218, 157, 13}, + {239, 228, 197, 245, 2} + }, + { {74, 73, 174, 244, 10}, + {10, 215, 135, 137, 14}, + {82, 247, 89, 37, 2}, + {121, 30, 30, 181, 0} + }, + { {121, 22, 119, 209, 9}, + {118, 73, 169, 252, 6}, + {152, 190, 230, 137, 14}, + {99, 249, 89, 38, 14} + }, + { {171, 195, 19, 250, 3}, + {22, 118, 186, 91, 3}, + {197, 252, 140, 61, 5}, + {205, 165, 214, 230, 8} + }, + { {97, 72, 188, 236, 1}, + {12, 165, 38, 216, 14}, + {131, 115, 209, 40, 6}, + {113, 182, 74, 83, 0} + }, + { {189, 91, 56, 250, 15}, + {198, 62, 239, 90, 15}, + {245, 241, 205, 171, 13}, + {245, 175, 119, 198, 3} + }, + { {167, 203, 22, 91, 5}, + {132, 103, 248, 95, 9}, + {173, 166, 141, 62, 5}, + {159, 161, 254, 98, 1} + }, + { {122, 215, 157, 221, 12}, + {90, 175, 253, 189, 2}, + {59, 187, 158, 181, 14}, + {75, 219, 255, 85, 10} + }, + { {30, 33, 123, 48, 10}, + {179, 90, 135, 17, 4}, + {80, 205, 232, 71, 8}, + {40, 142, 21, 172, 13} + }, + { {7, 106, 159, 128, 15}, + {149, 213, 77, 81, 10}, + {240, 31, 149, 110, 0}, + {88, 171, 42, 186, 9} + }, + { {66, 20, 244, 73, 5}, + {100, 161, 64, 189, 4}, + {169, 34, 242, 132, 2}, + {43, 208, 40, 82, 6} + }, + { {90, 77, 21, 204, 4}, + {26, 47, 64, 185, 10}, + {35, 58, 139, 37, 10}, + {89, 208, 47, 69, 8} + }, + { {42, 237, 24, 136, 14}, + {3, 54, 117, 49, 10}, + {113, 17, 139, 117, 4}, + {88, 202, 230, 204, 0} + }, + { {74, 162, 225, 254, 13}, + {63, 160, 219, 137, 7}, + {183, 248, 116, 85, 2}, + {233, 29, 176, 95, 12} + }, + { {132, 142, 174, 178, 2}, + {128, 209, 158, 34, 15}, + {68, 215, 87, 18, 1}, + {244, 71, 152, 176, 1} + }, + { {51, 40, 220, 225, 13}, + {37, 137, 103, 93, 10}, + {184, 115, 177, 76, 12}, + {91, 174, 105, 26, 4} + }, + { {58, 219, 131, 61, 10}, + {90, 254, 187, 5, 8}, + {91, 204, 29, 181, 12}, + {26, 13, 215, 245, 10} + }, + { {115, 169, 29, 159, 13}, + {29, 43, 245, 213, 11}, + {191, 155, 137, 92, 14}, + {218, 186, 253, 75, 8} + }, + { {4, 109, 218, 103, 10}, + {169, 214, 7, 60, 9}, + {94, 101, 187, 98, 0}, + {147, 206, 6, 185, 5} + }, + { {58, 203, 120, 189, 13}, + {46, 46, 255, 21, 14}, + {187, 209, 237, 53, 12}, + {122, 143, 247, 71, 4} + }, + { {206, 153, 231, 26, 12}, + {242, 227, 209, 131, 13}, + {53, 142, 121, 151, 3}, + {188, 24, 188, 116, 15} + }, + { {165, 37, 165, 84, 6}, + {153, 147, 224, 106, 4}, + {98, 170, 90, 74, 5}, + {37, 96, 124, 153, 9} + }, + { {240, 63, 168, 232, 1}, + {69, 170, 46, 170, 14}, + {129, 113, 95, 192, 15}, + {117, 87, 69, 90, 2} + }, + { {118, 229, 192, 251, 1}, + {165, 174, 178, 173, 3}, + {141, 240, 58, 118, 14}, + {203, 84, 215, 90, 5} + }, + { {227, 99, 237, 110, 12}, + {57, 167, 111, 203, 5}, + {55, 107, 124, 108, 7}, + {173, 63, 110, 89, 12} + }, + { {116, 249, 37, 224, 5}, + {213, 15, 114, 136, 14}, + {160, 122, 73, 242, 14}, + {113, 20, 239, 10, 11} + }, + { {198, 217, 168, 122, 2}, + {192, 182, 150, 139, 13}, + {69, 225, 89, 182, 3}, + {189, 22, 150, 208, 3} + }, + { {13, 96, 234, 117, 11}, + {175, 212, 135, 76, 4}, + {218, 229, 112, 107, 0}, + {35, 46, 18, 191, 5} + }, + { {51, 227, 74, 122, 13}, + {37, 110, 255, 73, 1}, + {181, 229, 44, 124, 12}, + {137, 47, 247, 106, 4} + }, + { {113, 122, 201, 96, 4}, + {113, 140, 110, 200, 8}, + {32, 105, 53, 232, 14}, + {17, 55, 99, 24, 14} + }, + { {152, 138, 48, 249, 12}, + {2, 40, 219, 30, 14}, + {57, 240, 197, 17, 9}, + {119, 141, 177, 68, 0} + }, + { {46, 163, 26, 179, 12}, + {131, 66, 255, 21, 3}, + {60, 213, 140, 87, 4}, + {202, 143, 244, 44, 1} + }, + { {172, 182, 25, 142, 10}, + {219, 48, 61, 50, 3}, + {87, 25, 134, 211, 5}, + {196, 203, 192, 205, 11} + }, + { {240, 101, 182, 107, 5}, + {5, 239, 98, 190, 5}, + {173, 102, 218, 96, 15}, + {167, 212, 111, 122, 0} + }, + { {168, 198, 88, 212, 0}, + {42, 4, 188, 58, 2}, + {2, 177, 166, 49, 5}, + {69, 195, 210, 5, 4} + }, + { {204, 173, 140, 128, 11}, + {135, 147, 21, 162, 10}, + {208, 19, 27, 83, 3}, + {84, 90, 140, 158, 1} + }, + { {163, 169, 133, 130, 14}, + {17, 147, 113, 67, 11}, + {116, 26, 25, 92, 5}, + {220, 40, 236, 152, 8} + }, + { {236, 44, 102, 29, 0}, + {171, 97, 160, 166, 12}, + {11, 134, 99, 67, 7}, + {54, 80, 88, 109, 5} + }, + { {25, 228, 167, 179, 8}, + {19, 205, 147, 100, 7}, + {28, 222, 82, 121, 8}, + {226, 108, 155, 60, 8} + }, + { {141, 26, 103, 29, 6}, + {250, 113, 200, 70, 12}, + {107, 142, 101, 139, 1}, + {54, 33, 56, 229, 15} + }, + { {178, 43, 3, 194, 13}, + {21, 74, 105, 11, 11}, + {180, 60, 13, 68, 13}, + {221, 9, 101, 42, 8} + }, + { {233, 107, 129, 42, 2}, + {19, 182, 42, 194, 9}, + {69, 72, 29, 105, 7}, + {148, 53, 70, 220, 8} + }, + { {175, 188, 151, 185, 4}, + {211, 225, 242, 119, 10}, + {41, 222, 147, 223, 5}, + {94, 228, 248, 124, 11} + }, + { {46, 29, 146, 160, 8}, + {194, 194, 35, 49, 10}, + {16, 84, 155, 135, 4}, + {88, 204, 68, 52, 3} + }, + { {20, 26, 214, 160, 3}, + {228, 217, 10, 16, 10}, + {192, 86, 181, 130, 8}, + {80, 133, 9, 178, 7} + }, + { {181, 12, 21, 252, 0}, + {152, 41, 162, 122, 10}, + {3, 250, 131, 10, 13}, + {85, 228, 89, 65, 9} + }, + { {95, 40, 182, 146, 9}, + {135, 201, 129, 209, 15}, + {148, 150, 209, 79, 10}, + {248, 184, 25, 62, 1} + }, + { {5, 73, 160, 42, 15}, + {132, 182, 67, 64, 13}, + {245, 64, 89, 42, 0}, + {176, 44, 38, 210, 1} + }, + { {170, 29, 19, 140, 15}, + {94, 114, 97, 51, 10}, + {243, 28, 139, 133, 5}, + {92, 200, 100, 231, 10} + }, + { {214, 197, 212, 242, 6}, + {160, 159, 210, 187, 3}, + {100, 242, 186, 54, 11}, + {205, 212, 191, 144, 5} + }, + { {175, 39, 99, 231, 15}, + {191, 82, 107, 111, 7}, + {254, 124, 110, 79, 5}, + {239, 109, 100, 175, 13} + }, + { {13, 30, 84, 39, 10}, + {234, 17, 11, 116, 9}, + {94, 66, 167, 139, 0}, + {146, 237, 8, 133, 7} + }, + { {105, 13, 29, 67, 9}, + {22, 3, 37, 252, 9}, + {156, 43, 139, 9, 6}, + {147, 250, 76, 6, 8} + }, + { {142, 118, 82, 211, 11}, + {231, 84, 137, 63, 3}, + {220, 180, 166, 231, 1}, + {207, 201, 18, 174, 7} + }, + { {183, 231, 13, 198, 11}, + {157, 31, 61, 107, 3}, + {214, 59, 14, 126, 13}, + {205, 107, 207, 139, 9} + }, + { {91, 61, 61, 135, 11}, + {95, 27, 5, 245, 15}, + {222, 27, 203, 205, 10}, + {250, 250, 13, 143, 10} + }, + { {79, 134, 142, 113, 6}, + {130, 209, 222, 237, 0}, + {104, 231, 22, 31, 2}, + {11, 119, 184, 180, 1} + }, + { {177, 31, 187, 167, 12}, + {88, 202, 111, 118, 15}, + {62, 93, 223, 136, 13}, + {246, 239, 101, 49, 10} + }, + { {128, 174, 197, 152, 2}, + {49, 177, 152, 34, 10}, + {65, 154, 55, 80, 1}, + {84, 65, 152, 216, 12} + }, + { {213, 231, 198, 181, 15}, + {173, 223, 219, 230, 2}, + {250, 214, 62, 122, 11}, + {70, 125, 191, 187, 5} + }, + { {93, 45, 129, 118, 3}, + {159, 154, 130, 232, 9}, + {198, 232, 27, 75, 10}, + {145, 116, 21, 159, 9} + }, + { {203, 168, 201, 76, 1}, + {63, 160, 20, 203, 8}, + {131, 41, 49, 93, 3}, + {29, 50, 128, 95, 12} + }, + { {103, 230, 75, 17, 3}, + {181, 84, 188, 229, 0}, + {200, 141, 38, 126, 6}, + {10, 115, 210, 170, 13} + }, + { {250, 67, 80, 92, 14}, + {42, 62, 233, 155, 0}, + {115, 160, 172, 37, 15}, + {13, 153, 119, 197, 4} + }, + { {240, 150, 174, 222, 7}, + {76, 249, 252, 170, 7}, + {231, 183, 86, 144, 15}, + {229, 83, 249, 243, 2} + }, + { {71, 218, 157, 211, 11}, + {212, 149, 157, 221, 11}, + {220, 187, 149, 190, 2}, + {219, 187, 154, 146, 11} + }, + { {154, 5, 75, 133, 14}, + {58, 90, 69, 39, 2}, + {122, 29, 42, 5, 9}, + {78, 74, 37, 165, 12} + }, + { {120, 108, 53, 241, 11}, + {23, 29, 163, 188, 14}, + {216, 250, 195, 97, 14}, + {115, 220, 91, 142, 8} + }, + { {147, 175, 244, 140, 6}, + {41, 187, 88, 115, 14}, + {99, 18, 255, 92, 9}, + {124, 225, 173, 217, 4} + }, + { {42, 176, 175, 52, 11}, + {95, 209, 183, 1, 4}, + {210, 207, 80, 213, 4}, + {40, 14, 216, 191, 10} + }, + { {240, 11, 250, 99, 1}, + {36, 202, 46, 158, 13}, + {140, 101, 253, 0, 15}, + {183, 151, 69, 50, 4} + }, + { {76, 166, 198, 133, 5}, + {175, 193, 88, 164, 2}, + {170, 22, 54, 83, 2}, + {66, 81, 168, 63, 5} + }, + { {186, 20, 227, 14, 1}, + {126, 232, 32, 35, 5}, + {135, 12, 114, 133, 13}, + {172, 64, 65, 119, 14} + }, + { {33, 208, 242, 145, 8}, + {96, 196, 177, 84, 6}, + {24, 148, 240, 184, 4}, + {98, 168, 210, 48, 6} + }, + { {195, 163, 48, 100, 2}, + {9, 18, 26, 219, 4}, + {66, 96, 204, 92, 3}, + {45, 181, 132, 137, 0} + }, + { {55, 157, 233, 153, 7}, + {244, 186, 244, 101, 14}, + {233, 153, 123, 158, 12}, + {122, 98, 245, 210, 15} + }, + { {34, 132, 160, 94, 1}, + {12, 160, 176, 41, 5}, + {135, 160, 82, 20, 4}, + {169, 64, 208, 83, 0} + }, + { {111, 213, 214, 14, 1}, + {238, 231, 48, 241, 1}, + {135, 6, 186, 191, 6}, + {136, 240, 206, 119, 7} + }, + { {203, 84, 111, 171, 13}, + {118, 101, 71, 231, 7}, + {189, 95, 98, 173, 3}, + {238, 126, 42, 102, 14} + }, + { {156, 42, 145, 229, 8}, + {155, 136, 11, 30, 10}, + {26, 120, 149, 67, 9}, + {87, 141, 1, 29, 9} + }, + { {61, 210, 148, 126, 15}, + {206, 189, 251, 88, 1}, + {247, 226, 148, 187, 12}, + {129, 173, 251, 215, 3} + }, + { {100, 83, 228, 51, 13}, + {228, 135, 235, 132, 5}, + {188, 194, 124, 162, 6}, + {162, 29, 126, 18, 7} + }, + { {43, 198, 86, 24, 12}, + {34, 101, 249, 113, 0}, + {49, 134, 166, 61, 4}, + {8, 233, 250, 100, 4} + }, + { {37, 39, 183, 207, 14}, + {153, 243, 105, 124, 7}, + {127, 62, 222, 74, 4}, + {227, 233, 108, 249, 9} + }, + { {22, 36, 199, 38, 13}, + {189, 201, 67, 33, 1}, + {182, 78, 50, 70, 8}, + {136, 76, 41, 59, 13} + }, + { {202, 176, 164, 248, 14}, + {67, 177, 211, 139, 6}, + {113, 242, 80, 213, 3}, + {109, 28, 184, 220, 2} + }, + { {193, 236, 138, 99, 14}, + {1, 212, 87, 238, 9}, + {124, 101, 19, 120, 3}, + {151, 126, 162, 184, 0} + }, + { {82, 142, 30, 232, 13}, + {4, 105, 95, 185, 10}, + {177, 119, 135, 20, 10}, + {89, 223, 169, 98, 0} + }, + { {254, 163, 79, 210, 9}, + {183, 75, 189, 139, 3}, + {148, 191, 44, 87, 15}, + {205, 27, 221, 46, 13} + }, + { {92, 145, 249, 82, 6}, + {242, 154, 212, 152, 5}, + {100, 169, 248, 147, 10}, + {161, 146, 181, 148, 15} + }, + { {42, 103, 218, 12, 8}, + {43, 230, 45, 49, 0}, + {19, 5, 190, 101, 4}, + {8, 203, 70, 125, 4} + }, + { {111, 147, 7, 201, 2}, + {210, 115, 56, 205, 2}, + {73, 62, 12, 159, 6}, + {75, 49, 204, 228, 11} + }, + { {27, 127, 101, 218, 0}, + {115, 47, 136, 105, 15}, + {5, 186, 111, 237, 8}, + {249, 97, 31, 76, 14} + }, + { {96, 94, 48, 82, 3}, + {68, 20, 168, 184, 13}, + {196, 160, 199, 160, 6}, + {177, 209, 82, 130, 2} + }, + { {187, 3, 116, 51, 0}, + {34, 11, 170, 87, 5}, + {12, 194, 236, 13, 13}, + {174, 165, 93, 4, 4} + }, + { {245, 150, 230, 89, 11}, + {228, 249, 185, 238, 4}, + {217, 166, 118, 154, 15}, + {39, 121, 217, 242, 7} + }, + { {146, 81, 227, 36, 14}, + {120, 222, 67, 3, 4}, + {114, 76, 120, 164, 9}, + {44, 12, 39, 177, 14} + }, + { {37, 129, 147, 161, 3}, + {148, 210, 50, 84, 2}, + {200, 92, 152, 26, 4}, + {66, 164, 196, 178, 9} + }, + { {179, 35, 202, 164, 8}, + {41, 202, 47, 67, 2}, + {18, 85, 60, 76, 13}, + {76, 47, 69, 57, 4} + }, + { {141, 73, 29, 60, 1}, + {158, 39, 134, 82, 8}, + {131, 203, 137, 43, 1}, + {20, 166, 30, 71, 9} + }, + { {227, 51, 36, 195, 13}, + {69, 3, 105, 207, 7}, + {188, 50, 76, 204, 7}, + {239, 57, 108, 10, 2} + }, + { {184, 54, 252, 47, 7}, + {111, 185, 110, 54, 5}, + {239, 67, 246, 193, 13}, + {166, 199, 105, 223, 6} + }, + { {237, 209, 46, 107, 4}, + {194, 103, 118, 206, 5}, + {45, 103, 72, 187, 7}, + {167, 54, 238, 100, 3} + }, + { {235, 71, 59, 247, 10}, + {26, 86, 175, 255, 7}, + {94, 253, 206, 45, 7}, + {239, 255, 86, 165, 8} + }, + { {20, 242, 111, 82, 9}, + {245, 77, 157, 8, 5}, + {148, 175, 100, 242, 8}, + {161, 11, 155, 42, 15} + }, + { {205, 127, 26, 185, 15}, + {199, 118, 207, 246, 10}, + {249, 213, 143, 235, 3}, + {86, 255, 54, 238, 3} + }, + { {198, 53, 226, 175, 3}, + {237, 242, 2, 167, 7}, + {207, 84, 122, 198, 3}, + {238, 84, 4, 251, 7} + }, + { {215, 249, 12, 215, 3}, + {205, 31, 148, 207, 11}, + {206, 179, 9, 254, 11}, + {223, 50, 159, 139, 3} + }, + { {83, 21, 215, 208, 12}, + {112, 203, 193, 249, 2}, + {48, 190, 186, 140, 10}, + {73, 248, 61, 48, 14} + }, + { {86, 18, 116, 213, 1}, + {236, 9, 136, 157, 6}, + {138, 178, 228, 134, 10}, + {107, 145, 25, 3, 7} + }, + { {181, 26, 186, 149, 2}, + {200, 216, 172, 86, 14}, + {74, 149, 213, 138, 13}, + {118, 163, 81, 177, 3} + }, + { {98, 62, 7, 58, 4}, + {81, 97, 234, 161, 9}, + {37, 206, 7, 196, 6}, + {152, 85, 120, 104, 10} + }, + { {235, 252, 157, 72, 14}, + {83, 181, 117, 251, 8}, + {113, 43, 147, 253, 7}, + {29, 250, 234, 220, 10} + }, + { {129, 51, 86, 47, 14}, + {105, 115, 75, 86, 1}, + {127, 70, 172, 200, 1}, + {134, 173, 44, 233, 6} + }, + { {125, 43, 146, 2, 6}, + {131, 218, 104, 208, 9}, + {100, 4, 157, 75, 14}, + {144, 177, 101, 188, 1} + }, + { {144, 85, 192, 202, 6}, + {96, 190, 64, 42, 3}, + {101, 48, 58, 160, 9}, + {197, 64, 39, 208, 6} + }, + { {205, 132, 181, 92, 15}, + {158, 177, 209, 250, 4}, + {243, 170, 210, 27, 3}, + {37, 248, 184, 215, 9} + }, + { {11, 54, 92, 9, 4}, + {99, 33, 76, 117, 0}, + {41, 3, 166, 205, 0}, + {10, 227, 40, 76, 6} + }, + { {190, 98, 7, 5, 10}, + {155, 93, 41, 7, 0}, + {90, 14, 4, 103, 13}, + {14, 9, 75, 173, 9} + }, + { {39, 38, 95, 35, 12}, + {177, 65, 111, 117, 1}, + {60, 79, 166, 78, 4}, + {138, 239, 104, 40, 13} + }, + { {100, 138, 166, 99, 10}, + {128, 209, 59, 140, 13}, + {92, 102, 85, 18, 6}, + {179, 29, 200, 176, 1} + }, + { {254, 101, 69, 2, 13}, + {183, 15, 97, 163, 1}, + {180, 10, 42, 103, 15}, + {140, 88, 111, 14, 13} + }, + { {167, 99, 68, 48, 13}, + {165, 7, 235, 67, 0}, + {176, 194, 44, 110, 5}, + {12, 45, 126, 10, 5} + }, + { {217, 192, 126, 10, 8}, + {34, 109, 21, 210, 5}, + {21, 7, 224, 57, 11}, + {164, 186, 139, 100, 4} + }, + { {29, 104, 212, 20, 13}, + {175, 141, 193, 80, 8}, + {178, 130, 177, 107, 8}, + {16, 168, 59, 31, 5} + }, + { {203, 197, 40, 112, 3}, + {6, 22, 150, 235, 4}, + {192, 225, 74, 61, 3}, + {45, 118, 150, 134, 0} + }, + { {228, 40, 28, 188, 9}, + {141, 33, 167, 146, 10}, + {147, 211, 129, 66, 7}, + {84, 158, 88, 75, 1} + }, + { {225, 221, 170, 221, 6}, + {72, 246, 244, 238, 14}, + {107, 181, 91, 184, 7}, + {119, 114, 246, 241, 2} + }, + { {244, 239, 90, 230, 13}, + {173, 78, 127, 186, 11}, + {182, 117, 175, 114, 15}, + {213, 223, 231, 43, 5} + }, + { {122, 159, 45, 115, 15}, + {86, 27, 255, 173, 13}, + {252, 235, 79, 149, 14}, + {187, 95, 253, 134, 10} + }, + { {189, 215, 111, 86, 6}, + {250, 95, 252, 106, 5}, + {102, 175, 110, 187, 13}, + {165, 99, 255, 165, 15} + }, + { {79, 139, 34, 80, 4}, + {130, 66, 216, 201, 12}, + {32, 164, 77, 31, 2}, + {57, 49, 180, 36, 1} + }, + { {104, 156, 225, 61, 6}, + {122, 176, 242, 164, 12}, + {107, 200, 115, 145, 6}, + {50, 84, 240, 213, 14} + }, + { {23, 125, 226, 145, 13}, + {229, 206, 193, 101, 14}, + {184, 148, 123, 238, 8}, + {122, 104, 55, 58, 7} + }, + { {101, 27, 125, 109, 2}, + {248, 51, 46, 220, 12}, + {75, 107, 237, 138, 6}, + {51, 183, 76, 193, 15} + }, + { {230, 30, 244, 128, 14}, + {224, 145, 105, 179, 14}, + {112, 18, 247, 134, 7}, + {124, 217, 104, 144, 7} + }, + { {10, 131, 197, 183, 14}, + {58, 147, 219, 5, 3}, + {126, 218, 60, 21, 0}, + {202, 13, 188, 149, 12} + }, + { {113, 216, 247, 205, 5}, + {124, 237, 112, 220, 14}, + {171, 62, 241, 184, 14}, + {115, 176, 235, 115, 14} + }, + { {119, 102, 40, 235, 6}, + {129, 60, 110, 237, 7}, + {109, 113, 70, 110, 14}, + {235, 119, 99, 200, 1} + }, + { {164, 71, 35, 16, 2}, + {144, 86, 168, 34, 4}, + {64, 140, 78, 34, 5}, + {36, 65, 86, 160, 9} + }, + { {141, 28, 30, 136, 7}, + {198, 113, 68, 114, 10}, + {225, 23, 131, 139, 1}, + {84, 226, 40, 230, 3} + }, + { {174, 5, 207, 117, 0}, + {186, 195, 166, 47, 0}, + {10, 239, 58, 7, 5}, + {15, 70, 92, 53, 13} + }, + { {242, 185, 238, 5, 10}, + {105, 219, 53, 135, 12}, + {90, 7, 121, 212, 15}, + {62, 26, 205, 185, 6} + }, + { {150, 184, 220, 80, 14}, + {225, 153, 213, 27, 8}, + {112, 163, 177, 214, 9}, + {29, 138, 185, 152, 7} + }, + { {191, 140, 179, 54, 4}, + {154, 200, 242, 115, 13}, + {38, 204, 211, 31, 13}, + {188, 228, 241, 53, 9} + }, + { {77, 139, 178, 107, 7}, + {134, 242, 90, 220, 13}, + {237, 100, 221, 27, 2}, + {179, 181, 164, 246, 1} + }, + { {48, 111, 43, 129, 9}, + {21, 78, 45, 36, 14}, + {152, 29, 79, 96, 12}, + {114, 75, 71, 42, 8} + }, + { {134, 223, 39, 101, 6}, + {216, 87, 90, 47, 12}, + {106, 110, 79, 182, 1}, + {63, 69, 174, 161, 11} + }, + { {201, 63, 168, 244, 10}, + {75, 146, 143, 234, 14}, + {82, 241, 95, 201, 3}, + {117, 127, 20, 157, 2} + }, + { {178, 101, 205, 76, 0}, + {57, 175, 36, 43, 0}, + {3, 43, 58, 100, 13}, + {13, 66, 79, 89, 12} + }, + { {230, 107, 232, 211, 7}, + {165, 150, 236, 143, 15}, + {236, 177, 125, 102, 7}, + {255, 19, 118, 154, 5} + }, + { {172, 157, 252, 25, 5}, + {230, 163, 244, 54, 12}, + {169, 131, 251, 147, 5}, + {54, 194, 252, 86, 7} + }, + { {177, 19, 196, 190, 12}, + {104, 171, 235, 66, 3}, + {55, 210, 60, 136, 13}, + {196, 45, 125, 81, 6} + }, + { {211, 223, 9, 161, 13}, + {84, 14, 95, 231, 10}, + {184, 89, 15, 188, 11}, + {94, 127, 167, 2, 10} + }, + { {238, 69, 143, 67, 7}, + {150, 215, 100, 175, 1}, + {236, 47, 26, 39, 7}, + {143, 82, 110, 182, 9} + }, + { {103, 202, 12, 176, 9}, + {132, 5, 191, 193, 10}, + {144, 211, 5, 62, 6}, + {88, 63, 218, 2, 1} + }, + { {179, 49, 95, 108, 5}, + {125, 107, 102, 91, 0}, + {163, 111, 168, 204, 13}, + {13, 166, 109, 107, 14} + }, + { {51, 113, 11, 210, 2}, + {81, 94, 164, 73, 3}, + {68, 189, 8, 236, 12}, + {201, 34, 87, 168, 10} + }, + { {155, 233, 139, 184, 6}, + {19, 254, 214, 67, 10}, + {97, 221, 25, 125, 9}, + {92, 38, 183, 252, 8} + }, + { {65, 191, 62, 12, 5}, + {77, 99, 92, 240, 12}, + {163, 7, 207, 216, 2}, + {48, 243, 172, 107, 2} + }, + { {39, 180, 57, 11, 8}, + {209, 32, 53, 117, 5}, + {29, 9, 194, 222, 4}, + {170, 234, 192, 72, 11} + }, + { {22, 194, 235, 111, 10}, + {184, 252, 31, 13, 5}, + {95, 109, 116, 54, 8}, + {171, 15, 131, 241, 13} + }, + { {180, 216, 190, 76, 8}, + {200, 237, 53, 26, 12}, + {19, 39, 209, 178, 13}, + {53, 138, 203, 113, 3} + }, + { {157, 108, 51, 41, 10}, + {147, 124, 3, 118, 12}, + {89, 76, 195, 107, 9}, + {54, 236, 3, 236, 9} + }, + { {62, 93, 56, 87, 15}, + {206, 30, 229, 61, 13}, + {254, 161, 203, 167, 12}, + {187, 202, 119, 135, 3} + }, + { {180, 100, 140, 48, 10}, + {129, 157, 167, 34, 0}, + {80, 195, 18, 98, 13}, + {4, 78, 91, 152, 1} + }, + { {93, 101, 141, 153, 11}, + {151, 191, 133, 228, 2}, + {217, 155, 26, 107, 10}, + {66, 122, 31, 222, 9} + }, + { {75, 57, 30, 253, 8}, + {75, 99, 135, 221, 10}, + {27, 247, 137, 205, 2}, + {91, 190, 28, 109, 2} + }, + { {159, 102, 246, 244, 7}, + {175, 221, 202, 123, 6}, + {226, 246, 246, 111, 9}, + {109, 229, 59, 191, 5} + }, + { {99, 251, 122, 188, 8}, + {105, 102, 191, 209, 14}, + {19, 213, 237, 252, 6}, + {120, 191, 214, 105, 6} + }, + { {48, 213, 149, 195, 13}, + {84, 143, 113, 60, 3}, + {188, 58, 154, 176, 12}, + {195, 200, 239, 18, 10} + }, + { {44, 228, 218, 91, 8}, + {163, 228, 181, 60, 1}, + {29, 165, 178, 115, 4}, + {131, 202, 210, 124, 5} + }, + { {123, 79, 47, 122, 4}, + {18, 111, 238, 233, 13}, + {37, 239, 79, 45, 14}, + {185, 119, 127, 100, 8} + }, + { {80, 165, 170, 211, 11}, + {5, 218, 149, 172, 7}, + {220, 181, 90, 80, 10}, + {227, 90, 149, 186, 0} + }, + { {53, 99, 219, 99, 9}, + {181, 206, 47, 92, 1}, + {156, 109, 188, 106, 12}, + {131, 175, 71, 58, 13} + }, + { {202, 17, 193, 89, 1}, + {118, 162, 128, 143, 0}, + {137, 168, 56, 133, 3}, + {15, 16, 20, 86, 14} + }, + { {119, 153, 15, 22, 8}, + {216, 75, 181, 193, 9}, + {22, 143, 9, 158, 14}, + {152, 58, 221, 33, 11} + }, + { {6, 232, 6, 138, 11}, + {133, 117, 17, 1, 11}, + {213, 22, 1, 118, 0}, + {216, 8, 138, 234, 1} + }, + { {88, 242, 76, 249, 1}, + {103, 45, 158, 140, 2}, + {137, 243, 36, 241, 10}, + {67, 23, 155, 78, 6} + }, + { {87, 131, 120, 211, 4}, + {160, 10, 220, 221, 7}, + {44, 177, 236, 30, 10}, + {235, 179, 181, 0, 5} + }, + { {102, 199, 182, 12, 6}, + {136, 247, 120, 177, 4}, + {99, 6, 222, 54, 6}, + {40, 209, 238, 241, 1} + }, + { {120, 143, 69, 72, 0}, + {50, 43, 56, 168, 8}, + {1, 42, 47, 17, 14}, + {17, 81, 205, 68, 12} + }, + { {233, 239, 208, 58, 13}, + {39, 166, 251, 242, 9}, + {181, 192, 191, 121, 7}, + {148, 253, 246, 94, 4} + }, + { {39, 212, 15, 163, 10}, + {208, 85, 55, 101, 3}, + {92, 95, 2, 190, 4}, + {202, 110, 202, 160, 11} + }, + { {125, 42, 249, 128, 10}, + {179, 152, 45, 208, 14}, + {80, 25, 245, 75, 14}, + {112, 187, 65, 156, 13} + }, + { {86, 144, 82, 117, 6}, + {232, 88, 210, 157, 0}, + {106, 228, 160, 150, 10}, + {11, 148, 177, 161, 7} + }, + { {30, 225, 231, 61, 2}, + {187, 255, 146, 5, 4}, + {75, 206, 120, 119, 8}, + {42, 4, 159, 253, 13} + }, + { {254, 157, 166, 210, 9}, + {198, 203, 177, 171, 15}, + {148, 182, 91, 151, 15}, + {253, 88, 221, 54, 3} + }, + { {75, 222, 118, 252, 11}, + {110, 117, 155, 249, 14}, + {211, 246, 231, 189, 2}, + {121, 253, 154, 231, 6} + }, + { {50, 21, 75, 223, 8}, + {120, 106, 165, 45, 3}, + {31, 189, 42, 132, 12}, + {203, 74, 85, 97, 14} + }, + { {141, 94, 72, 11, 6}, + {226, 52, 76, 102, 9}, + {109, 1, 39, 171, 1}, + {150, 99, 34, 196, 7} + }, + { {85, 153, 228, 23, 12}, + {232, 139, 209, 196, 13}, + {62, 130, 121, 154, 10}, + {178, 56, 189, 17, 7} + }, + { {127, 178, 100, 131, 2}, + {227, 25, 56, 197, 7}, + {76, 18, 100, 223, 14}, + {234, 49, 201, 140, 7} + }, + { {6, 201, 57, 111, 3}, + {156, 54, 22, 29, 13}, + {207, 105, 201, 54, 0}, + {187, 134, 134, 195, 9} + }, + { {66, 202, 124, 45, 2}, + {40, 53, 30, 149, 12}, + {75, 67, 229, 52, 2}, + {58, 151, 138, 193, 4} + }, + { {78, 192, 248, 186, 12}, + {162, 164, 215, 145, 7}, + {53, 209, 240, 55, 2}, + {232, 158, 178, 84, 5} + }, + { {45, 113, 131, 30, 11}, + {223, 246, 161, 64, 1}, + {215, 140, 24, 235, 4}, + {128, 40, 86, 255, 11} + }, + { {128, 130, 234, 70, 4}, + {40, 192, 92, 10, 5}, + {38, 37, 116, 16, 1}, + {165, 3, 160, 49, 4} + }, + { {172, 206, 58, 52, 3}, + {142, 84, 190, 50, 12}, + {194, 197, 199, 51, 5}, + {52, 199, 210, 167, 1} + }, + { {88, 160, 32, 181, 13}, + {15, 8, 211, 132, 6}, + {186, 208, 64, 81, 10}, + {98, 28, 177, 15, 0} + }, + { {114, 159, 129, 35, 8}, + {80, 138, 59, 165, 9}, + {28, 72, 31, 148, 14}, + {154, 93, 197, 16, 10} + }, + { {123, 5, 90, 20, 12}, + {42, 74, 229, 241, 0}, + {50, 133, 170, 13, 14}, + {8, 250, 117, 37, 4} + }, + { {150, 248, 124, 233, 1}, + {229, 45, 22, 31, 14}, + {137, 115, 225, 246, 9}, + {127, 134, 139, 74, 7} + }, + { {41, 99, 28, 75, 10}, + {3, 55, 45, 92, 1}, + {93, 35, 140, 105, 4}, + {131, 171, 78, 204, 0} + }, + { {157, 226, 24, 237, 14}, + {139, 60, 95, 94, 2}, + {123, 113, 132, 123, 9}, + {71, 175, 163, 205, 1} + }, + { {58, 230, 175, 66, 4}, + {19, 205, 124, 41, 5}, + {36, 47, 86, 117, 12}, + {169, 67, 235, 60, 8} + }, + { {180, 56, 174, 163, 3}, + {197, 217, 38, 6, 15}, + {204, 87, 81, 194, 13}, + {246, 6, 73, 186, 3} + }, + { {208, 70, 61, 144, 7}, + {20, 29, 204, 178, 6}, + {224, 155, 198, 32, 11}, + {100, 211, 59, 130, 8} + }, + { {187, 32, 151, 39, 0}, + {27, 201, 34, 87, 1}, + {14, 78, 144, 77, 13}, + {142, 164, 73, 61, 8} + }, + { {229, 143, 37, 62, 0}, + {152, 35, 186, 226, 13}, + {7, 202, 79, 26, 7}, + {180, 117, 220, 65, 9} + }, + { {194, 117, 158, 53, 4}, + {73, 199, 198, 183, 0}, + {42, 199, 154, 228, 3}, + {14, 214, 62, 57, 2} + }, + { {10, 66, 188, 132, 7}, + {14, 149, 76, 17, 6}, + {226, 19, 212, 37, 0}, + {104, 131, 42, 151, 0} + }, + { {173, 67, 236, 130, 11}, + {166, 151, 45, 66, 7}, + {212, 19, 124, 43, 5}, + {228, 43, 78, 150, 5} + }, + { {61, 136, 115, 251, 11}, + {182, 120, 179, 92, 15}, + {221, 252, 225, 27, 12}, + {243, 172, 209, 230, 13} + }, + { {102, 161, 166, 184, 11}, + {133, 243, 179, 129, 6}, + {209, 214, 88, 86, 6}, + {104, 28, 220, 250, 1} + }, + { {221, 141, 175, 200, 4}, + {146, 235, 84, 234, 14}, + {33, 63, 91, 27, 11}, + {117, 114, 173, 116, 9} + }, + { {21, 7, 218, 203, 3}, + {164, 250, 12, 124, 3}, + {205, 53, 190, 10, 8}, + {195, 227, 5, 242, 5} + }, + { {137, 254, 190, 99, 3}, + {71, 213, 30, 126, 13}, + {204, 103, 215, 249, 1}, + {183, 231, 138, 190, 2} + }, + { {25, 7, 189, 144, 10}, + {18, 155, 141, 112, 6}, + {80, 155, 222, 9, 8}, + {96, 235, 29, 148, 8} + }, + { {98, 241, 188, 178, 14}, + {65, 151, 247, 145, 7}, + {116, 211, 216, 244, 6}, + {232, 158, 254, 152, 2} + }, + { {14, 147, 144, 200, 10}, + {194, 178, 25, 25, 2}, + {81, 48, 156, 151, 0}, + {73, 137, 132, 212, 3} + }, + { {102, 251, 212, 124, 10}, + {233, 183, 187, 153, 8}, + {83, 226, 189, 246, 6}, + {25, 157, 222, 217, 7} + }, + { {203, 127, 203, 186, 5}, + {119, 230, 206, 227, 11}, + {165, 221, 63, 237, 3}, + {220, 119, 54, 126, 14} + }, + { {72, 77, 150, 114, 5}, + {6, 199, 194, 184, 9}, + {164, 230, 155, 33, 2}, + {145, 212, 62, 54, 0} + }, + { {182, 201, 156, 184, 13}, + {132, 175, 247, 19, 10}, + {177, 211, 153, 54, 13}, + {92, 142, 255, 82, 1} + }, + { {214, 73, 19, 109, 6}, + {152, 126, 66, 159, 8}, + {107, 108, 137, 38, 11}, + {31, 148, 39, 225, 9} + }, + { {13, 229, 2, 166, 2}, + {139, 86, 18, 96, 3}, + {70, 84, 10, 123, 0}, + {192, 100, 134, 173, 1} + }, + { {249, 154, 61, 119, 4}, + {90, 9, 254, 222, 13}, + {46, 235, 197, 153, 15}, + {183, 183, 249, 5, 10} + }, + { {124, 20, 82, 167, 4}, + {234, 72, 98, 180, 3}, + {46, 84, 162, 131, 14}, + {194, 212, 97, 37, 7} + }, + { {57, 81, 76, 89, 15}, + {102, 63, 229, 76, 0}, + {249, 163, 40, 169, 12}, + {3, 42, 127, 198, 6} + }, + { {202, 63, 57, 87, 14}, + {91, 18, 205, 191, 13}, + {126, 169, 207, 197, 3}, + {191, 219, 52, 141, 10} + }, + { {219, 212, 95, 243, 10}, + {114, 93, 151, 255, 3}, + {92, 255, 162, 189, 11}, + {207, 254, 155, 164, 14} + }, + { {113, 246, 139, 38, 2}, + {89, 220, 62, 224, 1}, + {70, 77, 22, 248, 14}, + {128, 119, 195, 185, 10} + }, + { {61, 34, 41, 13, 7}, + {159, 56, 108, 68, 4}, + {235, 9, 68, 75, 12}, + {34, 35, 97, 207, 9} + }, + { {154, 77, 130, 207, 12}, + {10, 238, 65, 47, 11}, + {63, 52, 27, 37, 9}, + {223, 72, 39, 117, 0} + }, + { {16, 141, 72, 139, 2}, + {32, 58, 20, 36, 11}, + {77, 17, 43, 16, 8}, + {210, 66, 133, 192, 4} + }, + { {66, 66, 157, 48, 14}, + {16, 149, 207, 145, 0}, + {112, 203, 148, 36, 2}, + {8, 159, 58, 144, 8} + }, + { {191, 242, 183, 46, 9}, + {223, 237, 59, 83, 5}, + {151, 78, 212, 255, 13}, + {172, 173, 203, 127, 11} + }, + { {137, 69, 178, 16, 8}, + {2, 198, 129, 114, 4}, + {16, 132, 218, 41, 1}, + {36, 232, 22, 52, 0} + }, + { {156, 83, 206, 106, 1}, + {230, 239, 14, 10, 1}, + {133, 103, 60, 163, 9}, + {133, 7, 15, 118, 7} + }, + { {246, 110, 46, 178, 11}, + {133, 93, 175, 163, 15}, + {212, 215, 71, 102, 15}, + {252, 95, 91, 170, 1} + }, + { {123, 135, 22, 82, 6}, + {2, 91, 248, 249, 1}, + {100, 166, 142, 29, 14}, + {137, 241, 253, 164, 0} + }, + { {117, 48, 198, 172, 7}, + {237, 249, 98, 192, 2}, + {227, 86, 48, 202, 14}, + {64, 52, 105, 251, 7} + }, + { {219, 148, 76, 224, 4}, + {98, 9, 86, 235, 2}, + {32, 115, 34, 157, 11}, + {77, 118, 169, 4, 6} + }, + { {64, 156, 92, 188, 6}, + {104, 49, 214, 176, 10}, + {99, 211, 163, 144, 2}, + {80, 214, 184, 193, 6} + }, + { {96, 119, 25, 168, 12}, + {81, 38, 111, 176, 2}, + {49, 89, 142, 224, 6}, + {64, 223, 102, 72, 10} + }, + { {100, 136, 107, 16, 1}, + {180, 64, 180, 128, 12}, + {128, 141, 97, 18, 6}, + {48, 18, 208, 34, 13} + }, + { {33, 158, 169, 148, 0}, + {88, 128, 188, 96, 14}, + {2, 153, 87, 152, 4}, + {112, 99, 208, 17, 10} + }, + { {146, 100, 250, 242, 11}, + {37, 220, 135, 59, 7}, + {212, 245, 242, 100, 9}, + {237, 206, 19, 186, 4} + }, + { {214, 14, 81, 55, 9}, + {188, 8, 139, 183, 9}, + {158, 200, 167, 6, 11}, + {158, 221, 17, 3, 13} + }, + { {6, 232, 25, 146, 12}, + {145, 4, 213, 17, 11}, + {52, 153, 129, 118, 0}, + {216, 138, 178, 8, 9} + }, + { {101, 196, 213, 201, 3}, + {180, 181, 48, 252, 2}, + {201, 58, 178, 58, 6}, + {67, 240, 202, 210, 13} + }, + { {91, 1, 85, 227, 4}, + {50, 11, 66, 221, 3}, + {44, 122, 168, 13, 10}, + {203, 180, 45, 4, 12} + }, + { {93, 64, 143, 102, 1}, + {158, 205, 6, 200, 1}, + {134, 111, 16, 43, 10}, + {129, 54, 11, 55, 9} + }, + { {61, 239, 243, 168, 13}, + {183, 238, 123, 112, 14}, + {177, 92, 255, 123, 12}, + {112, 237, 231, 126, 13} + }, + { {83, 37, 200, 86, 6}, + {41, 154, 196, 233, 1}, + {102, 161, 58, 76, 10}, + {137, 114, 53, 153, 4} + }, + { {18, 10, 174, 82, 10}, + {0, 217, 141, 9, 13}, + {84, 167, 85, 4, 8}, + {185, 11, 25, 176, 0} + }, + { {155, 29, 191, 200, 11}, + {86, 251, 5, 123, 14}, + {209, 63, 219, 141, 9}, + {125, 234, 13, 246, 10} + }, + { {207, 183, 171, 86, 8}, + {219, 194, 157, 235, 5}, + {22, 173, 94, 223, 3}, + {173, 123, 148, 61, 11} + }, + { {42, 141, 220, 32, 7}, + {38, 147, 118, 49, 8}, + {224, 67, 187, 21, 4}, + {24, 198, 236, 150, 4} + }, + { {142, 106, 174, 19, 3}, + {135, 213, 140, 7, 13}, + {204, 135, 85, 103, 1}, + {190, 3, 26, 190, 1} + }, + { {171, 253, 77, 159, 3}, + {127, 55, 180, 103, 11}, + {207, 155, 43, 253, 5}, + {222, 98, 222, 207, 14} + }, + { {45, 9, 17, 6, 12}, + {154, 2, 97, 80, 9}, + {54, 8, 137, 11, 4}, + {144, 168, 100, 5, 9} + }, + { {168, 207, 127, 12, 12}, + {58, 103, 125, 50, 12}, + {51, 15, 239, 49, 5}, + {52, 203, 238, 101, 12} + }, + { {166, 83, 153, 76, 7}, + {220, 182, 108, 27, 0}, + {227, 41, 156, 166, 5}, + {13, 131, 102, 211, 11} + }, + { {135, 166, 224, 109, 0}, + {169, 160, 26, 111, 4}, + {11, 96, 118, 94, 1}, + {47, 101, 128, 89, 5} + }, + { {59, 232, 221, 151, 0}, + {59, 141, 180, 85, 11}, + {14, 155, 177, 125, 12}, + {218, 162, 219, 29, 12} + }, + { {209, 178, 226, 250, 9}, + {101, 232, 155, 202, 7}, + {149, 244, 116, 216, 11}, + {229, 61, 145, 122, 6} + }, + { {0, 188, 184, 78, 7}, + {77, 176, 84, 56, 13}, + {231, 33, 211, 208, 0}, + {177, 194, 160, 219, 2} + }, + { {166, 167, 158, 98, 11}, + {133, 211, 63, 59, 1}, + {212, 103, 158, 86, 5}, + {141, 207, 204, 186, 1} + }, + { {200, 134, 91, 1, 3}, + {54, 80, 28, 182, 0}, + {200, 13, 166, 17, 3}, + {6, 211, 128, 166, 12} + }, + { {12, 255, 119, 177, 9}, + {247, 71, 155, 52, 14}, + {152, 222, 239, 243, 0}, + {114, 205, 158, 46, 15} + }, + { {142, 29, 220, 148, 3}, + {238, 147, 132, 51, 10}, + {194, 147, 187, 135, 1}, + {92, 194, 28, 151, 7} + }, + { {18, 244, 200, 158, 7}, + {109, 188, 212, 33, 3}, + {231, 145, 50, 244, 8}, + {200, 66, 179, 219, 6} + }, + { {221, 245, 180, 168, 13}, + {199, 175, 83, 242, 6}, + {177, 82, 218, 251, 11}, + {100, 252, 175, 94, 3} + }, + { {97, 194, 86, 250, 10}, + {32, 117, 187, 216, 3}, + {85, 246, 164, 56, 6}, + {193, 189, 218, 224, 4} + }, + { {34, 2, 81, 216, 5}, + {52, 32, 232, 25, 2}, + {161, 184, 164, 4, 4}, + {73, 129, 112, 66, 12} + }, + { {160, 222, 24, 47, 2}, + {72, 52, 62, 54, 9}, + {79, 65, 135, 176, 5}, + {150, 199, 194, 193, 2} + }, + { {203, 116, 62, 141, 0}, + {75, 101, 4, 247, 6}, + {11, 23, 194, 237, 3}, + {110, 242, 10, 109, 2} + }, + { {32, 93, 174, 186, 9}, + {68, 231, 167, 32, 15}, + {149, 215, 91, 160, 4}, + {240, 78, 94, 114, 2} + }, + { {182, 151, 72, 253, 13}, + {236, 42, 255, 47, 2}, + {187, 241, 46, 150, 13}, + {79, 79, 245, 67, 7} + }, + { {244, 165, 139, 213, 6}, + {153, 218, 244, 174, 2}, + {106, 189, 26, 82, 15}, + {71, 82, 245, 185, 9} + }, + { {137, 147, 208, 12, 15}, + {110, 178, 89, 82, 0}, + {243, 0, 188, 153, 1}, + {4, 169, 164, 215, 6} + }, + { {43, 161, 206, 80, 15}, + {39, 211, 245, 73, 0}, + {240, 167, 56, 93, 4}, + {9, 42, 252, 190, 4} + }, + { {84, 31, 38, 148, 10}, + {200, 91, 137, 160, 14}, + {82, 150, 79, 130, 10}, + {112, 89, 29, 161, 3} + }, + { {250, 73, 70, 153, 5}, + {38, 111, 224, 135, 10}, + {169, 150, 41, 37, 15}, + {94, 16, 127, 102, 4} + }, + { {143, 119, 77, 200, 6}, + {243, 55, 76, 107, 2}, + {97, 59, 46, 239, 1}, + {77, 99, 46, 204, 15} + }, + { {21, 73, 52, 191, 10}, + {136, 63, 131, 84, 15}, + {95, 210, 201, 42, 8}, + {242, 172, 31, 193, 1} + }, + { {160, 179, 125, 121, 0}, + {113, 35, 190, 30, 4}, + {9, 235, 236, 208, 5}, + {39, 135, 220, 72, 14} + }, + { {94, 29, 64, 6, 12}, + {234, 10, 65, 161, 9}, + {54, 0, 43, 135, 10}, + {152, 88, 37, 5, 7} + }, + { {247, 121, 227, 64, 9}, + {245, 206, 33, 203, 12}, + {144, 44, 121, 238, 15}, + {61, 56, 71, 58, 15} + }, + { {126, 60, 103, 41, 12}, + {243, 105, 99, 165, 12}, + {57, 78, 99, 199, 14}, + {58, 92, 105, 108, 15} + }, + { {38, 10, 20, 204, 8}, + {136, 33, 41, 25, 10}, + {19, 50, 133, 6, 4}, + {89, 137, 72, 65, 1} + }, + { {210, 180, 127, 62, 15}, + {125, 121, 215, 179, 5}, + {247, 207, 226, 212, 11}, + {172, 222, 185, 235, 14} + }, + { {52, 74, 22, 15, 2}, + {136, 125, 40, 20, 9}, + {79, 6, 133, 34, 12}, + {146, 129, 75, 225, 1} + }, + { {240, 88, 87, 246, 9}, + {124, 77, 163, 154, 11}, + {150, 254, 161, 160, 15}, + {213, 156, 91, 35, 14} + }, + { {200, 222, 73, 90, 3}, + {118, 52, 156, 170, 9}, + {197, 169, 39, 177, 3}, + {149, 83, 146, 198, 14} + }, + { {41, 152, 52, 89, 7}, + {70, 49, 240, 92, 12}, + {233, 162, 193, 153, 4}, + {51, 160, 248, 198, 2} + }, + { {192, 28, 220, 163, 10}, + {96, 145, 7, 182, 11}, + {92, 83, 179, 128, 3}, + {214, 222, 8, 144, 6} + }, + { {17, 134, 214, 198, 15}, + {44, 217, 89, 120, 3}, + {246, 54, 182, 24, 8}, + {193, 233, 169, 179, 4} + }, + { {199, 106, 99, 224, 0}, + {177, 68, 10, 203, 14}, + {0, 124, 101, 110, 3}, + {125, 53, 2, 40, 13} + }, + { {250, 63, 143, 86, 4}, + {91, 203, 236, 171, 9}, + {38, 175, 31, 197, 15}, + {157, 83, 125, 61, 10} + }, + { {30, 208, 47, 67, 7}, + {214, 93, 84, 13, 5}, + {236, 47, 64, 183, 8}, + {171, 2, 171, 166, 11} + }, + { {167, 132, 152, 85, 15}, + {140, 144, 245, 127, 0}, + {250, 161, 146, 30, 5}, + {15, 234, 240, 147, 1} + }, + { {12, 209, 94, 200, 6}, + {226, 119, 84, 24, 2}, + {97, 55, 168, 179, 0}, + {65, 130, 174, 228, 7} + }, + { {222, 242, 230, 20, 0}, + {235, 205, 152, 131, 4}, + {2, 134, 116, 247, 11}, + {44, 17, 155, 61, 7} + }, + { {172, 114, 24, 123, 12}, + {195, 36, 239, 30, 1}, + {61, 225, 132, 227, 5}, + {135, 143, 114, 76, 3} + }, + { {103, 201, 15, 119, 7}, + {156, 87, 246, 205, 9}, + {238, 239, 9, 62, 6}, + {155, 54, 254, 163, 9} + }, + { {7, 91, 205, 34, 11}, + {244, 151, 15, 65, 9}, + {212, 75, 61, 174, 0}, + {152, 47, 14, 146, 15} + }, + { {201, 190, 213, 237, 13}, + {127, 161, 91, 254, 10}, + {187, 122, 183, 217, 3}, + {87, 253, 168, 95, 14} + }, + { {106, 147, 24, 117, 4}, + {74, 2, 254, 157, 0}, + {42, 225, 140, 149, 6}, + {11, 151, 244, 5, 2} + }, + { {107, 212, 146, 123, 3}, + {70, 244, 178, 253, 1}, + {205, 228, 146, 189, 6}, + {139, 244, 210, 246, 2} + }, + { {118, 132, 7, 115, 8}, + {144, 73, 179, 173, 1}, + {28, 238, 2, 22, 14}, + {139, 92, 217, 32, 9} + }, + { {181, 12, 26, 120, 15}, + {132, 120, 231, 122, 8}, + {241, 229, 131, 10, 13}, + {21, 238, 113, 226, 1} + }, + { {153, 76, 54, 246, 14}, + {10, 93, 195, 122, 15}, + {118, 246, 195, 41, 9}, + {245, 236, 59, 165, 0} + }, + { {33, 245, 36, 87, 11}, + {77, 23, 177, 108, 5}, + {222, 162, 74, 248, 4}, + {163, 104, 222, 139, 2} + }, + { {97, 170, 231, 238, 4}, + {57, 225, 122, 200, 15}, + {39, 126, 117, 88, 6}, + {241, 53, 232, 121, 12} + }, + { {36, 1, 140, 232, 5}, + {132, 163, 102, 8, 2}, + {161, 115, 24, 2, 4}, + {65, 6, 108, 82, 1} + }, + { {97, 41, 206, 35, 10}, + {33, 211, 39, 196, 9}, + {92, 71, 57, 72, 6}, + {146, 62, 76, 184, 4} + }, + { {164, 226, 123, 199, 10}, + {185, 84, 61, 30, 7}, + {94, 61, 228, 114, 5}, + {231, 139, 194, 169, 13} + }, + { {182, 249, 62, 111, 6}, + {201, 127, 118, 31, 13}, + {111, 103, 201, 246, 13}, + {191, 134, 239, 233, 3} + }, + { {184, 158, 66, 250, 4}, + {98, 104, 250, 42, 11}, + {37, 244, 39, 145, 13}, + {213, 69, 241, 100, 6} + }, + { {205, 168, 178, 163, 12}, + {131, 192, 83, 214, 15}, + {60, 84, 209, 91, 3}, + {246, 188, 160, 60, 1} + }, + { {76, 57, 177, 159, 7}, + {223, 178, 192, 148, 15}, + {239, 152, 217, 195, 2}, + {242, 144, 52, 223, 11} + }, + { {185, 129, 38, 244, 2}, + {10, 91, 178, 74, 6}, + {66, 246, 72, 25, 13}, + {101, 36, 221, 165, 0} + }, + { {14, 11, 250, 182, 8}, + {170, 194, 143, 17, 15}, + {22, 213, 253, 7, 0}, + {248, 143, 20, 53, 5} + }, + { {242, 177, 157, 60, 6}, + {89, 187, 246, 147, 0}, + {99, 203, 152, 212, 15}, + {12, 150, 253, 217, 10} + }, + { {62, 75, 92, 90, 2}, + {162, 63, 172, 25, 9}, + {69, 163, 173, 39, 12}, + {153, 131, 95, 196, 5} + }, + { {53, 13, 105, 45, 8}, + {184, 42, 39, 100, 12}, + {27, 73, 107, 10, 12}, + {50, 110, 69, 65, 13} + }, + { {180, 35, 142, 37, 5}, + {141, 203, 110, 6, 0}, + {170, 71, 28, 66, 13}, + {6, 7, 109, 59, 1} + }, + { {69, 98, 201, 154, 0}, + {177, 164, 140, 192, 3}, + {5, 153, 52, 106, 2}, + {192, 51, 18, 88, 13} + }, + { {180, 56, 177, 59, 0}, + {209, 168, 162, 22, 13}, + {13, 200, 209, 194, 13}, + {182, 132, 81, 88, 11} + }, + { {131, 20, 145, 146, 14}, + {80, 144, 193, 115, 3}, + {116, 152, 146, 140, 1}, + {204, 232, 48, 144, 10} + }, + { {255, 9, 118, 5, 7}, + {174, 91, 96, 215, 12}, + {234, 6, 233, 15, 15}, + {62, 176, 109, 167, 5} + }, + { {76, 85, 220, 22, 13}, + {238, 135, 197, 176, 1}, + {182, 131, 186, 163, 2}, + {128, 218, 62, 23, 7} + }, + { {104, 114, 128, 201, 15}, + {71, 180, 105, 140, 2}, + {249, 48, 20, 225, 6}, + {67, 25, 98, 222, 2} + }, + { {118, 171, 174, 160, 4}, + {129, 203, 126, 129, 14}, + {32, 87, 93, 86, 14}, + {120, 23, 237, 56, 1} + }, + { {145, 170, 76, 41, 14}, + {33, 57, 95, 70, 8}, + {121, 67, 37, 88, 9}, + {22, 47, 169, 200, 4} + }, + { {172, 153, 124, 202, 8}, + {226, 35, 53, 26, 15}, + {21, 51, 233, 147, 5}, + {245, 138, 204, 68, 7} + }, + { {14, 211, 209, 214, 1}, + {254, 134, 152, 25, 3}, + {134, 184, 188, 183, 0}, + {201, 129, 150, 23, 15} + }, + { {186, 245, 248, 135, 3}, + {111, 158, 52, 55, 7}, + {206, 17, 250, 245, 13}, + {238, 194, 199, 159, 6} + }, + { {24, 203, 208, 54, 12}, + {42, 142, 219, 16, 9}, + {54, 192, 189, 49, 8}, + {144, 141, 183, 21, 4} + }, + { {230, 136, 49, 122, 7}, + {148, 48, 242, 155, 13}, + {229, 232, 193, 22, 7}, + {189, 148, 240, 194, 9} + }, + { {38, 93, 140, 24, 7}, + {196, 183, 228, 33, 8}, + {225, 131, 27, 166, 4}, + {24, 66, 126, 210, 3} + }, + { {37, 253, 177, 16, 7}, + {213, 150, 240, 112, 12}, + {224, 136, 219, 250, 4}, + {48, 224, 246, 154, 11} + }, + { {46, 105, 226, 33, 9}, + {167, 198, 35, 5, 12}, + {152, 68, 121, 103, 4}, + {58, 12, 70, 62, 5} + }, + { {148, 212, 15, 127, 5}, + {220, 109, 214, 46, 1}, + {175, 239, 2, 178, 9}, + {135, 70, 187, 99, 11} + }, + { {104, 227, 24, 161, 2}, + {3, 22, 62, 148, 2}, + {72, 81, 140, 113, 6}, + {66, 151, 198, 140, 0} + }, + { {115, 38, 168, 12, 2}, + {9, 184, 44, 225, 4}, + {67, 1, 86, 76, 14}, + {40, 115, 65, 217, 0} + }, + { {8, 225, 19, 56, 4}, + {19, 102, 210, 16, 0}, + {33, 204, 136, 113, 0}, + {0, 132, 182, 108, 8} + }, + { {30, 74, 153, 58, 11}, + {150, 188, 143, 17, 9}, + {213, 201, 149, 39, 8}, + {152, 143, 19, 214, 9} + }, + { {56, 123, 230, 193, 11}, + {103, 223, 41, 12, 14}, + {216, 54, 125, 225, 12}, + {115, 9, 79, 190, 6} + }, + { {23, 164, 136, 165, 2}, + {137, 152, 22, 101, 2}, + {74, 81, 18, 94, 8}, + {74, 102, 129, 153, 1} + }, + { {100, 67, 135, 200, 8}, + {144, 231, 41, 136, 2}, + {17, 62, 28, 34, 6}, + {65, 25, 78, 112, 9} + }, + { {110, 7, 130, 143, 7}, + {142, 242, 104, 165, 3}, + {239, 20, 30, 7, 6}, + {202, 81, 100, 247, 1} + }, + { {184, 128, 233, 2, 2}, + {50, 152, 52, 2, 5}, + {68, 9, 112, 17, 13}, + {164, 2, 193, 148, 12} + }, + { {62, 14, 185, 83, 2}, + {146, 152, 172, 61, 13}, + {76, 169, 215, 7, 12}, + {187, 195, 81, 148, 9} + }, + { {143, 140, 17, 176, 2}, + {146, 16, 146, 115, 10}, + {64, 216, 131, 31, 1}, + {92, 228, 144, 132, 9} + }, + { {27, 141, 195, 155, 13}, + {54, 234, 209, 101, 11}, + {189, 156, 59, 29, 8}, + {218, 104, 181, 118, 12} + }, + { {238, 130, 108, 30, 9}, + {174, 33, 189, 131, 5}, + {151, 131, 100, 23, 7}, + {172, 27, 216, 71, 5} + }, + { {149, 96, 110, 240, 8}, + {161, 77, 135, 74, 6}, + {16, 247, 96, 106, 9}, + {101, 46, 27, 40, 5} + }, + { {80, 228, 38, 86, 2}, + {9, 93, 144, 168, 5}, + {70, 166, 66, 112, 10}, + {161, 80, 155, 169, 0} + }, + { {212, 77, 143, 180, 2}, + {152, 223, 134, 162, 10}, + {66, 223, 27, 34, 11}, + {84, 86, 31, 177, 9} + }, + { {5, 79, 54, 9, 7}, + {132, 119, 72, 116, 12}, + {233, 6, 207, 42, 0}, + {50, 225, 46, 226, 1} + }, + { {165, 246, 91, 233, 9}, + {245, 100, 63, 126, 2}, + {153, 125, 166, 250, 5}, + {71, 239, 194, 106, 15} + }, + { {246, 67, 68, 234, 5}, + {164, 47, 106, 139, 3}, + {165, 114, 44, 38, 15}, + {205, 21, 111, 66, 5} + }, + { {85, 81, 175, 40, 13}, + {212, 239, 71, 192, 4}, + {177, 79, 88, 170, 10}, + {32, 62, 47, 114, 11} + }, + { {27, 94, 104, 63, 5}, + {110, 44, 206, 101, 13}, + {175, 193, 103, 173, 8}, + {186, 103, 51, 71, 6} + }, + { {6, 118, 167, 222, 12}, + {217, 229, 201, 41, 7}, + {55, 190, 86, 230, 0}, + {233, 73, 58, 121, 11} + }, + { {70, 176, 31, 231, 11}, + {221, 81, 23, 157, 3}, + {222, 127, 128, 214, 2}, + {203, 158, 136, 171, 11} + }, + { {38, 80, 223, 195, 0}, + {240, 197, 36, 29, 3}, + {12, 63, 176, 166, 4}, + {203, 130, 74, 48, 15} + }, + { {52, 1, 83, 23, 3}, + {188, 90, 160, 20, 1}, + {206, 140, 168, 2, 12}, + {130, 128, 85, 163, 13} + }, + { {185, 183, 76, 10, 12}, + {99, 43, 125, 98, 1}, + {53, 3, 46, 217, 13}, + {132, 107, 237, 76, 6} + }, + { {103, 124, 93, 21, 5}, + {253, 5, 228, 245, 8}, + {170, 139, 163, 238, 6}, + {26, 242, 122, 11, 15} + }, + { {62, 14, 116, 210, 9}, + {166, 9, 169, 57, 15}, + {148, 178, 231, 7, 12}, + {249, 201, 89, 6, 5} + }, + { {135, 91, 221, 123, 4}, + {240, 167, 206, 95, 9}, + {45, 235, 189, 174, 1}, + {159, 167, 62, 80, 15} + }, + { {243, 250, 71, 202, 15}, + {117, 125, 121, 203, 11}, + {245, 62, 37, 252, 15}, + {221, 57, 235, 234, 14} + }, + { {215, 194, 170, 201, 7}, + {132, 252, 92, 207, 6}, + {233, 53, 84, 62, 11}, + {111, 51, 163, 242, 1} + }, + { {9, 112, 244, 98, 8}, + {99, 133, 3, 88, 5}, + {20, 98, 240, 233, 0}, + {161, 172, 10, 28, 6} + }, + { {49, 139, 231, 50, 10}, + {48, 219, 187, 64, 13}, + {84, 206, 125, 24, 12}, + {176, 45, 221, 176, 12} + }, + { {250, 59, 245, 187, 4}, + {115, 171, 234, 151, 15}, + {45, 218, 253, 197, 15}, + {254, 149, 125, 92, 14} + }, + { {119, 186, 106, 0, 14}, + {225, 88, 125, 193, 12}, + {112, 5, 101, 222, 14}, + {56, 59, 225, 168, 7} + }, + { {245, 7, 16, 43, 15}, + {132, 58, 107, 246, 1}, + {253, 64, 142, 10, 15}, + {134, 253, 101, 194, 1} + }, + { {55, 185, 16, 190, 8}, + {201, 42, 179, 81, 11}, + {23, 208, 137, 222, 12}, + {216, 172, 213, 73, 3} + }, + { {154, 36, 87, 204, 1}, + {63, 105, 0, 59, 2}, + {131, 62, 162, 69, 9}, + {77, 192, 9, 111, 12} + }, + { {86, 238, 203, 19, 12}, + {177, 204, 221, 165, 9}, + {60, 141, 55, 118, 10}, + {154, 91, 179, 56, 13} + }, + { {19, 93, 119, 167, 4}, + {120, 79, 66, 117, 15}, + {46, 94, 235, 172, 8}, + {250, 228, 47, 33, 14} + }, + { {13, 146, 253, 48, 4}, + {242, 129, 222, 80, 4}, + {32, 203, 244, 155, 0}, + {32, 167, 184, 20, 15} + }, + { {237, 122, 230, 34, 3}, + {231, 213, 42, 194, 13}, + {196, 70, 117, 235, 7}, + {180, 53, 74, 190, 7} + }, + { {141, 125, 109, 37, 12}, + {251, 7, 71, 102, 12}, + {58, 75, 107, 235, 1}, + {54, 110, 46, 13, 15} + }, + { {120, 62, 207, 103, 3}, + {127, 217, 46, 172, 9}, + {206, 111, 55, 193, 14}, + {147, 87, 73, 191, 14} + }, + { {62, 116, 93, 252, 6}, + {251, 61, 230, 57, 2}, + {99, 251, 162, 231, 12}, + {73, 198, 123, 205, 15} + }, + { {188, 170, 218, 5, 10}, + {171, 216, 61, 22, 8}, + {90, 5, 181, 83, 13}, + {22, 139, 193, 189, 5} + }, + { {13, 162, 38, 20, 8}, + {139, 65, 153, 64, 4}, + {18, 134, 68, 91, 0}, + {32, 41, 152, 45, 1} + }, + { {134, 217, 94, 7, 10}, + {232, 87, 21, 23, 9}, + {94, 7, 169, 182, 1}, + {158, 138, 142, 161, 7} + }, + { {37, 126, 156, 27, 14}, + {193, 181, 237, 116, 9}, + {125, 131, 151, 234, 4}, + {146, 235, 122, 216, 3} + }, + { {221, 222, 210, 16, 1}, + {230, 204, 152, 242, 8}, + {128, 132, 183, 187, 11}, + {20, 241, 147, 54, 7} + }, + { {233, 1, 205, 160, 13}, + {54, 131, 103, 194, 2}, + {176, 91, 56, 9, 7}, + {68, 62, 108, 22, 12} + }, + { {119, 152, 12, 57, 4}, + {192, 41, 246, 197, 8}, + {41, 195, 1, 158, 14}, + {26, 54, 249, 64, 3} + }, + { {209, 14, 75, 139, 7}, + {52, 120, 76, 230, 11}, + {237, 29, 39, 8, 11}, + {214, 115, 33, 226, 12} + }, + { {226, 178, 81, 251, 9}, + {117, 32, 187, 159, 3}, + {157, 248, 164, 212, 7}, + {207, 157, 208, 74, 14} + }, + { {108, 39, 137, 18, 7}, + {151, 146, 236, 160, 1}, + {228, 137, 30, 67, 6}, + {128, 83, 116, 158, 9} + }, + { {78, 213, 70, 229, 4}, + {234, 71, 82, 173, 2}, + {42, 118, 42, 183, 2}, + {75, 84, 174, 37, 7} + }, + { {240, 6, 14, 244, 9}, + {12, 73, 175, 170, 2}, + {146, 247, 6, 0, 15}, + {69, 95, 89, 35, 0} + }, + { {179, 69, 148, 196, 9}, + {12, 143, 33, 123, 2}, + {146, 50, 154, 44, 13}, + {77, 232, 79, 19, 0} + }, + { {183, 99, 0, 71, 12}, + {137, 14, 105, 79, 1}, + {62, 32, 12, 110, 13}, + {143, 41, 103, 9, 1} + }, + { {124, 196, 229, 3, 1}, + {182, 141, 48, 164, 5}, + {140, 10, 114, 51, 14}, + {162, 80, 203, 22, 13} + }, + { {86, 1, 102, 16, 6}, + {160, 91, 192, 129, 4}, + {96, 134, 104, 6, 10}, + {40, 16, 61, 160, 5} + }, + { {82, 75, 187, 242, 15}, + {20, 222, 207, 153, 15}, + {244, 253, 221, 36, 10}, + {249, 159, 55, 178, 8} + }, + { {142, 50, 98, 30, 15}, + {239, 112, 201, 3, 5}, + {247, 132, 100, 199, 1}, + {172, 9, 48, 239, 7} + }, + { {66, 68, 75, 195, 9}, + {52, 68, 5, 173, 3}, + {156, 61, 34, 36, 2}, + {203, 90, 2, 34, 12} + }, + { {139, 101, 126, 225, 12}, + {35, 71, 71, 127, 6}, + {56, 119, 234, 109, 1}, + {111, 238, 46, 44, 4} + }, + { {53, 42, 182, 84, 7}, + {141, 217, 232, 88, 12}, + {226, 166, 213, 74, 12}, + {49, 161, 121, 187, 1} + }, + { {231, 165, 75, 233, 2}, + {177, 114, 54, 239, 2}, + {73, 125, 42, 94, 7}, + {79, 118, 196, 232, 13} + }, + { {120, 30, 244, 21, 3}, + {110, 153, 168, 180, 12}, + {202, 130, 247, 129, 14}, + {50, 209, 89, 151, 6} + }, + { {50, 242, 74, 129, 2}, + {97, 92, 60, 5, 2}, + {72, 21, 36, 244, 12}, + {74, 3, 195, 168, 6} + }, + { {40, 17, 16, 119, 11}, + {78, 18, 163, 28, 1}, + {222, 224, 136, 129, 4}, + {131, 140, 84, 135, 2} + }, + { {37, 203, 65, 84, 13}, + {188, 6, 249, 72, 8}, + {178, 168, 45, 58, 4}, + {17, 41, 246, 3, 13} + }, + { {167, 103, 245, 103, 1}, + {189, 135, 42, 127, 5}, + {142, 106, 254, 110, 5}, + {175, 229, 78, 27, 13} + }, + { {91, 165, 158, 70, 11}, + {15, 219, 21, 249, 1}, + {214, 39, 154, 93, 10}, + {137, 250, 141, 191, 0} + }, + { {88, 61, 62, 106, 2}, + {67, 123, 6, 184, 13}, + {69, 103, 203, 193, 10}, + {177, 214, 13, 236, 2} + }, + { {191, 77, 67, 125, 10}, + {186, 126, 163, 111, 8}, + {91, 236, 43, 47, 13}, + {31, 108, 87, 229, 13} + }, + { {160, 164, 162, 247, 2}, + {9, 208, 178, 46, 7}, + {78, 244, 82, 80, 5}, + {231, 68, 208, 185, 0} + }, + { {93, 52, 153, 184, 6}, + {211, 184, 198, 240, 2}, + {97, 217, 146, 203, 10}, + {64, 246, 49, 220, 11} + }, + { {211, 7, 214, 157, 5}, + {44, 235, 200, 247, 2}, + {171, 150, 190, 12, 11}, + {78, 241, 61, 115, 4} + }, + { {155, 172, 247, 16, 14}, + {51, 217, 209, 115, 12}, + {112, 142, 243, 93, 9}, + {60, 232, 185, 188, 12} + }, + { {196, 108, 246, 252, 13}, + {173, 229, 195, 186, 14}, + {179, 246, 243, 98, 3}, + {117, 220, 58, 123, 5} + }, + { {184, 1, 141, 118, 12}, + {26, 139, 231, 10, 1}, + {54, 235, 24, 1, 13}, + {133, 14, 125, 21, 8} + }, + { {232, 151, 127, 181, 13}, + {126, 67, 255, 182, 6}, + {186, 223, 238, 145, 7}, + {102, 223, 252, 39, 14} + }, + { {11, 60, 141, 188, 5}, + {95, 161, 198, 97, 10}, + {163, 219, 19, 205, 0}, + {88, 102, 56, 95, 10} + }, + { {20, 20, 133, 139, 8}, + {208, 169, 1, 36, 3}, + {29, 26, 18, 130, 8}, + {194, 72, 9, 80, 11} + }, + { {115, 104, 16, 10, 4}, + {1, 44, 96, 209, 9}, + {37, 0, 129, 108, 14}, + {152, 176, 99, 72, 0} + }, + { {18, 132, 134, 239, 3}, + {12, 249, 18, 45, 3}, + {207, 118, 18, 20, 8}, + {203, 68, 137, 243, 0} + }, + { {210, 252, 86, 125, 0}, + {105, 109, 146, 191, 8}, + {11, 230, 163, 244, 11}, + {31, 212, 155, 105, 6} + }, + { {91, 207, 66, 130, 14}, + {34, 94, 89, 225, 11}, + {116, 20, 47, 61, 10}, + {216, 121, 167, 164, 4} + }, + { {73, 183, 123, 167, 3}, + {127, 82, 30, 244, 7}, + {206, 93, 238, 217, 2}, + {226, 247, 132, 175, 14} + }, + { {97, 229, 12, 169, 15}, + {5, 55, 119, 228, 2}, + {249, 83, 10, 120, 6}, + {66, 126, 238, 202, 0} + }, + { {28, 221, 19, 226, 0}, + {210, 78, 18, 56, 11}, + {4, 124, 139, 179, 8}, + {209, 196, 135, 36, 11} + }, + { {241, 23, 253, 83, 15}, + {116, 155, 237, 254, 5}, + {252, 171, 254, 136, 15}, + {167, 251, 125, 146, 14} + }, + { {141, 51, 92, 19, 13}, + {231, 3, 205, 86, 1}, + {188, 131, 172, 203, 1}, + {134, 171, 60, 14, 7} + }, + { {248, 223, 155, 48, 4}, + {82, 206, 254, 178, 8}, + {32, 205, 159, 177, 15}, + {20, 215, 247, 52, 10} + }, + { {205, 115, 59, 196, 0}, + {219, 70, 12, 218, 6}, + {2, 61, 204, 235, 3}, + {101, 179, 6, 45, 11} + }, + { {35, 29, 150, 151, 9}, + {76, 195, 161, 117, 11}, + {158, 150, 155, 140, 4}, + {218, 232, 92, 51, 2} + }, + { {44, 151, 177, 71, 6}, + {218, 146, 120, 60, 5}, + {110, 40, 222, 147, 4}, + {163, 193, 228, 149, 11} + }, + { {230, 105, 156, 80, 1}, + {133, 135, 164, 155, 8}, + {128, 163, 153, 102, 7}, + {29, 146, 94, 26, 1} + }, + { {181, 161, 208, 86, 14}, + {169, 154, 241, 90, 1}, + {118, 160, 184, 90, 13}, + {133, 168, 245, 153, 5} + }, + { {60, 125, 33, 88, 4}, + {211, 46, 224, 40, 12}, + {33, 168, 75, 227, 12}, + {49, 64, 119, 76, 11} + }, + { {173, 20, 11, 20, 5}, + {222, 64, 228, 98, 0}, + {162, 141, 2, 139, 5}, + {4, 98, 112, 39, 11} + }, + { {251, 9, 110, 114, 3}, + {38, 91, 166, 203, 13}, + {196, 231, 105, 13, 15}, + {189, 54, 93, 166, 4} + }, + { {23, 132, 105, 191, 13}, + {188, 40, 215, 101, 7}, + {191, 217, 98, 30, 8}, + {234, 110, 177, 67, 13} + }, + { {36, 48, 61, 47, 6}, + {217, 49, 102, 20, 5}, + {111, 75, 192, 194, 4}, + {162, 134, 104, 201, 11} + }, + { {235, 214, 231, 21, 5}, + {126, 197, 248, 231, 4}, + {170, 142, 118, 189, 7}, + {46, 113, 250, 55, 14} + }, + { {147, 91, 170, 215, 11}, + {76, 222, 141, 79, 15}, + {222, 181, 93, 172, 9}, + {255, 43, 23, 179, 2} + }, + { {209, 95, 34, 205, 13}, + {76, 110, 73, 238, 14}, + {187, 52, 79, 168, 11}, + {119, 121, 39, 99, 2} + }, + { {55, 178, 213, 83, 15}, + {245, 153, 249, 93, 1}, + {252, 170, 180, 222, 12}, + {139, 169, 249, 154, 15} + }, + { {211, 101, 213, 98, 8}, + {49, 143, 3, 251, 1}, + {20, 106, 186, 108, 11}, + {141, 252, 15, 24, 12} + }, + { {29, 118, 183, 146, 15}, + {215, 221, 201, 112, 7}, + {244, 158, 214, 235, 8}, + {224, 233, 59, 190, 11} + }, + { {87, 185, 126, 171, 11}, + {229, 123, 23, 213, 15}, + {221, 87, 233, 222, 10}, + {250, 190, 141, 234, 7} + }, + { {9, 11, 171, 164, 4}, + {26, 194, 78, 64, 14}, + {34, 93, 93, 9, 0}, + {112, 39, 36, 53, 8} + }, + { {163, 131, 97, 118, 11}, + {60, 18, 187, 75, 5}, + {214, 232, 108, 28, 5}, + {173, 45, 212, 131, 12} + }, + { {156, 250, 125, 156, 6}, + {251, 61, 220, 18, 14}, + {99, 155, 229, 243, 9}, + {116, 131, 187, 205, 15} + }, + { {76, 134, 94, 122, 15}, + {166, 113, 223, 184, 1}, + {245, 231, 166, 19, 2}, + {129, 223, 184, 230, 5} + }, + { {249, 13, 22, 148, 11}, + {14, 91, 161, 242, 10}, + {210, 150, 139, 9, 15}, + {84, 248, 93, 167, 0} + }, + { {120, 93, 12, 212, 1}, + {78, 15, 164, 168, 10}, + {130, 179, 11, 161, 14}, + {81, 82, 95, 7, 2} + }, + { {51, 98, 95, 192, 1}, + {53, 77, 44, 89, 2}, + {128, 63, 164, 108, 12}, + {73, 163, 75, 42, 12} + }, + { {0, 213, 240, 148, 3}, + {108, 150, 144, 48, 6}, + {194, 144, 250, 176, 0}, + {96, 192, 150, 147, 6} + }, + { {167, 185, 175, 176, 8}, + {209, 195, 183, 67, 14}, + {16, 223, 89, 222, 5}, + {124, 46, 220, 56, 11} + }, + { {223, 59, 151, 88, 10}, + {211, 251, 137, 219, 8}, + {81, 174, 157, 207, 11}, + {29, 185, 29, 252, 11} + }, + { {182, 138, 31, 89, 2}, + {144, 121, 188, 31, 8}, + {73, 175, 133, 22, 13}, + {31, 131, 217, 224, 9} + }, + { {103, 50, 106, 212, 0}, + {233, 64, 172, 201, 6}, + {2, 181, 100, 206, 6}, + {105, 51, 80, 41, 7} + }, + { {7, 184, 90, 134, 3}, + {237, 80, 20, 81, 11}, + {198, 21, 161, 222, 0}, + {216, 162, 128, 171, 7} + }, + { {37, 38, 25, 247, 15}, + {157, 16, 239, 124, 3}, + {254, 249, 134, 74, 4}, + {195, 239, 112, 139, 9} + }, + { {130, 206, 71, 104, 3}, + {52, 117, 26, 43, 8}, + {193, 110, 39, 52, 1}, + {29, 69, 138, 226, 12} + }, + { {108, 230, 33, 121, 12}, + {147, 36, 251, 172, 4}, + {57, 232, 70, 115, 6}, + {35, 93, 242, 76, 9} + }, + { {98, 230, 221, 55, 8}, + {57, 133, 191, 181, 1}, + {30, 203, 182, 116, 6}, + {138, 223, 218, 25, 12} + }, + { {62, 92, 11, 17, 10}, + {210, 92, 165, 37, 8}, + {88, 141, 3, 167, 12}, + {26, 74, 83, 164, 11} + }, + { {57, 245, 112, 80, 8}, + {99, 14, 177, 120, 4}, + {16, 160, 234, 249, 12}, + {33, 232, 215, 12, 6} + }, + { {214, 87, 51, 142, 6}, + {216, 126, 72, 179, 7}, + {103, 28, 206, 166, 11}, + {236, 209, 39, 225, 11} + }, + { {192, 98, 168, 92, 12}, + {9, 164, 205, 138, 4}, + {51, 161, 84, 96, 3}, + {37, 27, 50, 89, 0} + }, + { {131, 220, 0, 222, 5}, + {76, 36, 208, 107, 11}, + {167, 176, 3, 188, 1}, + {221, 96, 178, 67, 2} + }, + { {177, 33, 195, 250, 3}, + {53, 250, 162, 74, 3}, + {197, 252, 56, 72, 13}, + {197, 36, 85, 250, 12} + }, + { {227, 149, 10, 113, 14}, + {64, 82, 247, 239, 0}, + {120, 229, 10, 156, 7}, + {15, 126, 244, 160, 2} + }, + { {113, 29, 253, 190, 8}, + {120, 171, 167, 240, 15}, + {23, 219, 251, 136, 14}, + {240, 254, 93, 81, 14} + }, + { {130, 171, 112, 191, 2}, + {41, 50, 154, 23, 15}, + {79, 208, 237, 84, 1}, + {254, 133, 148, 201, 4} + }, + { {93, 123, 132, 66, 1}, + {199, 143, 8, 200, 9}, + {132, 34, 29, 235, 10}, + {145, 49, 15, 30, 3} + }, + { {103, 239, 52, 200, 12}, + {129, 39, 121, 249, 14}, + {49, 50, 207, 126, 6}, + {121, 249, 238, 72, 1} + }, + { {202, 53, 231, 230, 13}, + {127, 195, 67, 171, 7}, + {182, 126, 122, 197, 3}, + {237, 92, 44, 63, 14} + }, + { {36, 1, 77, 111, 11}, + {188, 51, 39, 12, 1}, + {223, 107, 40, 2, 4}, + {131, 14, 76, 195, 13} + }, + { {183, 239, 157, 161, 14}, + {145, 159, 127, 119, 10}, + {120, 91, 159, 126, 13}, + {94, 239, 239, 152, 9} + }, + { {176, 76, 172, 188, 7}, + {12, 189, 230, 34, 14}, + {227, 211, 83, 32, 13}, + {116, 70, 123, 211, 0} + }, + { {193, 166, 3, 154, 8}, + {17, 96, 153, 226, 3}, + {21, 156, 6, 88, 3}, + {196, 121, 144, 104, 8} + }, + { {197, 1, 82, 163, 10}, + {160, 82, 3, 214, 3}, + {92, 84, 168, 10, 3}, + {198, 188, 4, 160, 5} + }, + { {220, 202, 43, 43, 7}, + {150, 124, 94, 134, 13}, + {237, 77, 69, 51, 11}, + {182, 23, 163, 230, 9} + }, + { {88, 6, 54, 143, 3}, + {14, 121, 8, 180, 7}, + {207, 22, 198, 1, 10}, + {226, 209, 9, 231, 0} + }, + { {139, 223, 114, 151, 10}, + {106, 86, 153, 119, 15}, + {94, 148, 239, 189, 1}, + {254, 233, 150, 165, 6} + }, + { {118, 72, 225, 171, 10}, + {176, 188, 35, 133, 15}, + {93, 88, 113, 38, 14}, + {250, 28, 67, 208, 13} + }, + { {224, 20, 244, 247, 7}, + {108, 145, 226, 190, 7}, + {238, 242, 242, 128, 7}, + {231, 212, 120, 147, 6} + }, + { {113, 170, 60, 249, 0}, + {1, 41, 190, 220, 14}, + {9, 243, 197, 88, 14}, + {115, 183, 217, 72, 0} + }, + { {156, 171, 243, 108, 14}, + {187, 250, 91, 26, 12}, + {115, 108, 253, 83, 9}, + {53, 141, 165, 253, 13} + }, + { {135, 84, 73, 90, 15}, + {244, 52, 197, 107, 1}, + {245, 169, 34, 174, 1}, + {141, 106, 50, 194, 15} + }, + { {154, 153, 213, 71, 2}, + {122, 155, 16, 31, 9}, + {78, 42, 185, 149, 9}, + {159, 128, 141, 149, 14} + }, + { {231, 44, 187, 69, 6}, + {153, 208, 100, 255, 12}, + {106, 45, 211, 78, 7}, + {63, 242, 96, 185, 9} + }, + { {196, 176, 203, 147, 1}, + {245, 192, 148, 134, 3}, + {140, 157, 48, 210, 3}, + {198, 18, 144, 58, 15} + }, + { {137, 237, 4, 93, 7}, + {15, 55, 208, 110, 8}, + {235, 162, 11, 121, 1}, + {23, 96, 190, 207, 0} + }, + { {199, 169, 10, 58, 11}, + {133, 114, 151, 195, 9}, + {213, 197, 9, 94, 3}, + {156, 62, 148, 234, 1} + }, + { {65, 90, 235, 196, 2}, + {120, 212, 12, 200, 14}, + {66, 61, 117, 168, 2}, + {113, 51, 2, 177, 14} + }, + { {127, 85, 215, 48, 2}, + {242, 223, 162, 241, 0}, + {64, 206, 186, 175, 14}, + {8, 244, 95, 180, 15} + }, + { {17, 75, 16, 122, 6}, + {0, 62, 202, 88, 9}, + {101, 224, 141, 40, 8}, + {145, 165, 55, 192, 0} + }, + { {230, 235, 111, 39, 4}, + {185, 71, 126, 135, 13}, + {46, 79, 109, 118, 7}, + {190, 23, 238, 41, 13} + }, + { {132, 244, 176, 135, 15}, + {205, 148, 81, 54, 7}, + {254, 16, 210, 242, 1}, + {230, 200, 162, 155, 3} + }, + { {91, 222, 44, 8, 8}, + {66, 45, 29, 225, 12}, + {17, 3, 71, 189, 10}, + {56, 123, 139, 68, 2} + }, + { {205, 253, 149, 194, 7}, + {215, 151, 80, 250, 11}, + {228, 58, 155, 251, 3}, + {213, 240, 174, 158, 11} + }, + { {51, 193, 37, 123, 3}, + {20, 63, 178, 77, 5}, + {205, 234, 72, 60, 12}, + {171, 36, 223, 194, 8} + }, + { {99, 27, 71, 141, 15}, + {124, 115, 105, 197, 10}, + {251, 30, 45, 140, 6}, + {90, 57, 108, 227, 14} + }, + { {229, 239, 80, 81, 11}, + {165, 22, 185, 254, 8}, + {216, 160, 175, 122, 7}, + {23, 249, 214, 138, 5} + }, + { {65, 141, 160, 190, 12}, + {8, 162, 211, 224, 15}, + {55, 208, 91, 24, 2}, + {240, 124, 180, 81, 0} + }, + { {193, 184, 36, 139, 1}, + {69, 33, 16, 198, 15}, + {141, 18, 65, 216, 3}, + {246, 48, 136, 74, 2} + }, + { {118, 222, 87, 133, 11}, + {252, 93, 57, 181, 10}, + {218, 30, 167, 182, 14}, + {90, 217, 203, 163, 15} + }, + { {219, 201, 87, 218, 6}, + {50, 127, 208, 219, 11}, + {101, 190, 169, 61, 11}, + {221, 176, 191, 228, 12} + }, + { {202, 225, 244, 5, 4}, + {43, 135, 80, 151, 4}, + {42, 2, 248, 117, 3}, + {46, 144, 174, 29, 4} + }, + { {145, 76, 36, 42, 2}, + {0, 61, 2, 98, 13}, + {69, 66, 67, 40, 9}, + {180, 100, 11, 192, 0} + }, + { {69, 163, 208, 168, 2}, + {161, 178, 26, 208, 2}, + {65, 80, 188, 90, 2}, + {64, 181, 132, 216, 5} + }, + { {136, 154, 125, 93, 13}, + {126, 33, 221, 30, 12}, + {187, 171, 229, 145, 1}, + {55, 139, 184, 71, 14} + }, + { {154, 61, 179, 211, 2}, + {83, 218, 128, 63, 15}, + {76, 188, 219, 197, 9}, + {255, 192, 21, 188, 10} + }, + { {208, 44, 134, 112, 15}, + {5, 217, 195, 170, 8}, + {240, 230, 19, 64, 11}, + {21, 92, 57, 186, 0} + }, + { {21, 123, 52, 80, 12}, + {193, 15, 201, 88, 12}, + {48, 162, 205, 234, 8}, + {49, 169, 63, 8, 3} + }, + { {137, 13, 55, 91, 10}, + {18, 115, 129, 126, 13}, + {93, 174, 203, 9, 1}, + {183, 232, 28, 228, 8} + }, + { {137, 27, 201, 59, 10}, + {114, 178, 143, 70, 9}, + {93, 201, 61, 137, 1}, + {150, 47, 20, 212, 14} + }, + { {138, 2, 216, 10, 3}, + {38, 176, 12, 19, 1}, + {197, 1, 180, 5, 1}, + {140, 131, 0, 214, 4} + }, + { {30, 93, 243, 65, 14}, + {242, 222, 65, 61, 12}, + {120, 44, 251, 167, 8}, + {59, 200, 39, 180, 15} + }, + { {120, 109, 137, 131, 2}, + {19, 158, 36, 164, 11}, + {76, 25, 27, 97, 14}, + {210, 82, 71, 156, 8} + }, + { {172, 247, 228, 126, 13}, + {239, 167, 251, 42, 5}, + {183, 226, 126, 243, 5}, + {165, 77, 254, 95, 7} + }, + { {28, 22, 226, 90, 0}, + {226, 232, 136, 40, 5}, + {5, 164, 118, 131, 8}, + {161, 65, 17, 116, 7} + }, + { {46, 169, 4, 13, 9}, + {143, 35, 49, 5, 8}, + {155, 2, 9, 87, 4}, + {26, 8, 204, 79, 1} + }, + { {243, 38, 138, 238, 5}, + {13, 232, 110, 235, 3}, + {167, 117, 22, 76, 15}, + {205, 119, 97, 123, 0} + }, + { {128, 232, 75, 204, 11}, + {61, 116, 21, 10, 10}, + {211, 61, 33, 112, 1}, + {85, 10, 130, 235, 12} + }, + { {162, 190, 211, 245, 12}, + {121, 192, 251, 63, 10}, + {58, 252, 183, 212, 5}, + {95, 205, 240, 57, 14} + }, + { {179, 172, 4, 251, 2}, + {1, 57, 178, 111, 11}, + {77, 242, 3, 92, 13}, + {223, 100, 217, 200, 0} + }, + { {132, 89, 129, 25, 2}, + {208, 182, 128, 6, 8}, + {73, 136, 25, 162, 1}, + {22, 0, 22, 208, 11} + }, + { {96, 167, 159, 201, 4}, + {17, 227, 124, 188, 2}, + {41, 63, 158, 80, 6}, + {67, 211, 236, 120, 8} + }, + { {19, 143, 250, 197, 9}, + {44, 202, 29, 125, 14}, + {154, 53, 255, 28, 8}, + {123, 235, 133, 51, 4} + }, + { {214, 125, 112, 144, 3}, + {229, 30, 128, 179, 14}, + {192, 144, 235, 230, 11}, + {124, 208, 23, 138, 7} + }, + { {43, 138, 160, 242, 5}, + {6, 128, 250, 73, 15}, + {164, 240, 85, 29, 4}, + {249, 37, 240, 22, 0} + }, + { {41, 131, 176, 59, 10}, + {2, 178, 187, 84, 5}, + {93, 192, 220, 25, 4}, + {162, 173, 212, 212, 0} + }, + { {140, 215, 81, 28, 8}, + {250, 38, 153, 50, 0}, + {19, 136, 174, 179, 1}, + {4, 201, 150, 69, 15} + }, + { {47, 222, 77, 2, 12}, + {242, 5, 125, 97, 9}, + {52, 11, 39, 191, 4}, + {152, 107, 234, 4, 15} + }, + { {213, 4, 207, 5, 12}, + {184, 201, 69, 230, 0}, + {58, 15, 50, 10, 11}, + {6, 122, 41, 49, 13} + }, + { {186, 231, 214, 153, 0}, + {35, 239, 184, 55, 2}, + {9, 150, 190, 117, 13}, + {78, 193, 223, 124, 4} + }, + { {156, 243, 126, 164, 13}, + {239, 79, 95, 18, 6}, + {178, 87, 236, 243, 9}, + {100, 143, 175, 47, 7} + }, + { {143, 79, 97, 177, 14}, + {178, 22, 203, 103, 14}, + {120, 216, 111, 47, 1}, + {126, 109, 54, 132, 13} + }, + { {137, 255, 175, 206, 6}, + {91, 247, 92, 106, 15}, + {103, 63, 95, 249, 1}, + {245, 99, 174, 253, 10} + }, + { {124, 126, 177, 202, 2}, + {211, 188, 40, 184, 15}, + {69, 56, 215, 227, 14}, + {241, 209, 67, 220, 11} + }, + { {150, 194, 61, 167, 11}, + {156, 29, 31, 23, 7}, + {222, 91, 196, 54, 9}, + {238, 143, 139, 131, 9} + } +}; + + + +static unsigned char DICT_APRILTAG_36h11_BYTES[][4][5] = +{ + { {33, 161, 70, 186, 11}, + {37, 115, 179, 64, 3}, + {213, 214, 40, 88, 4}, + {192, 44, 220, 234, 4} + }, + { {146, 209, 143, 233, 11}, + {84, 255, 23, 15, 2}, + {217, 127, 24, 180, 9}, + {79, 14, 143, 242, 10} + }, + { {112, 137, 1, 75, 11}, + {20, 58, 49, 140, 9}, + {221, 40, 9, 16, 14}, + {147, 24, 197, 194, 8} + }, + { {25, 57, 121, 226, 7}, + {119, 26, 70, 88, 15}, + {228, 121, 233, 201, 8}, + {241, 166, 37, 142, 14} + }, + { {68, 21, 61, 61, 7}, + {220, 51, 198, 180, 4}, + {235, 203, 202, 130, 2}, + {34, 214, 60, 195, 11} + }, + { {53, 205, 91, 140, 15}, + {188, 126, 117, 112, 10}, + {243, 29, 171, 58, 12}, + {80, 234, 231, 227, 13} + }, + { {161, 11, 165, 106, 0}, + {16, 163, 42, 74, 13}, + {5, 106, 93, 8, 5}, + {181, 37, 76, 80, 8} + }, + { {43, 135, 74, 96, 8}, + {34, 66, 63, 105, 0}, + {16, 101, 46, 29, 4}, + {9, 111, 196, 36, 4} + }, + { {181, 127, 184, 212, 4}, + {201, 142, 236, 122, 14}, + {34, 177, 223, 234, 13}, + {117, 227, 119, 25, 3} + }, + { {78, 32, 181, 166, 4}, + {155, 129, 66, 145, 7}, + {38, 90, 208, 71, 2}, + {232, 148, 40, 29, 9} + }, + { {97, 216, 151, 242, 12}, + {80, 197, 243, 216, 11}, + {52, 254, 145, 184, 6}, + {209, 188, 250, 48, 10} + }, + { {171, 52, 105, 255, 12}, + {123, 32, 231, 111, 7}, + {63, 249, 98, 205, 5}, + {239, 110, 112, 77, 14} + }, + { {89, 76, 164, 92, 2}, + {10, 189, 128, 232, 12}, + {67, 162, 83, 41, 10}, + {49, 112, 27, 213, 0} + }, + { {250, 28, 45, 46, 2}, + {90, 57, 38, 163, 13}, + {71, 75, 67, 133, 15}, + {188, 86, 73, 197, 10} + }, + { {151, 194, 75, 151, 2}, + {184, 92, 156, 71, 3}, + {78, 157, 36, 62, 9}, + {206, 35, 147, 161, 13} + }, + { {117, 146, 134, 36, 10}, + {200, 217, 59, 192, 0}, + {82, 70, 20, 154, 14}, + {0, 61, 201, 177, 3} + }, + { {28, 170, 254, 153, 10}, + {163, 249, 157, 20, 14}, + {89, 151, 245, 83, 8}, + {114, 139, 153, 252, 5} + }, + { {50, 54, 221, 193, 6}, + {113, 153, 108, 61, 2}, + {104, 59, 182, 196, 12}, + {75, 195, 105, 152, 14} + }, + { {136, 78, 82, 125, 6}, + {42, 116, 202, 62, 8}, + {107, 228, 167, 33, 1}, + {23, 197, 50, 229, 4} + }, + { {87, 113, 168, 199, 14}, + {201, 158, 69, 205, 7}, + {126, 49, 88, 238, 10}, + {235, 58, 39, 153, 3} + }, + { {245, 41, 37, 184, 1}, + {149, 43, 162, 194, 14}, + {129, 218, 73, 74, 15}, + {116, 52, 93, 74, 9} + }, + { {46, 89, 204, 26, 1}, + {230, 167, 164, 1, 9}, + {133, 131, 57, 167, 4}, + {152, 2, 94, 86, 7} + }, + { {127, 165, 138, 179, 1}, + {135, 202, 182, 229, 3}, + {140, 213, 26, 95, 14}, + {202, 118, 213, 62, 1} + }, + { {235, 67, 56, 79, 9}, + {14, 38, 45, 223, 5}, + {159, 33, 204, 45, 7}, + {175, 187, 70, 71, 0} + }, + { {57, 19, 213, 52, 5}, + {126, 139, 234, 80, 0}, + {162, 202, 188, 137, 12}, + {0, 165, 125, 23, 14} + }, + { {215, 155, 26, 59, 5}, + {196, 106, 222, 215, 9}, + {173, 197, 141, 158, 11}, + {158, 183, 181, 98, 3} + }, + { {13, 251, 231, 104, 13}, + {247, 231, 91, 72, 12}, + {177, 110, 125, 251, 0}, + {49, 45, 174, 126, 15} + }, + { {92, 215, 160, 49, 13}, + {198, 142, 219, 164, 4}, + {184, 192, 94, 179, 10}, + {34, 93, 183, 22, 3} + }, + { {185, 200, 247, 164, 11}, + {62, 221, 51, 82, 14}, + {210, 94, 241, 57, 13}, + {116, 172, 203, 183, 12} + }, + { {70, 152, 126, 6, 11}, + {236, 81, 21, 145, 13}, + {214, 7, 225, 150, 2}, + {184, 154, 136, 163, 7} + }, + { {110, 12, 93, 82, 7}, + {182, 17, 228, 185, 9}, + {228, 171, 163, 7, 6}, + {153, 210, 120, 134, 13} + }, + { {91, 197, 103, 187, 4}, + {50, 111, 210, 229, 7}, + {45, 222, 106, 61, 10}, + {234, 116, 191, 100, 12} + }, + { {30, 99, 2, 188, 2}, + {139, 126, 138, 1, 2}, + {67, 212, 12, 103, 8}, + {72, 5, 23, 237, 1} + }, + { {138, 199, 208, 70, 10}, + {42, 150, 25, 59, 1}, + {86, 32, 190, 53, 1}, + {141, 201, 134, 149, 4} + }, + { {166, 95, 226, 50, 6}, + {224, 214, 234, 35, 13}, + {100, 196, 127, 166, 5}, + {188, 69, 118, 176, 7} + }, + { {84, 0, 235, 97, 6}, + {176, 216, 70, 140, 4}, + {104, 109, 112, 2, 10}, + {35, 22, 33, 176, 13} + }, + { {247, 160, 102, 131, 6}, + {161, 89, 112, 199, 7}, + {108, 22, 96, 94, 15}, + {238, 48, 233, 168, 5} + }, + { {45, 208, 175, 223, 6}, + {218, 245, 244, 76, 7}, + {111, 191, 80, 187, 4}, + {227, 34, 250, 245, 11} + }, + { {59, 156, 182, 11, 1}, + {70, 233, 48, 117, 13}, + {141, 6, 211, 157, 12}, + {186, 224, 201, 118, 2} + }, + { {173, 106, 28, 175, 9}, + {143, 37, 47, 86, 11}, + {159, 83, 133, 107, 5}, + {214, 175, 74, 79, 1} + }, + { {147, 182, 46, 251, 5}, + {69, 105, 222, 111, 7}, + {173, 247, 70, 220, 9}, + {239, 103, 185, 106, 2} + }, + { {187, 33, 13, 147, 13}, + {23, 11, 229, 71, 3}, + {188, 155, 8, 77, 13}, + {206, 42, 125, 14, 8} + }, + { {222, 229, 80, 148, 11}, + {175, 30, 145, 179, 2}, + {210, 144, 170, 119, 11}, + {76, 216, 151, 143, 5} + }, + { {160, 205, 20, 121, 11}, + {4, 55, 179, 62, 8}, + {217, 226, 139, 48, 5}, + {23, 204, 222, 194, 0} + }, + { {42, 35, 171, 178, 7}, + {23, 210, 238, 1, 7}, + {228, 221, 92, 69, 4}, + {232, 7, 116, 190, 8} + }, + { {79, 103, 244, 181, 15}, + {175, 151, 203, 245, 6}, + {250, 210, 254, 111, 2}, + {106, 253, 62, 159, 5} + }, + { {88, 168, 21, 129, 8}, + {19, 9, 17, 148, 10}, + {24, 26, 129, 81, 10}, + {82, 152, 137, 12, 8} + }, + { {116, 52, 39, 214, 4}, + {217, 73, 224, 168, 7}, + {38, 190, 66, 194, 14}, + {225, 80, 121, 41, 11} + }, + { {29, 236, 64, 135, 4}, + {171, 12, 80, 100, 11}, + {46, 16, 35, 123, 8}, + {210, 96, 163, 13, 5} + }, + { {137, 42, 179, 160, 2}, + {19, 208, 10, 82, 14}, + {64, 92, 213, 73, 1}, + {116, 165, 0, 188, 8} + }, + { {56, 213, 116, 46, 14}, + {106, 63, 115, 48, 5}, + {119, 66, 234, 177, 12}, + {160, 204, 239, 197, 6} + }, + { {12, 67, 86, 122, 1}, + {166, 103, 138, 24, 1}, + {133, 230, 172, 35, 0}, + {129, 133, 30, 102, 5} + }, + { {93, 179, 17, 203, 1}, + {215, 42, 24, 220, 3}, + {141, 56, 140, 219, 10}, + {195, 177, 133, 78, 11} + }, + { {201, 87, 202, 31, 9}, + {110, 230, 141, 230, 1}, + {159, 133, 62, 169, 3}, + {134, 123, 22, 119, 6} + }, + { {162, 36, 87, 130, 3}, + {53, 81, 32, 51, 3}, + {196, 30, 162, 68, 5}, + {204, 192, 72, 170, 12} + }, + { {41, 172, 233, 143, 3}, + {63, 176, 52, 100, 15}, + {207, 25, 115, 89, 4}, + {242, 98, 192, 223, 12} + }, + { {204, 197, 44, 82, 8}, + {130, 7, 149, 170, 5}, + {20, 163, 74, 51, 3}, + {165, 90, 158, 4, 1} + }, + { {218, 139, 53, 120, 12}, + {18, 43, 219, 155, 12}, + {49, 234, 205, 21, 11}, + {61, 157, 189, 68, 8} + }, + { {213, 180, 185, 227, 6}, + {209, 152, 86, 254, 7}, + {108, 121, 210, 218, 11}, + {231, 246, 161, 152, 11} + }, + { {172, 156, 221, 60, 14}, + {250, 177, 247, 50, 8}, + {115, 203, 179, 147, 5}, + {20, 206, 248, 213, 15} + }, + { {203, 218, 168, 57, 1}, + {70, 164, 158, 199, 12}, + {137, 193, 85, 189, 3}, + {62, 55, 146, 86, 2} + }, + { {25, 134, 37, 107, 1}, + {22, 41, 26, 108, 5}, + {141, 106, 70, 25, 8}, + {163, 101, 137, 70, 8} + }, + { {186, 230, 204, 136, 9}, + {39, 173, 61, 35, 2}, + {145, 19, 54, 117, 13}, + {76, 75, 203, 94, 4} + }, + { {223, 145, 177, 14, 5}, + {222, 170, 80, 211, 5}, + {167, 8, 216, 159, 11}, + {172, 176, 165, 87, 11} + }, + { {161, 169, 213, 167, 5}, + {61, 131, 114, 86, 11}, + {174, 90, 185, 88, 5}, + {214, 164, 236, 27, 12} + }, + { {43, 101, 26, 169, 13}, + {7, 102, 103, 117, 2}, + {185, 85, 138, 109, 4}, + {74, 238, 102, 110, 0} + }, + { {77, 92, 46, 25, 13}, + {198, 101, 197, 228, 12}, + {185, 135, 67, 171, 2}, + {50, 122, 58, 102, 3} + }, + { {91, 22, 55, 54, 11}, + {94, 89, 139, 241, 5}, + {214, 206, 198, 141, 10}, + {168, 253, 25, 167, 10} + }, + { {247, 83, 214, 10, 8}, + {224, 239, 41, 211, 1}, + {21, 6, 188, 174, 15}, + {140, 185, 79, 112, 7} + }, + { {94, 132, 179, 125, 2}, + {154, 248, 146, 189, 4}, + {75, 236, 210, 23, 10}, + {43, 212, 145, 245, 9} + }, + { {252, 228, 58, 159, 2}, + {139, 124, 180, 182, 7}, + {79, 149, 194, 115, 15}, + {230, 210, 211, 237, 1} + }, + { {187, 146, 109, 26, 6}, + {114, 57, 252, 67, 5}, + {101, 139, 100, 157, 13}, + {172, 35, 249, 196, 14} + }, + { {237, 141, 35, 110, 5}, + {158, 98, 114, 234, 13}, + {167, 108, 75, 27, 7}, + {181, 116, 228, 103, 9} + }, + { {106, 248, 70, 243, 7}, + {103, 85, 242, 141, 11}, + {236, 246, 33, 245, 6}, + {219, 20, 250, 174, 6} + }, + { {234, 86, 165, 19, 8}, + {82, 133, 169, 167, 5}, + {28, 138, 86, 165, 7}, + {174, 89, 90, 20, 10} + }, + { {93, 73, 113, 70, 12}, + {186, 14, 65, 216, 13}, + {54, 40, 233, 43, 10}, + {177, 184, 39, 5, 13} + }, + { {71, 3, 96, 236, 10}, + {168, 50, 11, 201, 6}, + {83, 112, 108, 14, 2}, + {105, 61, 4, 193, 5} + }, + { {140, 75, 205, 103, 10}, + {186, 151, 15, 14, 9}, + {94, 107, 61, 35, 1}, + {151, 15, 14, 149, 13} + }, + { {41, 95, 57, 1, 14}, + {82, 22, 109, 116, 12}, + {120, 9, 207, 169, 4}, + {50, 235, 102, 132, 10} + }, + { {246, 181, 119, 233, 7}, + {245, 123, 114, 191, 6}, + {233, 126, 234, 214, 15}, + {111, 212, 237, 234, 15} + }, + { {82, 6, 2, 245, 10}, + {8, 88, 139, 173, 2}, + {90, 244, 6, 4, 10}, + {75, 93, 17, 161, 0} + }, + { {181, 17, 148, 146, 14}, + {192, 155, 225, 82, 3}, + {116, 146, 152, 138, 13}, + {196, 168, 125, 144, 3} + }, + { {72, 226, 153, 171, 7}, + {23, 180, 94, 148, 3}, + {237, 89, 148, 113, 2}, + {194, 151, 162, 222, 8} + }, + { {183, 159, 87, 55, 10}, + {248, 91, 187, 119, 9}, + {94, 206, 175, 158, 13}, + {158, 237, 221, 161, 15} + }, + { {168, 20, 198, 131, 14}, + {98, 209, 97, 38, 3}, + {124, 22, 50, 129, 5}, + {198, 72, 104, 180, 6} + }, + { {45, 169, 0, 88, 3}, + {135, 50, 176, 72, 8}, + {193, 160, 9, 91, 4}, + {17, 32, 212, 206, 1} + }, + { {212, 171, 164, 121, 7}, + {133, 187, 218, 142, 12}, + {233, 226, 93, 82, 11}, + {55, 21, 189, 218, 1} + }, + { {152, 42, 14, 101, 12}, + {11, 73, 79, 14, 8}, + {58, 103, 5, 65, 9}, + {23, 15, 41, 45, 0} + }, + { {244, 128, 204, 193, 5}, + {164, 137, 116, 142, 2}, + {168, 51, 48, 18, 15}, + {71, 18, 233, 18, 5} + }, + { {11, 32, 234, 115, 2}, + {35, 208, 134, 77, 5}, + {76, 229, 112, 77, 0}, + {171, 38, 16, 188, 4} + }, + { {169, 80, 103, 16, 10}, + {114, 85, 161, 66, 4}, + {80, 142, 96, 169, 5}, + {36, 40, 90, 164, 14} + }, + { {4, 134, 226, 238, 1}, + {172, 224, 26, 40, 7}, + {135, 116, 118, 18, 0}, + {225, 69, 128, 115, 5} + }, + { {176, 169, 114, 97, 5}, + {37, 74, 114, 30, 12}, + {168, 100, 233, 80, 13}, + {55, 132, 229, 42, 4} + }, + { {231, 63, 58, 37, 7}, + {205, 82, 110, 247, 12}, + {234, 69, 207, 206, 7}, + {62, 247, 100, 171, 3} + }, + { {23, 226, 5, 185, 8}, + {145, 45, 155, 69, 2}, + {25, 218, 4, 126, 8}, + {74, 45, 155, 72, 9} + }, + { {236, 154, 178, 178, 4}, + {194, 192, 250, 146, 15}, + {36, 212, 213, 147, 7}, + {244, 149, 240, 52, 3} + }, + { {217, 51, 158, 7, 6}, + {75, 219, 76, 214, 1}, + {110, 7, 156, 201, 11}, + {134, 179, 45, 189, 2} + }, + { {235, 136, 116, 249, 9}, + {38, 33, 179, 223, 14}, + {153, 242, 225, 29, 7}, + {127, 188, 216, 70, 4} + }, + { {148, 32, 165, 92, 12}, + {153, 169, 193, 10, 4}, + {51, 170, 80, 66, 9}, + {37, 8, 57, 89, 9} + }, + { {227, 100, 255, 153, 2}, + {49, 245, 164, 247, 6}, + {73, 159, 242, 108, 7}, + {110, 242, 90, 248, 12} + }, + { {138, 76, 184, 240, 10}, + {2, 148, 135, 59, 14}, + {80, 241, 211, 37, 1}, + {125, 206, 18, 148, 0} + }, + { {247, 42, 238, 118, 6}, + {169, 217, 238, 203, 13}, + {102, 231, 117, 78, 15}, + {189, 55, 121, 185, 5} + }, + { {115, 204, 107, 86, 9}, + {60, 76, 181, 233, 13}, + {150, 173, 99, 60, 14}, + {185, 122, 211, 35, 12} + }, + { {201, 156, 166, 165, 9}, + {78, 193, 19, 230, 14}, + {154, 86, 83, 153, 3}, + {118, 124, 136, 55, 2} + }, + { {54, 106, 12, 83, 5}, + {133, 13, 236, 13, 9}, + {172, 163, 5, 102, 12}, + {155, 3, 123, 10, 1} + }, + { {80, 158, 81, 148, 3}, + {124, 24, 152, 176, 10}, + {194, 152, 167, 144, 10}, + {80, 209, 145, 131, 14} + }, + { {186, 109, 34, 125, 1}, + {15, 110, 162, 47, 12}, + {139, 228, 75, 101, 13}, + {63, 68, 87, 111, 0} + }, + { {210, 172, 241, 99, 11}, + {53, 152, 19, 191, 13}, + {220, 104, 243, 84, 11}, + {191, 220, 129, 154, 12} + }, + { {217, 137, 254, 246, 8}, + {42, 203, 151, 218, 15}, + {22, 247, 249, 25, 11}, + {245, 190, 157, 53, 4} + }, + { {100, 23, 7, 187, 2}, + {208, 115, 170, 164, 3}, + {77, 222, 14, 130, 6}, + {194, 85, 92, 224, 11} + }, + { {249, 189, 49, 42, 11}, + {87, 58, 51, 242, 13}, + {213, 72, 203, 217, 15}, + {180, 252, 197, 206, 10} + }, + { {119, 115, 156, 32, 7}, + {197, 159, 110, 209, 0}, + {224, 67, 156, 238, 14}, + {8, 183, 111, 154, 3} + }, + { {104, 191, 141, 27, 15}, + {87, 179, 253, 164, 9}, + {253, 139, 31, 209, 6}, + {146, 91, 252, 222, 10} + }, + { {222, 221, 132, 166, 14}, + {202, 159, 83, 163, 11}, + {118, 82, 27, 183, 11}, + {220, 92, 175, 149, 3} + }, + { {66, 234, 236, 73, 11}, + {37, 181, 29, 141, 12}, + {217, 35, 117, 116, 2}, + {59, 27, 138, 218, 4} + }, + { {48, 43, 104, 213, 3}, + {45, 26, 172, 12, 14}, + {202, 177, 109, 64, 12}, + {115, 3, 85, 139, 4} + }, + { {78, 187, 147, 12, 14}, + {219, 242, 89, 145, 8}, + {115, 12, 157, 215, 2}, + {24, 153, 164, 253, 11} + }, + { {67, 156, 93, 155, 5}, + {116, 33, 212, 245, 11}, + {173, 155, 163, 156, 2}, + {218, 242, 184, 66, 14} + }, + { {225, 252, 146, 40, 13}, + {69, 228, 115, 242, 8}, + {177, 68, 147, 248, 7}, + {20, 252, 226, 122, 2} + }, + { {248, 166, 168, 212, 10}, + {11, 152, 189, 170, 6}, + {82, 177, 86, 81, 15}, + {101, 91, 209, 157, 0} + }, + { {181, 57, 2, 161, 8}, + {193, 74, 35, 70, 10}, + {24, 84, 9, 202, 13}, + {86, 44, 69, 40, 3} + }, + { {177, 99, 71, 94, 14}, + {57, 127, 233, 74, 1}, + {119, 174, 44, 104, 13}, + {133, 41, 127, 233, 12} + }, + { {173, 47, 90, 242, 9}, + {167, 66, 175, 122, 11}, + {148, 245, 175, 75, 5}, + {213, 239, 84, 46, 5} + }, + { {170, 181, 220, 133, 0}, + {107, 131, 52, 55, 2}, + {10, 19, 186, 213, 5}, + {78, 194, 204, 29, 6} + }, + { {179, 236, 37, 120, 6}, + {17, 61, 242, 107, 12}, + {97, 234, 67, 124, 13}, + {61, 100, 251, 200, 8} + }, + { {71, 153, 196, 64, 5}, + {228, 131, 80, 201, 8}, + {160, 34, 57, 158, 2}, + {25, 48, 172, 18, 7} + }, + { {139, 198, 141, 192, 4}, + {18, 133, 92, 107, 2}, + {32, 59, 22, 61, 1}, + {77, 99, 170, 20, 8} + }, + { {112, 47, 21, 221, 14}, + {25, 59, 233, 188, 10}, + {123, 186, 143, 64, 14}, + {83, 217, 125, 201, 8} + }, + { {180, 34, 147, 218, 9}, + {149, 232, 169, 26, 3}, + {149, 188, 148, 66, 13}, + {197, 137, 81, 122, 9} + }, + { {26, 202, 208, 56, 2}, + {34, 188, 154, 17, 8}, + {65, 192, 181, 53, 8}, + {24, 133, 147, 212, 4} + }, + { {129, 51, 99, 151, 1}, + {125, 66, 136, 70, 7}, + {142, 156, 108, 200, 1}, + {230, 33, 20, 43, 14} + }, + { {14, 105, 42, 215, 0}, + {139, 70, 132, 13, 15}, + {14, 181, 73, 103, 0}, + {251, 2, 22, 45, 1} + }, + { {138, 208, 92, 91, 9}, + {102, 37, 149, 31, 1}, + {157, 163, 160, 181, 1}, + {143, 138, 154, 70, 6} + }, + { {133, 230, 84, 193, 11}, + {165, 21, 25, 126, 2}, + {216, 50, 166, 122, 1}, + {71, 233, 138, 138, 5} + }, + { {61, 10, 194, 179, 11}, + {166, 216, 171, 68, 11}, + {220, 212, 53, 11, 12}, + {210, 45, 81, 182, 5} + }, + { {75, 191, 160, 85, 2}, + {75, 146, 152, 237, 12}, + {74, 160, 95, 221, 2}, + {59, 113, 148, 157, 2} + }, + { {114, 45, 222, 48, 6}, + {33, 219, 230, 177, 8}, + {96, 199, 187, 68, 14}, + {24, 214, 125, 184, 4} + }, + { {54, 157, 26, 205, 2}, + {200, 122, 52, 61, 10}, + {75, 53, 139, 150, 12}, + {91, 194, 197, 225, 3} + }, + { {162, 217, 148, 230, 0}, + {72, 135, 50, 27, 11}, + {6, 114, 153, 180, 5}, + {221, 132, 206, 17, 2} + }, + { {95, 173, 145, 57, 13}, + {151, 170, 211, 245, 8}, + {185, 200, 155, 95, 10}, + {26, 252, 181, 94, 9} + }, + { {87, 83, 90, 233, 6}, + {224, 126, 78, 221, 2}, + {105, 117, 172, 174, 10}, + {75, 183, 39, 224, 7} + }, + { {95, 94, 241, 212, 6}, + {250, 156, 200, 249, 14}, + {98, 184, 247, 175, 10}, + {121, 241, 51, 149, 15} + }, + { {235, 195, 230, 172, 12}, + {42, 231, 123, 195, 6}, + {51, 86, 124, 61, 7}, + {108, 61, 238, 117, 4} + }, + { {10, 153, 200, 53, 13}, + {110, 130, 215, 5, 8}, + {186, 193, 57, 149, 0}, + {26, 14, 180, 23, 6} + }, + { {84, 99, 228, 154, 5}, + {165, 175, 200, 128, 7}, + {165, 146, 124, 98, 10}, + {224, 17, 63, 90, 5} + }, + { {37, 103, 104, 147, 10}, + {161, 22, 173, 100, 7}, + {92, 145, 110, 106, 4}, + {226, 107, 86, 136, 5} + }, + { {171, 237, 55, 6, 4}, + {27, 71, 112, 115, 13}, + {38, 14, 203, 125, 5}, + {188, 224, 238, 45, 8} + }, + { {89, 189, 222, 217, 4}, + {99, 235, 212, 252, 10}, + {41, 183, 187, 217, 10}, + {83, 242, 189, 124, 6} + }, + { {100, 250, 4, 192, 9}, + {197, 5, 57, 136, 10}, + {144, 50, 5, 242, 6}, + {81, 25, 202, 10, 3} + }, + { {37, 14, 68, 231, 5}, + {172, 1, 106, 108, 11}, + {174, 114, 39, 10, 4}, + {211, 101, 104, 3, 5} + }, + { {229, 138, 55, 75, 12}, + {144, 97, 121, 222, 13}, + {61, 46, 197, 26, 7}, + {183, 185, 232, 96, 9} + }, + { {36, 202, 165, 116, 1}, + {156, 133, 186, 8, 12}, + {130, 234, 85, 50, 4}, + {49, 5, 218, 19, 9} + }, + { {228, 44, 246, 88, 8}, + {161, 225, 161, 186, 12}, + {17, 166, 243, 66, 7}, + {53, 216, 88, 120, 5} + }, + { {110, 162, 56, 83, 8}, + {131, 0, 189, 157, 5}, + {28, 161, 196, 87, 6}, + {171, 155, 208, 12, 1} + }, + { {247, 88, 19, 2, 5}, + {212, 76, 96, 211, 9}, + {164, 12, 129, 174, 15}, + {156, 176, 99, 34, 11} + }, + { {12, 235, 51, 187, 13}, + {151, 102, 219, 20, 15}, + {189, 220, 205, 115, 0}, + {242, 141, 182, 110, 9} + }, + { {120, 106, 232, 103, 0}, + {43, 140, 46, 140, 13}, + {14, 97, 117, 97, 14}, + {179, 23, 67, 29, 4} + }, + { {240, 222, 140, 150, 9}, + {76, 141, 189, 162, 11}, + {150, 147, 23, 176, 15}, + {212, 91, 219, 19, 2} + }, + { {38, 200, 30, 254, 6}, + {136, 117, 246, 25, 11}, + {103, 247, 129, 54, 4}, + {217, 134, 250, 225, 1} + }, + { {105, 244, 137, 8, 6}, + {83, 180, 116, 224, 0}, + {97, 9, 18, 249, 6}, + {0, 114, 226, 220, 10} + }, + { {63, 191, 146, 164, 13}, + {207, 202, 123, 113, 10}, + {178, 84, 159, 223, 12}, + {88, 237, 229, 63, 3} + }, + { {214, 85, 90, 118, 0}, + {232, 78, 134, 187, 1}, + {6, 229, 170, 166, 11}, + {141, 214, 23, 33, 7} + }, + { {156, 202, 226, 54, 13}, + {174, 204, 219, 2, 13}, + {182, 196, 117, 51, 9}, + {180, 13, 179, 55, 5} + }, + { {178, 86, 193, 106, 3}, + {116, 188, 42, 43, 1}, + {197, 104, 54, 164, 13}, + {141, 69, 67, 210, 14} + }, + { {215, 138, 199, 34, 1}, + {180, 201, 26, 195, 9}, + {132, 78, 53, 30, 11}, + {156, 53, 137, 50, 13} + }, + { {45, 193, 254, 38, 2}, + {170, 215, 54, 80, 5}, + {70, 71, 248, 59, 4}, + {160, 166, 206, 181, 5} + }, + { {175, 32, 81, 9, 0}, + {179, 32, 32, 87, 0}, + {9, 8, 160, 79, 5}, + {14, 160, 64, 76, 13} + }, + { {147, 44, 8, 175, 14}, + {9, 56, 71, 103, 11}, + {127, 81, 3, 76, 9}, + {222, 110, 33, 201, 0} + }, + { {83, 254, 202, 119, 5}, + {109, 204, 222, 237, 9}, + {174, 229, 55, 252, 10}, + {155, 119, 179, 59, 6} + }, + { {93, 41, 36, 151, 10}, + {139, 27, 129, 196, 15}, + {94, 146, 73, 75, 10}, + {242, 56, 29, 141, 1} + }, + { {47, 16, 26, 34, 11}, + {198, 80, 39, 81, 1}, + {212, 69, 128, 143, 4}, + {136, 174, 64, 166, 3} + }, + { {198, 173, 22, 177, 4}, + {129, 67, 210, 183, 10}, + {40, 214, 139, 86, 3}, + {94, 212, 188, 40, 1} + }, + { {210, 236, 164, 178, 0}, + {1, 141, 146, 163, 15}, + {4, 210, 83, 116, 11}, + {252, 84, 155, 24, 0} + }, + { {166, 250, 96, 61, 4}, + {233, 36, 250, 7, 12}, + {43, 192, 101, 246, 5}, + {62, 5, 242, 73, 7} + }, + { {48, 87, 195, 182, 9}, + {124, 206, 171, 32, 3}, + {150, 220, 62, 160, 12}, + {192, 77, 87, 51, 14} + }, + { {232, 23, 181, 77, 2}, + {90, 179, 40, 190, 4}, + {75, 42, 222, 129, 7}, + {39, 209, 76, 213, 10} + }, + { {64, 171, 252, 62, 0}, + {41, 163, 158, 144, 13}, + {7, 195, 253, 80, 2}, + {176, 151, 156, 89, 4} + }, + { {245, 58, 209, 110, 7}, + {253, 184, 106, 218, 9}, + {231, 104, 181, 202, 15}, + {149, 181, 97, 219, 15} + }, + { {54, 243, 160, 110, 8}, + {201, 174, 59, 9, 5}, + {23, 96, 92, 246, 12}, + {169, 13, 199, 89, 3} + }, + { {39, 120, 46, 65, 8}, + {193, 69, 37, 77, 12}, + {24, 39, 65, 238, 4}, + {59, 42, 74, 40, 3} + }, + { {62, 144, 98, 20, 6}, + {234, 88, 240, 1, 4}, + {98, 132, 96, 151, 12}, + {40, 0, 241, 165, 7} + }, + { {1, 253, 122, 212, 14}, + {105, 86, 213, 120, 14}, + {114, 181, 235, 248, 0}, + {113, 234, 182, 169, 6} + }, + { {200, 118, 91, 198, 4}, + {123, 68, 76, 186, 3}, + {38, 61, 166, 225, 3}, + {197, 211, 34, 45, 14} + }, + { {173, 61, 130, 21, 15}, + {207, 210, 225, 102, 8}, + {250, 132, 27, 203, 5}, + {22, 104, 116, 191, 3} + }, + { {150, 49, 122, 39, 14}, + {233, 90, 71, 23, 5}, + {126, 69, 232, 198, 9}, + {174, 142, 37, 169, 7} + }, + { {5, 248, 19, 202, 6}, + {209, 116, 80, 88, 11}, + {101, 60, 129, 250, 0}, + {209, 160, 162, 232, 11} + }, + { {14, 122, 220, 34, 14}, + {227, 149, 79, 17, 9}, + {116, 67, 181, 231, 0}, + {152, 143, 42, 156, 7} + }, + { {191, 82, 196, 165, 0}, + {234, 141, 42, 71, 2}, + {10, 82, 52, 175, 13}, + {78, 37, 75, 21, 7} + }, + { {114, 79, 106, 226, 14}, + {32, 94, 111, 169, 15}, + {116, 117, 111, 36, 14}, + {249, 95, 103, 160, 4} + }, + { {84, 15, 215, 234, 8}, + {176, 235, 11, 184, 11}, + {21, 126, 191, 2, 10}, + {209, 221, 13, 112, 13} + }, + { {156, 150, 47, 116, 1}, + {222, 73, 158, 42, 4}, + {130, 239, 70, 147, 9}, + {37, 71, 153, 39, 11} + }, + { {38, 64, 66, 176, 1}, + {164, 68, 162, 1, 2}, + {128, 212, 32, 38, 4}, + {72, 4, 82, 34, 5} + }, + { {25, 124, 183, 117, 4}, + {91, 205, 194, 124, 12}, + {42, 238, 211, 233, 8}, + {51, 228, 59, 61, 10} + }, + { {31, 43, 184, 80, 13}, + {135, 138, 205, 89, 12}, + {176, 161, 221, 79, 8}, + {57, 171, 53, 30, 1} + }, + { {200, 125, 108, 42, 13}, + {103, 39, 71, 162, 13}, + {181, 67, 107, 225, 3}, + {180, 94, 46, 78, 6} + }, + { {220, 141, 45, 237, 14}, + {154, 59, 87, 174, 14}, + {123, 123, 75, 19, 11}, + {119, 94, 173, 197, 9} + }, + { {203, 107, 1, 115, 8}, + {19, 6, 139, 207, 9}, + {28, 232, 13, 109, 3}, + {159, 61, 22, 12, 8} + }, + { {190, 66, 16, 54, 8}, + {138, 12, 171, 19, 1}, + {22, 192, 132, 39, 13}, + {140, 141, 83, 5, 1} + }, + { {187, 70, 34, 167, 7}, + {14, 92, 106, 103, 7}, + {238, 84, 70, 45, 13}, + {238, 101, 99, 167, 0} + }, + { {115, 50, 38, 239, 8}, + {73, 105, 43, 205, 7}, + {31, 118, 68, 204, 14}, + {235, 61, 73, 105, 2} + }, + { {73, 51, 173, 227, 9}, + {87, 131, 15, 204, 7}, + {156, 123, 92, 201, 2}, + {227, 63, 12, 30, 10} + }, + { {31, 28, 137, 108, 7}, + {222, 184, 70, 105, 8}, + {227, 105, 19, 143, 8}, + {25, 102, 33, 215, 11} + }, + { {137, 118, 101, 171, 10}, + {115, 53, 11, 102, 7}, + {93, 90, 102, 233, 1}, + {230, 109, 10, 204, 14} + }, + { {146, 244, 107, 161, 2}, + {113, 92, 22, 39, 6}, + {72, 93, 98, 244, 9}, + {110, 70, 131, 168, 14} + }, + { {41, 121, 129, 118, 7}, + {95, 150, 226, 72, 9}, + {230, 232, 25, 233, 4}, + {145, 36, 118, 159, 10} + }, + { {173, 112, 44, 248, 13}, + {199, 37, 231, 74, 6}, + {177, 243, 64, 235, 5}, + {101, 46, 122, 78, 3} + }, + { {71, 49, 152, 185, 0}, + {193, 162, 134, 213, 2}, + {9, 209, 152, 206, 2}, + {74, 182, 20, 88, 3} + }, + { {236, 46, 142, 6, 11}, + {143, 209, 45, 162, 9}, + {214, 7, 23, 67, 7}, + {148, 91, 72, 191, 1} + }, + { {54, 161, 214, 152, 0}, + {161, 235, 176, 17, 2}, + {1, 150, 184, 86, 12}, + {72, 128, 221, 120, 5} + }, + { {165, 206, 134, 80, 15}, + {132, 213, 249, 106, 8}, + {240, 166, 23, 58, 5}, + {21, 105, 250, 178, 1} + }, + { {99, 120, 112, 52, 8}, + {105, 4, 163, 209, 12}, + {18, 192, 225, 236, 6}, + {56, 188, 82, 9, 6} + }, + { {243, 171, 65, 212, 6}, + {57, 26, 248, 203, 10}, + {98, 184, 45, 92, 15}, + {93, 49, 245, 137, 12} + }, + { {230, 3, 42, 139, 5}, + {132, 98, 108, 135, 7}, + {173, 21, 76, 6, 7}, + {238, 19, 100, 98, 1} + }, + { {76, 17, 238, 199, 3}, + {238, 211, 4, 140, 7}, + {206, 55, 120, 131, 2}, + {227, 18, 12, 183, 7} + }, + { {38, 29, 229, 13, 6}, + {248, 179, 96, 37, 12}, + {107, 10, 123, 134, 4}, + {58, 64, 108, 209, 15} + }, + { {41, 102, 22, 227, 2}, + {3, 85, 42, 124, 3}, + {76, 118, 134, 105, 4}, + {195, 229, 74, 172, 0} + }, + { {55, 207, 241, 160, 10}, + {176, 158, 59, 113, 14}, + {80, 88, 255, 62, 12}, + {120, 237, 199, 144, 13} + }, + { {196, 87, 132, 122, 11}, + {196, 183, 139, 170, 1}, + {213, 226, 30, 162, 3}, + {133, 93, 30, 210, 3} + }, + { {119, 110, 82, 255, 9}, + {173, 108, 171, 253, 11}, + {159, 244, 167, 110, 14}, + {219, 253, 83, 107, 5} + }, + { {152, 8, 25, 187, 9}, + {22, 40, 135, 22, 11}, + {157, 217, 129, 1, 9}, + {214, 142, 17, 70, 8} + }, + { {80, 203, 43, 93, 8}, + {24, 110, 157, 140, 12}, + {27, 173, 77, 48, 10}, + {51, 27, 151, 97, 8} + }, + { {139, 105, 24, 6, 3}, + {15, 22, 4, 83, 9}, + {198, 1, 137, 109, 1}, + {156, 162, 6, 143, 0} + }, + { {18, 217, 194, 206, 9}, + {108, 238, 17, 9, 11}, + {151, 52, 57, 180, 8}, + {217, 8, 135, 115, 6} + }, + { {21, 140, 46, 115, 8}, + {128, 73, 151, 108, 13}, + {28, 231, 67, 26, 8}, + {179, 110, 153, 32, 1} + }, + { {107, 132, 1, 94, 10}, + {26, 48, 177, 233, 1}, + {87, 168, 2, 29, 6}, + {137, 120, 208, 197, 8} + }, + { {103, 105, 93, 185, 15}, + {181, 55, 231, 213, 10}, + {249, 219, 169, 110, 6}, + {90, 190, 126, 202, 13} + }, + { {62, 114, 116, 92, 1}, + {239, 45, 168, 25, 4}, + {131, 162, 228, 231, 12}, + {41, 129, 91, 79, 7} + }, + { {1, 202, 26, 87, 1}, + {12, 68, 156, 92, 9}, + {142, 165, 133, 56, 0}, + {147, 163, 146, 35, 0} + }, + { {34, 231, 3, 111, 5}, + {29, 102, 122, 45, 1}, + {175, 108, 14, 116, 4}, + {139, 69, 230, 107, 8} + }, + { {213, 220, 237, 174, 1}, + {252, 173, 22, 226, 15}, + {135, 91, 115, 186, 11}, + {244, 118, 139, 83, 15} + }, + { {128, 149, 50, 197, 11}, + {76, 82, 17, 62, 6}, + {218, 52, 202, 144, 1}, + {103, 200, 132, 163, 2} + }, + { {247, 228, 233, 213, 4}, + {185, 140, 244, 239, 6}, + {42, 185, 114, 126, 15}, + {111, 114, 243, 25, 13} + }, + { {89, 216, 79, 156, 11}, + {126, 125, 149, 192, 10}, + {211, 159, 33, 185, 10}, + {80, 58, 155, 231, 14} + }, + { {244, 46, 208, 133, 1}, + {173, 136, 40, 182, 10}, + {138, 16, 183, 66, 15}, + {86, 209, 65, 27, 5} + }, + { {93, 180, 169, 31, 10}, + {219, 184, 149, 228, 5}, + {95, 137, 82, 219, 10}, + {162, 122, 145, 221, 11} + }, + { {13, 88, 229, 177, 15}, + {246, 149, 195, 68, 14}, + {248, 218, 113, 171, 0}, + {114, 44, 58, 150, 15} + }, + { {105, 251, 159, 164, 3}, + {95, 215, 62, 208, 10}, + {194, 95, 157, 249, 6}, + {80, 183, 206, 191, 10} + }, + { {41, 186, 81, 246, 0}, + {123, 0, 186, 88, 11}, + {6, 248, 165, 217, 4}, + {209, 165, 208, 13, 14} + }, + { {88, 124, 103, 66, 7}, + {119, 93, 64, 168, 13}, + {228, 46, 99, 225, 10}, + {177, 80, 43, 174, 14} + }, + { {216, 185, 2, 30, 13}, + {79, 106, 209, 130, 9}, + {183, 132, 9, 209, 11}, + {148, 24, 181, 111, 2} + }, + { {187, 89, 68, 73, 14}, + {98, 63, 97, 79, 8}, + {121, 34, 41, 173, 13}, + {31, 40, 111, 196, 6} + }, + { {170, 35, 31, 53, 10}, + {27, 83, 175, 23, 0}, + {90, 207, 140, 69, 5}, + {14, 143, 92, 173, 8} + }, + { {13, 149, 148, 63, 4}, + {202, 163, 210, 116, 1}, + {47, 194, 154, 155, 0}, + {130, 228, 188, 85, 3} + }, + { {222, 196, 97, 37, 7}, + {190, 28, 82, 167, 4}, + {234, 72, 98, 55, 11}, + {46, 84, 163, 135, 13} + }, + { {5, 161, 105, 48, 13}, + {181, 2, 215, 64, 4}, + {176, 201, 104, 90, 0}, + {32, 46, 180, 10, 13} + }, + { {226, 81, 46, 181, 8}, + {72, 71, 167, 135, 6}, + {26, 215, 72, 164, 7}, + {110, 30, 94, 33, 2} + }, + { {171, 72, 226, 220, 0}, + {42, 228, 160, 75, 14}, + {3, 180, 113, 45, 5}, + {125, 32, 82, 117, 4} + }, + { {12, 182, 132, 148, 15}, + {207, 145, 217, 32, 2}, + {242, 146, 22, 211, 0}, + {64, 73, 184, 159, 3} + }, + { {146, 122, 107, 91, 1}, + {117, 108, 140, 15, 13}, + {141, 173, 101, 228, 9}, + {191, 3, 19, 106, 14} + }, + { {187, 144, 243, 69, 0}, + {122, 200, 48, 95, 4}, + {10, 44, 240, 157, 13}, + {47, 160, 193, 53, 14} + }, + { {99, 46, 143, 173, 0}, + {25, 225, 46, 229, 10}, + {11, 95, 23, 76, 6}, + {90, 119, 72, 121, 8} + }, + { {194, 238, 175, 42, 7}, + {21, 245, 94, 163, 13}, + {229, 79, 87, 116, 3}, + {188, 87, 170, 250, 8} + }, + { {30, 178, 175, 57, 13}, + {215, 233, 223, 5, 4}, + {185, 207, 84, 215, 8}, + {42, 15, 185, 126, 11} + }, + { {81, 21, 72, 197, 2}, + {104, 26, 4, 236, 2}, + {74, 49, 42, 136, 10}, + {67, 114, 5, 129, 6} + }, + { {70, 147, 213, 222, 4}, + {248, 163, 216, 153, 3}, + {39, 186, 188, 150, 2}, + {201, 145, 188, 81, 15} + }, + { {136, 142, 145, 97, 13}, + {22, 128, 91, 62, 8}, + {184, 104, 151, 17, 1}, + {23, 205, 160, 22, 8} + }, + { {104, 131, 111, 244, 15}, + {62, 83, 255, 136, 6}, + {242, 255, 108, 17, 6}, + {97, 31, 252, 167, 12} + }, + { {81, 189, 46, 68, 1}, + {77, 75, 20, 232, 12}, + {130, 39, 75, 216, 10}, + {49, 114, 141, 43, 2} + }, + { {122, 10, 196, 14, 9}, + {46, 169, 41, 129, 9}, + {151, 2, 53, 5, 14}, + {152, 25, 73, 87, 4} + }, + { {139, 183, 98, 180, 10}, + {107, 82, 155, 99, 6}, + {82, 212, 110, 221, 1}, + {108, 109, 148, 173, 6} + }, + { {28, 175, 131, 161, 10}, + {147, 218, 27, 36, 10}, + {88, 92, 31, 83, 8}, + {82, 77, 133, 188, 9} + }, + { {194, 112, 11, 187, 13}, + {85, 100, 199, 135, 3}, + {189, 221, 0, 228, 3}, + {206, 30, 50, 106, 10} + }, + { {251, 46, 208, 154, 10}, + {35, 184, 169, 243, 11}, + {85, 144, 183, 77, 15}, + {220, 249, 81, 220, 4} + }, + { {248, 82, 2, 148, 14}, + {74, 92, 233, 130, 2}, + {114, 148, 4, 161, 15}, + {68, 25, 115, 165, 2} + }, + { {122, 161, 4, 202, 6}, + {3, 59, 112, 137, 3}, + {101, 50, 8, 85, 14}, + {201, 16, 237, 204, 0} + }, + { {106, 150, 210, 188, 11}, + {110, 240, 187, 177, 2}, + {211, 212, 182, 149, 6}, + {72, 221, 208, 247, 6} + }, + { {40, 242, 236, 117, 14}, + {107, 149, 255, 12, 4}, + {122, 227, 116, 241, 4}, + {35, 15, 250, 157, 6} + }, + { {96, 112, 101, 235, 4}, + {113, 37, 98, 140, 7}, + {45, 122, 96, 224, 6}, + {227, 20, 106, 72, 14} + }, + { {122, 217, 179, 168, 12}, + {82, 238, 115, 145, 14}, + {49, 92, 217, 181, 14}, + {120, 156, 231, 116, 10} + }, + { {53, 114, 214, 203, 15}, + {229, 253, 105, 92, 3}, + {253, 54, 180, 234, 12}, + {195, 169, 107, 250, 7} + }, + { {212, 38, 239, 184, 15}, + {181, 249, 207, 162, 6}, + {241, 223, 118, 66, 11}, + {100, 95, 57, 250, 13} + }, + { {100, 238, 77, 186, 10}, + {177, 53, 191, 160, 11}, + {85, 219, 39, 114, 6}, + {208, 95, 218, 200, 13} + }, + { {152, 61, 122, 143, 1}, + {111, 106, 4, 54, 15}, + {143, 21, 235, 193, 9}, + {246, 194, 5, 111, 6} + }, + { {140, 163, 31, 73, 9}, + {151, 99, 29, 30, 0}, + {153, 47, 140, 83, 1}, + {7, 139, 140, 110, 9} + }, + { {2, 84, 87, 180, 6}, + {120, 85, 194, 49, 2}, + {98, 222, 162, 164, 0}, + {72, 196, 58, 161, 14} + }, + { {213, 58, 228, 20, 9}, + {237, 137, 137, 194, 12}, + {146, 130, 117, 202, 11}, + {52, 57, 25, 27, 7} + }, + { {157, 60, 100, 57, 6}, + {227, 57, 194, 102, 12}, + {105, 194, 99, 203, 9}, + {54, 100, 57, 204, 7} + }, + { {121, 83, 50, 238, 5}, + {78, 110, 106, 216, 7}, + {167, 116, 204, 169, 14}, + {225, 181, 103, 103, 2} + }, + { {112, 136, 77, 102, 4}, + {56, 9, 118, 136, 9}, + {38, 107, 33, 16, 14}, + {145, 22, 233, 1, 12} + }, + { {29, 211, 82, 141, 8}, + {234, 110, 25, 84, 2}, + {27, 20, 172, 187, 8}, + {66, 169, 135, 101, 7} + }, + { {216, 229, 29, 36, 6}, + {27, 31, 86, 178, 0}, + {98, 75, 138, 113, 11}, + {4, 214, 175, 141, 8} + }, + { {214, 2, 156, 163, 8}, + {128, 137, 15, 151, 3}, + {28, 83, 148, 6, 11}, + {206, 159, 9, 16, 1} + }, + { {217, 74, 182, 16, 12}, + {2, 205, 201, 210, 12}, + {48, 134, 213, 41, 11}, + {52, 185, 59, 52, 0} + }, + { {118, 107, 184, 136, 0}, + {129, 174, 44, 145, 14}, + {1, 17, 221, 102, 14}, + {120, 147, 71, 88, 1} + }, + { {253, 12, 107, 49, 9}, + {182, 72, 167, 230, 12}, + {152, 205, 99, 11, 15}, + {54, 126, 81, 38, 13} + }, + { {59, 255, 139, 157, 1}, + {95, 238, 188, 101, 10}, + {139, 157, 31, 253, 12}, + {90, 99, 215, 127, 10} + }, + { {231, 134, 244, 212, 14}, + {168, 145, 249, 251, 6}, + {114, 178, 246, 30, 7}, + {109, 249, 248, 145, 5} + }, + { {212, 230, 185, 183, 9}, + {157, 140, 159, 182, 7}, + {158, 217, 214, 114, 11}, + {230, 223, 147, 27, 9} + }, + { {207, 168, 187, 54, 9}, + {159, 192, 151, 211, 13}, + {150, 205, 209, 95, 3}, + {188, 190, 144, 63, 9} + }, + { {32, 164, 54, 108, 6}, + {9, 113, 114, 56, 4}, + {99, 102, 194, 80, 4}, + {33, 196, 232, 233, 0} + }, + { {92, 239, 185, 83, 6}, + {147, 158, 220, 188, 13}, + {108, 169, 223, 115, 10}, + {179, 211, 183, 156, 9} + }, + { {15, 6, 182, 96, 5}, + {134, 193, 74, 121, 4}, + {160, 102, 214, 15, 0}, + {41, 229, 40, 54, 1} + }, + { {187, 81, 210, 94, 7}, + {110, 254, 224, 91, 1}, + {231, 164, 184, 173, 13}, + {141, 160, 119, 247, 6} + }, + { {247, 226, 98, 100, 1}, + {173, 76, 58, 203, 4}, + {130, 100, 100, 126, 15}, + {45, 53, 195, 43, 5} + }, + { {129, 253, 169, 133, 5}, + {93, 134, 84, 102, 14}, + {170, 25, 91, 248, 1}, + {118, 98, 166, 27, 10} + }, + { {34, 65, 219, 197, 9}, + {60, 198, 37, 29, 2}, + {154, 61, 184, 36, 4}, + {75, 138, 70, 51, 12} + }, + { {86, 251, 75, 40, 0}, + {241, 110, 30, 129, 8}, + {1, 77, 45, 246, 10}, + {24, 23, 135, 104, 15} + }, + { {237, 166, 76, 109, 0}, + {171, 33, 62, 238, 0}, + {11, 99, 38, 91, 7}, + {7, 119, 200, 77, 5} + }, + { {206, 123, 8, 15, 4}, + {203, 38, 76, 135, 9}, + {47, 1, 13, 231, 3}, + {158, 19, 38, 77, 3} + }, + { {97, 146, 238, 126, 9}, + {108, 225, 191, 200, 5}, + {151, 231, 116, 152, 6}, + {161, 63, 216, 115, 6} + }, + { {148, 63, 245, 18, 4}, + {241, 139, 200, 50, 13}, + {36, 138, 255, 194, 9}, + {180, 193, 61, 24, 15} + }, + { {136, 179, 190, 173, 15}, + {79, 243, 95, 22, 6}, + {251, 87, 220, 209, 1}, + {102, 143, 172, 255, 2} + }, + { {155, 39, 229, 141, 0}, + {59, 171, 8, 103, 6}, + {11, 26, 126, 77, 9}, + {110, 97, 13, 93, 12} + }, + { {190, 11, 69, 253, 2}, + {186, 59, 170, 15, 10}, + {75, 250, 45, 7, 13}, + {95, 5, 93, 197, 13} + }, + { {105, 251, 58, 114, 10}, + {67, 86, 191, 216, 13}, + {84, 229, 205, 249, 6}, + {177, 191, 214, 172, 2} + }, + { {97, 56, 58, 131, 2}, + {65, 80, 36, 212, 15}, + {76, 21, 193, 200, 6}, + {242, 178, 64, 168, 2} + }, + { {102, 136, 99, 155, 3}, + {180, 112, 176, 133, 15}, + {205, 156, 97, 22, 6}, + {250, 16, 208, 226, 13} + }, + { {84, 208, 28, 153, 2}, + {192, 61, 148, 148, 2}, + {73, 147, 128, 178, 10}, + {66, 146, 155, 192, 3} + }, + { {144, 177, 12, 2, 10}, + {65, 27, 21, 2, 1}, + {84, 3, 8, 208, 9}, + {132, 10, 141, 136, 2} + }, + { {246, 103, 252, 109, 1}, + {173, 175, 46, 191, 4}, + {139, 99, 254, 102, 15}, + {47, 215, 79, 91, 5} + }, + { {122, 205, 117, 129, 3}, + {54, 31, 48, 181, 14}, + {200, 26, 235, 53, 14}, + {122, 208, 207, 134, 12} + }, + { {58, 189, 7, 246, 1}, + {95, 75, 178, 41, 11}, + {134, 254, 11, 213, 12}, + {217, 68, 221, 47, 10} + }, + { {84, 48, 18, 71, 3}, + {205, 88, 0, 156, 1}, + {206, 36, 128, 194, 10}, + {131, 144, 1, 171, 3} + }, + { {6, 119, 148, 213, 8}, + {201, 135, 137, 61, 2}, + {26, 178, 158, 230, 0}, + {75, 201, 30, 25, 3} + }, + { {168, 85, 177, 32, 1}, + {86, 134, 34, 50, 4}, + {128, 72, 218, 161, 5}, + {36, 196, 70, 22, 10} + }, + { {156, 180, 215, 253, 11}, + {255, 249, 147, 62, 2}, + {219, 254, 178, 211, 9}, + {71, 204, 153, 255, 15} + }, + { {49, 161, 147, 230, 10}, + {25, 218, 51, 88, 3}, + {86, 124, 152, 88, 12}, + {193, 172, 197, 185, 8} + }, + { {61, 163, 183, 109, 4}, + {155, 235, 122, 92, 4}, + {43, 110, 220, 91, 12}, + {35, 165, 237, 125, 9} + }, + { {65, 83, 241, 224, 4}, + {112, 134, 74, 216, 6}, + {32, 120, 252, 168, 2}, + {97, 181, 38, 16, 14} + }, + { {85, 68, 51, 91, 5}, + {148, 108, 192, 252, 5}, + {173, 172, 194, 42, 10}, + {163, 240, 51, 98, 9} + }, + { {146, 246, 18, 73, 13}, + {69, 108, 89, 63, 0}, + {185, 36, 134, 244, 9}, + {15, 201, 163, 106, 2} + }, + { {38, 154, 53, 248, 10}, + {208, 49, 187, 25, 14}, + {81, 250, 197, 150, 4}, + {121, 141, 216, 192, 11} + }, + { {4, 38, 119, 137, 6}, + {177, 113, 72, 52, 6}, + {105, 30, 230, 66, 0}, + {98, 193, 40, 232, 13} + }, + { {148, 135, 205, 198, 15}, + {188, 155, 93, 42, 3}, + {246, 59, 62, 18, 9}, + {197, 75, 173, 147, 13} + }, + { {78, 167, 231, 217, 12}, + {179, 227, 217, 173, 6}, + {57, 190, 126, 87, 2}, + {107, 89, 188, 124, 13} + }, + { {46, 54, 115, 69, 11}, + {255, 80, 41, 61, 4}, + {218, 44, 230, 199, 4}, + {43, 201, 64, 175, 15} + }, + { {58, 94, 143, 48, 4}, + {82, 205, 238, 33, 8}, + {32, 207, 23, 165, 12}, + {24, 71, 123, 52, 10} + }, + { {118, 217, 250, 167, 7}, + {236, 222, 118, 149, 15}, + {238, 85, 249, 182, 14}, + {250, 150, 231, 179, 7} + }, + { {100, 228, 234, 29, 15}, + {173, 244, 245, 164, 4}, + {251, 133, 114, 114, 6}, + {34, 90, 242, 251, 5} + }, + { {18, 167, 180, 52, 13}, + {13, 139, 219, 49, 4}, + {178, 194, 222, 84, 8}, + {40, 205, 189, 27, 0} + }, + { {73, 115, 217, 85, 3}, + {127, 150, 140, 220, 0}, + {202, 169, 188, 233, 2}, + {3, 179, 22, 159, 14} + }, + { {205, 180, 24, 151, 5}, + {207, 0, 212, 246, 3}, + {174, 145, 130, 219, 3}, + {198, 242, 176, 15, 3} + }, + { {139, 218, 118, 210, 14}, + {98, 85, 217, 91, 15}, + {116, 182, 229, 189, 1}, + {253, 169, 186, 164, 6} + }, + { {245, 74, 77, 236, 8}, + {184, 45, 47, 202, 10}, + {19, 123, 37, 42, 15}, + {85, 63, 75, 65, 13} + }, + { {129, 22, 22, 248, 10}, + {64, 113, 139, 122, 2}, + {81, 246, 134, 136, 1}, + {69, 237, 24, 224, 2} + }, + { {64, 54, 244, 230, 11}, + {109, 145, 11, 184, 7}, + {214, 114, 246, 192, 2}, + {225, 221, 8, 155, 6} + }, + { {39, 54, 40, 42, 6}, + {193, 48, 110, 97, 5}, + {101, 65, 70, 206, 4}, + {168, 103, 96, 200, 3} + }, + { {164, 19, 210, 41, 15}, + {228, 242, 107, 22, 0}, + {249, 68, 188, 130, 5}, + {6, 141, 100, 242, 7} + }, + { {144, 31, 228, 193, 7}, + {100, 155, 72, 46, 14}, + {232, 50, 127, 128, 9}, + {119, 65, 45, 146, 6} + }, + { {154, 191, 206, 86, 14}, + {107, 219, 221, 43, 9}, + {118, 167, 63, 213, 9}, + {157, 75, 189, 189, 6} + }, + { {45, 50, 91, 158, 15}, + {255, 112, 237, 80, 3}, + {247, 157, 164, 203, 4}, + {192, 171, 112, 239, 15} + }, + { {217, 144, 31, 106, 9}, + {86, 105, 23, 218, 1}, + {149, 111, 128, 153, 11}, + {133, 190, 137, 102, 10} + }, + { {58, 166, 79, 173, 14}, + {59, 121, 127, 37, 2}, + {123, 95, 38, 85, 12}, + {74, 79, 233, 237, 12} + }, + { {101, 40, 141, 96, 11}, + {149, 145, 39, 200, 8}, + {208, 107, 17, 74, 6}, + {17, 62, 72, 154, 9} + }, + { {10, 83, 233, 96, 11}, + {118, 150, 15, 9, 4}, + {208, 105, 124, 165, 0}, + {41, 15, 6, 150, 14} + }, + { {233, 224, 156, 62, 4}, + {11, 165, 246, 210, 1}, + {39, 195, 144, 121, 7}, + {132, 182, 250, 93, 0} + }, + { {77, 155, 120, 99, 13}, + {230, 2, 95, 220, 13}, + {188, 97, 237, 155, 2}, + {179, 191, 164, 6, 7} + }, + { {217, 139, 180, 131, 3}, + {6, 155, 24, 214, 15}, + {204, 18, 221, 25, 11}, + {246, 177, 141, 150, 0} + }, + { {52, 177, 129, 237, 7}, + {221, 186, 114, 12, 2}, + {235, 120, 24, 210, 12}, + {67, 4, 229, 219, 11} + }, + { {91, 55, 63, 73, 0}, + {83, 107, 12, 253, 4}, + {9, 47, 206, 205, 10}, + {43, 243, 13, 108, 10} + }, + { {133, 114, 183, 185, 1}, + {213, 229, 138, 86, 6}, + {137, 222, 212, 234, 1}, + {102, 165, 26, 122, 11} + }, + { {44, 93, 5, 106, 4}, + {210, 39, 98, 40, 9}, + {37, 106, 11, 163, 4}, + {145, 68, 110, 68, 11} + }, + { {228, 103, 18, 150, 13}, + {141, 70, 233, 178, 3}, + {182, 148, 142, 98, 7}, + {196, 217, 118, 43, 1} + }, + { {42, 8, 214, 247, 8}, + {42, 193, 163, 29, 11}, + {30, 246, 177, 5, 4}, + {219, 140, 88, 53, 4} + }, + { {69, 41, 73, 198, 1}, + {189, 2, 4, 200, 11}, + {134, 57, 41, 74, 2}, + {209, 50, 4, 11, 13} + }, + { {37, 1, 236, 91, 7}, + {164, 179, 228, 76, 5}, + {237, 163, 120, 10, 4}, + {163, 34, 124, 210, 5} + }, + { {184, 245, 71, 121, 12}, + {115, 111, 243, 46, 0}, + {57, 238, 42, 241, 13}, + {7, 76, 255, 108, 14} + }, + { {163, 183, 234, 75, 3}, + {101, 242, 60, 111, 5}, + {205, 37, 126, 220, 5}, + {175, 99, 196, 250, 6} + }, + { {68, 61, 107, 155, 4}, + {241, 98, 196, 164, 15}, + {45, 157, 107, 194, 2}, + {242, 82, 52, 104, 15} + }, + { {190, 45, 203, 132, 8}, + {187, 202, 37, 35, 10}, + {18, 29, 59, 71, 13}, + {92, 74, 69, 61, 13} + }, + { {181, 31, 196, 126, 12}, + {232, 171, 235, 106, 9}, + {55, 226, 63, 138, 13}, + {149, 109, 125, 81, 7} + }, + { {111, 56, 207, 152, 8}, + {243, 225, 165, 193, 10}, + {17, 159, 49, 207, 6}, + {88, 58, 88, 124, 15} + }, + { {144, 242, 254, 147, 4}, + {97, 205, 220, 22, 7}, + {44, 151, 244, 240, 9}, + {230, 131, 187, 56, 6} + }, + { {7, 253, 84, 10, 11}, + {229, 55, 17, 113, 9}, + {213, 2, 171, 254, 0}, + {152, 232, 142, 202, 7} + }, + { {238, 83, 195, 208, 8}, + {242, 198, 169, 139, 2}, + {16, 188, 60, 167, 7}, + {77, 25, 86, 52, 15} + }, + { {247, 9, 142, 188, 10}, + {136, 251, 167, 195, 10}, + {83, 215, 25, 14, 15}, + {92, 62, 93, 241, 1} + }, + { {101, 70, 124, 48, 9}, + {164, 5, 175, 240, 4}, + {144, 195, 230, 42, 6}, + {32, 255, 90, 2, 5} + }, + { {174, 111, 215, 151, 14}, + {187, 215, 233, 55, 11}, + {126, 158, 191, 103, 5}, + {222, 201, 126, 189, 13} + }, + { {139, 196, 115, 158, 9}, + {62, 100, 145, 115, 7}, + {151, 156, 226, 61, 1}, + {236, 232, 146, 103, 12} + }, + { {151, 25, 48, 254, 4}, + {200, 42, 194, 91, 15}, + {39, 240, 201, 142, 9}, + {253, 164, 53, 65, 3} + }, + { {154, 117, 45, 245, 7}, + {95, 31, 198, 47, 6}, + {234, 251, 74, 229, 9}, + {111, 70, 63, 143, 10} + }, + { {189, 71, 254, 83, 14}, + {162, 223, 237, 126, 5}, + {124, 167, 254, 43, 13}, + {167, 235, 127, 180, 5} + }, + { {111, 21, 48, 146, 8}, + {194, 2, 161, 241, 7}, + {20, 144, 202, 143, 6}, + {232, 248, 84, 4, 3} + }, + { {93, 225, 204, 72, 9}, + {167, 175, 21, 200, 0}, + {145, 35, 56, 123, 10}, + {1, 58, 143, 94, 5} + }, + { {17, 28, 227, 220, 13}, + {124, 232, 193, 104, 14}, + {179, 188, 115, 136, 8}, + {113, 104, 49, 115, 14} + }, + { {164, 199, 225, 87, 12}, + {184, 134, 249, 46, 5}, + {62, 168, 126, 50, 5}, + {167, 73, 246, 17, 13} + }, + { {85, 38, 145, 17, 2}, + {145, 152, 136, 244, 0}, + {72, 136, 150, 74, 10}, + {2, 241, 17, 152, 9} + }, + { {157, 101, 17, 128, 5}, + {151, 14, 64, 114, 2}, + {160, 24, 138, 107, 9}, + {68, 224, 39, 14, 9} + }, + { {102, 99, 193, 203, 3}, + {181, 182, 40, 141, 3}, + {205, 56, 60, 102, 6}, + {203, 17, 70, 218, 13} + }, + { {191, 197, 148, 0, 2}, + {130, 159, 48, 115, 0}, + {64, 2, 154, 63, 13}, + {12, 224, 207, 148, 1} + }, + { {58, 53, 0, 56, 7}, + {71, 58, 226, 33, 0}, + {225, 192, 10, 197, 12}, + {8, 68, 117, 206, 2} + }, + { {118, 212, 246, 121, 12}, + {224, 237, 243, 189, 4}, + {57, 230, 242, 182, 14}, + {43, 220, 251, 112, 7} + }, + { {68, 111, 38, 103, 11}, + {141, 87, 11, 172, 13}, + {222, 102, 79, 98, 2}, + {179, 93, 14, 171, 1} + }, + { {23, 174, 89, 233, 7}, + {181, 56, 94, 125, 10}, + {233, 121, 167, 94, 8}, + {91, 231, 161, 202, 13} + }, + { {54, 19, 45, 99, 14}, + {208, 27, 111, 13, 5}, + {124, 107, 76, 134, 12}, + {171, 15, 109, 128, 11} + }, + { {142, 7, 73, 43, 15}, + {182, 50, 79, 39, 1}, + {253, 73, 46, 7, 1}, + {142, 79, 36, 198, 13} + }, + { {24, 138, 205, 223, 1}, + {62, 169, 156, 12, 11}, + {143, 187, 53, 17, 8}, + {211, 3, 153, 87, 12} + }, + { {237, 144, 82, 208, 2}, + {226, 80, 176, 218, 2}, + {64, 180, 160, 155, 7}, + {69, 176, 208, 164, 7} + }, + { {91, 107, 208, 31, 5}, + {47, 174, 200, 213, 9}, + {175, 128, 189, 109, 10}, + {154, 177, 55, 95, 4} + }, + { {132, 164, 208, 112, 14}, + {161, 144, 211, 58, 0}, + {112, 224, 178, 82, 1}, + {5, 204, 176, 152, 5} + }, + { {201, 178, 163, 175, 4}, + {91, 224, 90, 198, 7}, + {47, 92, 84, 217, 3}, + {230, 53, 160, 125, 10} + }, + { {171, 24, 133, 47, 12}, + {90, 161, 99, 71, 9}, + {63, 74, 17, 141, 5}, + {158, 44, 104, 85, 10} + }, + { {157, 233, 24, 57, 0}, + {131, 46, 150, 86, 8}, + {9, 193, 137, 123, 9}, + {22, 166, 151, 76, 1} + }, + { {53, 118, 238, 158, 0}, + {233, 237, 172, 96, 7}, + {7, 151, 118, 234, 12}, + {224, 99, 91, 121, 7} + }, + { {232, 208, 13, 33, 0}, + {82, 5, 54, 134, 0}, + {8, 75, 0, 177, 7}, + {6, 22, 202, 4, 10} + }, + { {112, 155, 90, 149, 12}, + {104, 74, 253, 148, 10}, + {58, 149, 173, 144, 14}, + {82, 155, 245, 33, 6} + }, + { {169, 2, 42, 49, 4}, + {2, 64, 238, 70, 4}, + {40, 197, 68, 9, 5}, + {38, 39, 112, 36, 0} + }, + { {160, 62, 162, 185, 12}, + {65, 224, 235, 38, 14}, + {57, 212, 87, 192, 5}, + {118, 77, 112, 120, 2} + }, + { {30, 88, 158, 197, 14}, + {202, 221, 69, 29, 10}, + {122, 55, 145, 167, 8}, + {91, 138, 43, 181, 3} + }, + { {116, 71, 14, 237, 13}, + {140, 111, 111, 172, 2}, + {187, 119, 14, 34, 14}, + {67, 95, 111, 99, 1} + }, + { {54, 189, 68, 21, 9}, + {237, 11, 177, 37, 8}, + {154, 130, 43, 214, 12}, + {26, 72, 221, 11, 7} + }, + { {183, 248, 210, 81, 6}, + {225, 220, 240, 95, 8}, + {104, 164, 177, 254, 13}, + {31, 160, 243, 184, 7} + }, + { {220, 222, 75, 250, 5}, + {246, 108, 222, 170, 11}, + {165, 253, 39, 179, 11}, + {213, 87, 179, 102, 15} + }, + { {158, 218, 153, 195, 0}, + {210, 140, 28, 31, 11}, + {12, 57, 149, 183, 9}, + {223, 131, 131, 20, 11} + }, + { {143, 251, 152, 168, 11}, + {199, 182, 31, 83, 10}, + {209, 81, 157, 255, 1}, + {92, 175, 134, 222, 3} + }, + { {154, 199, 119, 231, 1}, + {62, 79, 26, 63, 7}, + {142, 126, 238, 53, 9}, + {239, 197, 143, 39, 12} + }, + { {141, 10, 83, 203, 3}, + {182, 112, 8, 94, 11}, + {205, 60, 165, 11, 1}, + {215, 161, 0, 230, 13} + }, + { {201, 237, 29, 206, 13}, + {31, 39, 85, 250, 11}, + {183, 59, 139, 121, 3}, + {213, 250, 174, 79, 8} + }, + { {194, 56, 83, 94, 10}, + {121, 112, 129, 155, 9}, + {87, 172, 161, 196, 3}, + {157, 152, 16, 233, 14} + }, + { {238, 74, 166, 67, 7}, + {134, 213, 104, 143, 13}, + {236, 38, 85, 39, 7}, + {191, 17, 106, 182, 1} + }, + { {205, 131, 69, 161, 14}, + {178, 19, 91, 198, 2}, + {120, 90, 44, 27, 3}, + {70, 61, 172, 132, 13} + }, + { {90, 25, 224, 90, 14}, + {98, 186, 193, 137, 13}, + {117, 160, 121, 133, 10}, + {185, 24, 53, 212, 6} + }, + { {150, 215, 128, 210, 5}, + {196, 142, 216, 43, 3}, + {164, 176, 30, 182, 9}, + {205, 65, 183, 18, 3} + }, + { {226, 200, 133, 24, 5}, + {20, 165, 240, 131, 8}, + {161, 138, 17, 52, 7}, + {28, 16, 250, 82, 8} + }, + { {197, 49, 79, 141, 11}, + {253, 115, 5, 198, 2}, + {219, 31, 40, 202, 3}, + {70, 58, 12, 235, 15} + }, + { {164, 124, 55, 238, 1}, + {221, 101, 34, 58, 15}, + {135, 126, 195, 226, 5}, + {245, 196, 74, 107, 11} + }, + { {29, 130, 198, 248, 12}, + {162, 233, 219, 72, 2}, + {49, 246, 52, 27, 8}, + {65, 45, 185, 116, 5} + }, + { {50, 92, 92, 108, 8}, + {104, 45, 39, 57, 8}, + {19, 99, 163, 164, 12}, + {25, 206, 75, 65, 6} + }, + { {93, 157, 27, 174, 0}, + {218, 106, 22, 240, 11}, + {7, 93, 139, 155, 10}, + {208, 246, 133, 101, 11} + }, + { {149, 73, 224, 117, 14}, + {168, 158, 195, 78, 12}, + {122, 224, 121, 42, 9}, + {55, 44, 55, 145, 5} + }, + { {138, 104, 69, 193, 5}, + {55, 5, 64, 15, 10}, + {168, 58, 33, 101, 1}, + {95, 0, 42, 14, 12} + }, + { {173, 249, 241, 15, 14}, + {251, 182, 113, 86, 13}, + {127, 8, 249, 251, 5}, + {182, 168, 230, 221, 15} + }, + { {149, 206, 243, 11, 0}, + {176, 236, 24, 118, 13}, + {13, 12, 247, 58, 9}, + {182, 225, 131, 112, 13} + }, + { {113, 147, 59, 32, 13}, + {84, 74, 127, 208, 4}, + {176, 77, 204, 152, 14}, + {32, 191, 229, 34, 10} + }, + { {31, 13, 227, 166, 15}, + {190, 218, 67, 97, 15}, + {246, 92, 123, 15, 8}, + {248, 108, 37, 183, 13} + }, + { {89, 111, 91, 186, 6}, + {51, 126, 206, 240, 11}, + {101, 221, 175, 105, 10}, + {208, 247, 55, 236, 12} + }, + { {205, 97, 217, 243, 4}, + {179, 134, 198, 222, 3}, + {44, 249, 184, 107, 3}, + {199, 182, 54, 28, 13} + }, + { {164, 120, 173, 50, 8}, + {209, 133, 167, 2, 13}, + {20, 203, 81, 226, 5}, + {180, 14, 90, 24, 11} + }, + { {218, 222, 64, 18, 15}, + {102, 28, 217, 163, 9}, + {244, 128, 39, 181, 11}, + {156, 89, 179, 134, 6} + }, + { {209, 87, 100, 55, 0}, + {104, 15, 138, 230, 5}, + {14, 194, 110, 168, 11}, + {166, 117, 31, 1, 6} + }, + { {58, 43, 245, 210, 2}, + {51, 155, 168, 25, 15}, + {68, 186, 253, 69, 12}, + {249, 129, 93, 156, 12} + }, + { {35, 3, 227, 72, 13}, + {52, 226, 105, 73, 4}, + {177, 44, 124, 12, 4}, + {41, 41, 100, 114, 12} + }, + { {65, 185, 191, 154, 9}, + {85, 227, 149, 208, 15}, + {149, 159, 217, 216, 2}, + {240, 186, 156, 122, 10} + }, + { {90, 78, 147, 238, 3}, + {30, 252, 10, 185, 11}, + {199, 124, 151, 37, 10}, + {217, 213, 3, 247, 8} + }, + { {70, 43, 68, 81, 2}, + {161, 19, 136, 141, 8}, + {72, 162, 45, 70, 2}, + {27, 17, 28, 136, 5} + }, + { {78, 1, 113, 156, 1}, + {190, 34, 128, 145, 6}, + {131, 152, 232, 7, 2}, + {104, 144, 20, 71, 13} + }, + { {115, 160, 217, 19, 7}, + {53, 152, 244, 213, 1}, + {236, 137, 176, 92, 14}, + {138, 178, 241, 154, 12} + }, + { {147, 43, 65, 109, 13}, + {61, 42, 75, 79, 8}, + {187, 104, 45, 76, 9}, + {31, 45, 37, 75, 12} + }, + { {255, 75, 97, 90, 7}, + {182, 62, 232, 203, 13}, + {229, 168, 109, 47, 15}, + {189, 49, 119, 198, 13} + }, + { {44, 136, 15, 26, 12}, + {146, 97, 245, 0, 9}, + {53, 143, 1, 19, 4}, + {144, 10, 248, 100, 9} + }, + { {111, 59, 182, 170, 1}, + {199, 227, 42, 209, 15}, + {133, 86, 221, 207, 6}, + {248, 181, 76, 126, 3} + }, + { {71, 109, 33, 174, 6}, + {153, 54, 66, 225, 15}, + {103, 88, 75, 110, 2}, + {248, 116, 38, 201, 9} + }, + { {249, 194, 144, 104, 8}, + {2, 172, 59, 218, 0}, + {17, 96, 148, 57, 15}, + {5, 189, 195, 84, 0} + }, + { {118, 129, 17, 33, 0}, + {144, 10, 50, 149, 0}, + {8, 72, 136, 22, 14}, + {10, 148, 197, 0, 9} + }, + { {98, 52, 88, 245, 5}, + {109, 0, 230, 189, 2}, + {170, 241, 162, 196, 6}, + {75, 214, 112, 11, 6} + }, + { {243, 25, 25, 225, 15}, + {84, 26, 103, 223, 10}, + {248, 121, 137, 140, 15}, + {95, 190, 101, 130, 10} + }, + { {239, 71, 167, 221, 1}, + {158, 231, 168, 239, 6}, + {139, 190, 94, 47, 7}, + {111, 113, 94, 119, 9} + }, + { {198, 199, 119, 128, 8}, + {176, 71, 25, 179, 6}, + {16, 30, 238, 54, 3}, + {108, 217, 142, 32, 13} + }, + { {238, 201, 111, 9, 6}, + {178, 119, 116, 135, 12}, + {105, 15, 105, 55, 7}, + {62, 18, 238, 228, 13} + }, + { {155, 60, 155, 218, 15}, + {87, 248, 197, 123, 11}, + {245, 189, 147, 205, 9}, + {221, 234, 49, 254, 10} + }, + { {111, 212, 50, 87, 3}, + {206, 84, 176, 253, 5}, + {206, 164, 194, 191, 6}, + {171, 240, 210, 167, 3} + }, + { {96, 231, 208, 170, 9}, + {37, 166, 59, 176, 3}, + {149, 80, 190, 112, 6}, + {192, 221, 198, 90, 4} + }, + { {6, 81, 205, 163, 4}, + {240, 135, 70, 5, 3}, + {44, 91, 56, 166, 0}, + {202, 6, 46, 16, 15} + }, + { {226, 172, 192, 78, 7}, + {45, 176, 112, 171, 9}, + {231, 32, 51, 84, 7}, + {157, 80, 224, 219, 4} + }, + { {148, 96, 209, 162, 2}, + {177, 156, 2, 18, 3}, + {68, 88, 176, 98, 9}, + {196, 132, 3, 152, 13} + }, + { {130, 183, 155, 23, 7}, + {93, 210, 220, 55, 1}, + {238, 141, 158, 212, 1}, + {142, 195, 180, 187, 10} + }, + { {78, 177, 137, 244, 11}, + {223, 146, 151, 137, 2}, + {210, 249, 24, 215, 2}, + {73, 30, 148, 159, 11} + }, + { {203, 249, 195, 177, 14}, + {115, 214, 211, 199, 10}, + {120, 220, 57, 253, 3}, + {94, 60, 182, 188, 14} + }, + { {111, 108, 100, 132, 1}, + {175, 5, 32, 225, 14}, + {130, 18, 99, 111, 6}, + {120, 112, 74, 15, 5} + }, + { {229, 78, 80, 162, 14}, + {160, 20, 107, 242, 11}, + {116, 80, 167, 42, 7}, + {212, 253, 98, 128, 5} + }, + { {99, 47, 48, 242, 3}, + {5, 18, 170, 249, 15}, + {196, 240, 207, 76, 6}, + {249, 245, 84, 138, 0} + }, + { {179, 187, 61, 230, 12}, + {89, 11, 127, 91, 15}, + {54, 123, 205, 220, 13}, + {253, 175, 237, 9, 10} + }, + { {99, 49, 245, 56, 3}, + {117, 179, 162, 209, 4}, + {193, 202, 248, 204, 6}, + {40, 180, 92, 218, 14} + }, + { {188, 161, 218, 254, 5}, + {175, 234, 246, 26, 3}, + {167, 245, 184, 83, 13}, + {197, 134, 245, 127, 5} + }, + { {209, 129, 108, 171, 9}, + {36, 43, 23, 198, 7}, + {157, 83, 104, 24, 11}, + {230, 62, 141, 66, 4} + }, + { {13, 132, 7, 172, 10}, + {154, 113, 19, 96, 2}, + {83, 94, 2, 27, 0}, + {64, 108, 136, 229, 9} + }, + { {121, 78, 29, 126, 5}, + {30, 45, 238, 248, 9}, + {167, 235, 135, 41, 14}, + {145, 247, 123, 71, 8} + }, + { {146, 222, 101, 68, 2}, + {120, 29, 24, 43, 12}, + {66, 42, 103, 180, 9}, + {61, 65, 139, 129, 14} + }, + { {101, 20, 151, 148, 9}, + {220, 193, 161, 240, 2}, + {146, 158, 146, 138, 6}, + {64, 248, 88, 51, 11} + }, + { {51, 247, 81, 113, 2}, + {113, 30, 186, 125, 0}, + {72, 232, 174, 252, 12}, + {11, 229, 215, 136, 14} + }, + { {60, 156, 233, 25, 5}, + {246, 168, 244, 36, 12}, + {169, 137, 115, 147, 12}, + {50, 66, 241, 86, 15} + }, + { {126, 203, 129, 215, 10}, + {154, 158, 185, 141, 11}, + {94, 184, 29, 55, 14}, + {219, 25, 215, 149, 9} + }, + { {237, 225, 64, 23, 8}, + {171, 6, 177, 198, 1}, + {30, 128, 40, 123, 7}, + {134, 56, 214, 13, 5} + }, + { {192, 155, 14, 226, 12}, + {64, 67, 95, 138, 11}, + {52, 119, 13, 144, 3}, + {213, 31, 172, 32, 2} + }, + { {20, 230, 31, 119, 4}, + {153, 77, 222, 60, 1}, + {46, 239, 134, 114, 8}, + {131, 199, 187, 41, 9} + }, + { {109, 51, 102, 73, 10}, + {227, 115, 41, 204, 4}, + {89, 38, 108, 203, 6}, + {35, 57, 76, 236, 7} + }, + { {134, 93, 208, 136, 0}, + {224, 166, 0, 51, 10}, + {1, 16, 187, 166, 1}, + {92, 192, 6, 80, 7} + }, + { {247, 33, 75, 251, 8}, + {177, 106, 167, 207, 3}, + {29, 253, 40, 78, 15}, + {207, 62, 85, 104, 13} + }, + { {216, 185, 194, 226, 2}, + {99, 218, 18, 138, 11}, + {68, 116, 57, 209, 11}, + {213, 20, 133, 188, 6} + }, + { {153, 167, 7, 55, 15}, + {31, 91, 219, 102, 1}, + {254, 206, 14, 89, 9}, + {134, 109, 189, 175, 8} + }, + { {192, 236, 249, 12, 9}, + {61, 164, 21, 178, 12}, + {147, 9, 243, 112, 3}, + {52, 218, 130, 91, 12} + }, + { {187, 139, 38, 224, 1}, + {6, 75, 58, 75, 14}, + {128, 118, 77, 29, 13}, + {125, 37, 205, 38, 0} + }, + { {5, 103, 143, 202, 12}, + {145, 231, 77, 104, 3}, + {53, 63, 30, 106, 0}, + {193, 107, 46, 120, 9} + }, + { {51, 180, 131, 131, 4}, + {81, 200, 112, 101, 3}, + {44, 28, 18, 220, 12}, + {202, 96, 225, 56, 10} + }, + { {235, 157, 132, 184, 3}, + {70, 179, 178, 227, 10}, + {193, 210, 27, 157, 7}, + {92, 116, 220, 214, 2} + }, + { {124, 162, 119, 234, 2}, + {179, 121, 58, 152, 7}, + {69, 126, 228, 83, 14}, + {225, 149, 201, 236, 13} + }, + { {2, 175, 95, 102, 11}, + {61, 83, 31, 57, 9}, + {214, 111, 175, 84, 0}, + {153, 207, 140, 171, 12} + }, + { {74, 103, 140, 18, 2}, + {3, 151, 140, 161, 1}, + {68, 131, 30, 101, 2}, + {136, 83, 30, 156, 0} + }, + { {18, 21, 146, 191, 3}, + {76, 250, 130, 53, 3}, + {207, 212, 154, 132, 8}, + {202, 196, 21, 243, 2} + }, + { {204, 130, 180, 140, 9}, + {142, 161, 25, 146, 6}, + {147, 18, 212, 19, 3}, + {100, 153, 136, 87, 1} + }, + { {119, 203, 233, 15, 12}, + {184, 174, 125, 197, 13}, + {63, 9, 125, 62, 14}, + {186, 59, 231, 81, 13} + }, + { {94, 247, 215, 147, 1}, + {247, 207, 152, 181, 3}, + {140, 158, 190, 247, 10}, + {202, 209, 159, 62, 15} + }, + { {17, 121, 71, 153, 5}, + {117, 111, 192, 68, 10}, + {169, 158, 41, 232, 8}, + {82, 32, 63, 106, 14} + }, + { {52, 77, 95, 151, 0}, + {184, 79, 164, 52, 11}, + {14, 159, 171, 34, 12}, + {210, 194, 95, 33, 13} + }, + { {223, 47, 18, 231, 2}, + {139, 90, 10, 255, 11}, + {78, 116, 143, 79, 11}, + {223, 245, 5, 173, 1} + }, + { {17, 66, 119, 179, 7}, + {52, 93, 202, 84, 7}, + {236, 222, 228, 40, 8}, + {226, 165, 59, 162, 12} + }, + { {11, 143, 167, 61, 5}, + {30, 227, 218, 101, 12}, + {171, 206, 95, 29, 0}, + {58, 101, 188, 119, 8} + }, + { {54, 225, 111, 66, 3}, + {181, 95, 52, 9, 5}, + {196, 47, 104, 118, 12}, + {169, 2, 207, 170, 13} + }, + { {5, 116, 2, 183, 14}, + {201, 84, 195, 100, 3}, + {126, 212, 2, 234, 0}, + {194, 108, 50, 169, 3} + }, + { {195, 66, 66, 58, 7}, + {36, 116, 202, 195, 1}, + {229, 196, 36, 44, 3}, + {140, 53, 50, 226, 4} + }, + { {117, 140, 52, 218, 11}, + {132, 57, 177, 248, 15}, + {213, 178, 195, 26, 14}, + {241, 248, 217, 194, 1} + }, + { {103, 220, 97, 97, 4}, + {240, 4, 114, 237, 12}, + {40, 104, 99, 190, 6}, + {59, 116, 226, 0, 15} + }, + { {41, 178, 34, 6, 8}, + {75, 64, 57, 64, 5}, + {22, 4, 68, 217, 4}, + {160, 41, 192, 45, 2} + }, + { {113, 1, 175, 15, 10}, + {24, 251, 37, 196, 5}, + {95, 15, 88, 8, 14}, + {162, 58, 77, 241, 8} + }, + { {84, 210, 43, 199, 5}, + {220, 76, 92, 140, 7}, + {174, 61, 68, 178, 10}, + {227, 19, 163, 35, 11} + }, + { {144, 139, 163, 175, 9}, + {28, 234, 27, 6, 15}, + {159, 92, 93, 16, 9}, + {246, 13, 133, 115, 8} + }, + { {235, 104, 201, 181, 1}, + {63, 132, 166, 199, 10}, + {138, 217, 49, 109, 7}, + {94, 54, 82, 31, 12} + }, + { {38, 5, 212, 66, 12}, + {160, 131, 97, 57, 1}, + {52, 34, 186, 6, 4}, + {137, 200, 108, 16, 5} + }, + { {106, 184, 139, 127, 4}, + {91, 224, 246, 141, 9}, + {47, 237, 17, 213, 6}, + {155, 22, 240, 125, 10} + }, + { {32, 124, 118, 87, 13}, + {109, 69, 225, 60, 13}, + {190, 166, 227, 224, 4}, + {179, 200, 122, 43, 6} + }, + { {55, 121, 64, 210, 0}, + {225, 14, 160, 73, 11}, + {4, 176, 41, 238, 12}, + {217, 32, 87, 8, 7} + }, + { {172, 46, 183, 123, 6}, + {147, 241, 234, 62, 13}, + {109, 238, 215, 67, 5}, + {183, 197, 120, 252, 9} + }, + { {62, 135, 49, 142, 12}, + {154, 42, 121, 49, 7}, + {55, 24, 206, 23, 12}, + {232, 201, 229, 69, 9} + }, + { {71, 175, 234, 26, 10}, + {161, 242, 157, 225, 13}, + {85, 133, 127, 94, 2}, + {184, 123, 148, 248, 5} + }, + { {70, 222, 53, 183, 4}, + {216, 5, 218, 181, 15}, + {46, 218, 199, 182, 2}, + {250, 213, 186, 1, 11} + }, + { {72, 163, 107, 161, 0}, + {51, 66, 30, 132, 6}, + {8, 93, 108, 81, 2}, + {98, 23, 132, 44, 12} + }, + { {240, 113, 21, 54, 11}, + {93, 31, 163, 146, 1}, + {214, 202, 136, 224, 15}, + {132, 156, 95, 139, 10} + }, + { {150, 63, 135, 237, 4}, + {217, 235, 74, 47, 10}, + {43, 126, 31, 198, 9}, + {95, 69, 45, 121, 11} + }, + { {216, 58, 185, 150, 5}, + {95, 136, 204, 146, 15}, + {166, 153, 213, 193, 11}, + {244, 147, 49, 31, 10} + }, + { {123, 46, 181, 160, 15}, + {23, 153, 107, 241, 14}, + {240, 90, 215, 77, 14}, + {120, 253, 105, 158, 8} + }, + { {182, 173, 250, 243, 2}, + {161, 218, 182, 63, 15}, + {76, 245, 251, 86, 13}, + {255, 198, 213, 184, 5} + }, + { {135, 224, 122, 78, 10}, + {169, 116, 21, 91, 5}, + {87, 37, 224, 126, 1}, + {173, 170, 130, 233, 5} + }, + { {123, 69, 24, 244, 14}, + {10, 30, 231, 249, 2}, + {114, 241, 138, 45, 14}, + {73, 254, 119, 133, 0} + }, + { {144, 100, 163, 115, 8}, + {17, 204, 131, 46, 5}, + {28, 236, 82, 96, 9}, + {167, 76, 19, 56, 8} + }, + { {60, 36, 44, 161, 2}, + {131, 25, 38, 36, 6}, + {72, 83, 66, 67, 12}, + {98, 70, 73, 140, 1} + }, + { {150, 178, 92, 74, 4}, + {225, 41, 92, 27, 1}, + {37, 35, 164, 214, 9}, + {141, 131, 169, 72, 7} + }, + { {139, 1, 216, 58, 12}, + {34, 162, 199, 83, 1}, + {53, 193, 184, 13, 1}, + {140, 174, 52, 84, 4} + }, + { {229, 28, 206, 178, 5}, + {228, 193, 230, 226, 11}, + {164, 215, 51, 138, 7}, + {212, 118, 120, 50, 7} + }, + { {72, 15, 88, 219, 0}, + {34, 34, 140, 188, 11}, + {13, 177, 175, 1, 2}, + {211, 211, 20, 68, 4} + }, + { {186, 226, 149, 134, 7}, + {31, 157, 120, 19, 3}, + {230, 26, 148, 117, 13}, + {204, 129, 235, 159, 8} + }, + { {137, 252, 15, 186, 0}, + {83, 101, 150, 98, 11}, + {5, 223, 3, 249, 1}, + {212, 102, 154, 108, 10} + }, + { {221, 6, 71, 94, 9}, + {190, 105, 137, 234, 1}, + {151, 174, 38, 11, 11}, + {133, 121, 25, 103, 13} + }, + { {247, 254, 70, 180, 12}, + {233, 77, 251, 227, 10}, + {50, 214, 39, 254, 15}, + {92, 125, 251, 41, 7} + }, + { {67, 206, 240, 38, 13}, + {44, 132, 91, 241, 13}, + {182, 64, 247, 60, 2}, + {184, 253, 162, 19, 4} + }, + { {99, 147, 88, 90, 2}, + {96, 50, 188, 217, 1}, + {69, 161, 172, 156, 6}, + {137, 179, 212, 192, 6} + }, + { {135, 180, 4, 69, 6}, + {201, 17, 80, 111, 0}, + {106, 34, 2, 222, 1}, + {15, 96, 168, 137, 3} + }, + { {79, 164, 124, 190, 10}, + {171, 49, 151, 241, 7}, + {87, 211, 226, 95, 2}, + {232, 254, 152, 205, 5} + }, + { {118, 25, 249, 85, 10}, + {248, 154, 165, 157, 12}, + {90, 169, 249, 134, 14}, + {59, 154, 85, 145, 15} + }, + { {81, 185, 161, 51, 0}, + {81, 138, 146, 196, 13}, + {12, 200, 89, 216, 10}, + {178, 52, 149, 24, 10} + }, + { {21, 51, 99, 124, 7}, + {253, 122, 202, 72, 4}, + {227, 236, 108, 202, 8}, + {33, 37, 53, 235, 15} + }, + { {117, 135, 169, 114, 10}, + {144, 154, 191, 232, 5}, + {84, 233, 94, 26, 14}, + {161, 127, 213, 144, 9} + }, + { {96, 222, 17, 98, 2}, + {80, 20, 58, 184, 9}, + {68, 104, 135, 176, 6}, + {145, 213, 194, 128, 10} + }, + { {125, 76, 230, 138, 14}, + {162, 253, 97, 224, 15}, + {117, 22, 115, 43, 14}, + {240, 120, 107, 244, 5} + }, + { {175, 19, 94, 129, 12}, + {226, 67, 109, 87, 2}, + {56, 23, 172, 143, 5}, + {78, 171, 108, 36, 7} + }, + { {49, 201, 182, 71, 12}, + {8, 207, 113, 92, 13}, + {62, 38, 217, 56, 12}, + {179, 168, 239, 49, 0} + }, + { {172, 67, 216, 21, 5}, + {174, 134, 236, 22, 0}, + {170, 129, 188, 35, 5}, + {6, 131, 118, 23, 5} + }, + { {216, 83, 225, 207, 11}, + {126, 190, 9, 142, 7}, + {223, 56, 124, 161, 11}, + {231, 25, 7, 215, 14} + }, + { {79, 205, 197, 207, 0}, + {186, 167, 16, 237, 11}, + {15, 58, 59, 63, 2}, + {219, 112, 142, 85, 13} + }, + { {238, 105, 111, 210, 13}, + {183, 71, 229, 139, 15}, + {180, 191, 105, 103, 7}, + {253, 26, 126, 46, 13} + }, + { {183, 22, 24, 71, 5}, + {204, 8, 108, 127, 1}, + {174, 33, 134, 142, 13}, + {143, 227, 97, 3, 3} + }, + { {185, 180, 184, 120, 1}, + {71, 168, 182, 122, 4}, + {129, 225, 210, 217, 13}, + {37, 230, 209, 94, 2} + }, + { {50, 133, 121, 80, 5}, + {52, 10, 244, 57, 4}, + {160, 169, 234, 20, 12}, + {41, 194, 245, 2, 12} + }, + { {149, 233, 71, 119, 1}, + {189, 79, 146, 78, 9}, + {142, 238, 41, 122, 9}, + {151, 36, 159, 43, 13} + }, + { {154, 160, 48, 46, 2}, + {11, 56, 18, 19, 5}, + {71, 64, 192, 85, 9}, + {172, 132, 129, 205, 0} + }, + { {236, 24, 40, 209, 7}, + {198, 16, 228, 142, 14}, + {232, 177, 65, 131, 7}, + {119, 18, 112, 134, 3} + }, + { {188, 80, 121, 99, 5}, + {246, 12, 102, 30, 5}, + {172, 105, 224, 163, 13}, + {167, 134, 99, 6, 15} + }, + { {135, 133, 193, 183, 9}, + {188, 130, 147, 103, 3}, + {158, 216, 58, 30, 1}, + {206, 108, 148, 19, 13} + }, + { {211, 122, 173, 183, 10}, + {89, 157, 143, 199, 15}, + {94, 219, 85, 236, 11}, + {254, 63, 27, 153, 10} + }, + { {129, 127, 2, 206, 0}, + {73, 102, 8, 106, 11}, + {7, 52, 15, 232, 1}, + {213, 97, 6, 105, 2} + }, + { {253, 99, 223, 176, 10}, + {179, 223, 175, 210, 2}, + {80, 223, 188, 107, 15}, + {68, 191, 95, 188, 13} + }, + { {81, 10, 193, 175, 2}, + {56, 184, 10, 196, 11}, + {79, 88, 53, 8, 10}, + {210, 53, 1, 209, 12} + }, + { {61, 30, 223, 141, 3}, + {254, 249, 44, 116, 10}, + {203, 31, 183, 139, 12}, + {82, 227, 73, 247, 15} + }, + { {244, 183, 58, 64, 2}, + {193, 90, 60, 186, 4}, + {64, 37, 206, 210, 15}, + {37, 211, 197, 168, 3} + }, + { {55, 215, 137, 233, 12}, + {208, 174, 127, 109, 2}, + {57, 121, 30, 190, 12}, + {75, 111, 231, 80, 11} + }, + { {143, 17, 199, 124, 10}, + {250, 243, 131, 75, 0}, + {83, 238, 56, 143, 1}, + {13, 44, 28, 245, 15} + }, + { {196, 189, 3, 107, 14}, + {209, 114, 83, 174, 9}, + {125, 108, 11, 210, 3}, + {151, 92, 164, 232, 11} + }, + { {125, 250, 153, 92, 9}, + {223, 172, 189, 216, 8}, + {147, 169, 149, 251, 14}, + {17, 187, 211, 95, 11} + }, + { {123, 20, 232, 37, 1}, + {110, 136, 38, 229, 4}, + {138, 65, 114, 141, 14}, + {42, 118, 65, 23, 6} + }, + { {202, 7, 14, 127, 4}, + {10, 99, 206, 175, 1}, + {47, 231, 14, 5, 3}, + {143, 87, 60, 101, 0} + }, + { {92, 34, 219, 205, 5}, + {191, 232, 76, 156, 2}, + {171, 61, 180, 67, 10}, + {67, 147, 33, 127, 13} + }, + { {160, 219, 114, 108, 8}, + {104, 102, 59, 26, 12}, + {19, 100, 237, 176, 5}, + {53, 141, 198, 97, 6} + }, + { {107, 119, 118, 222, 2}, + {107, 119, 168, 249, 7}, + {71, 182, 238, 237, 6}, + {233, 241, 94, 237, 6} + }, + { {128, 245, 142, 116, 8}, + {73, 199, 151, 42, 0}, + {18, 231, 26, 240, 1}, + {5, 78, 158, 57, 2} + }, + { {225, 172, 31, 244, 10}, + {25, 81, 183, 250, 10}, + {82, 255, 131, 88, 7}, + {85, 254, 216, 169, 8} + }, + { {27, 186, 234, 133, 12}, + {107, 200, 93, 69, 14}, + {58, 21, 117, 221, 8}, + {122, 43, 161, 61, 6} + }, + { {38, 172, 85, 127, 12}, + {185, 33, 243, 61, 9}, + {63, 234, 163, 86, 4}, + {155, 204, 248, 73, 13} + }, + { {229, 209, 239, 53, 7}, + {252, 215, 246, 198, 4}, + {234, 207, 120, 186, 7}, + {38, 54, 254, 179, 15} + }, + { {31, 41, 212, 225, 0}, + {163, 139, 2, 93, 10}, + {8, 114, 185, 79, 8}, + {91, 164, 13, 28, 5} + }, + { {69, 159, 187, 21, 11}, + {220, 210, 157, 244, 12}, + {218, 141, 223, 154, 2}, + {50, 251, 148, 179, 11} + }, + { {212, 176, 73, 85, 9}, + {253, 8, 149, 142, 0}, + {154, 169, 32, 210, 11}, + {7, 26, 145, 11, 15} + }, + { {108, 183, 10, 207, 8}, + {203, 98, 61, 172, 3}, + {31, 53, 14, 211, 6}, + {195, 91, 196, 109, 3} + }, + { {248, 97, 122, 81, 8}, + {35, 78, 165, 158, 4}, + {24, 165, 232, 97, 15}, + {39, 154, 87, 44, 4} + }, + { {195, 145, 218, 195, 4}, + {96, 194, 84, 223, 3}, + {44, 53, 184, 156, 3}, + {207, 178, 164, 48, 6} + }, + { {81, 35, 94, 44, 12}, + {41, 107, 79, 208, 0}, + {51, 71, 172, 72, 10}, + {0, 191, 45, 105, 4} + }, + { {116, 64, 124, 79, 14}, + {168, 61, 101, 156, 5}, + {127, 35, 224, 34, 14}, + {163, 154, 107, 193, 5} + }, + { {223, 22, 9, 162, 4}, + {210, 8, 78, 227, 3}, + {36, 89, 6, 143, 11}, + {204, 119, 33, 4, 11} + }, + { {206, 210, 125, 193, 7}, + {246, 21, 92, 159, 6}, + {232, 59, 228, 183, 3}, + {111, 147, 170, 134, 15} + } +}; + +}//namespace diff --git a/modules/objdetect/src/aruco/apriltag/unionfind.hpp b/modules/objdetect/src/aruco/apriltag/unionfind.hpp new file mode 100644 index 0000000000..6ecc58a407 --- /dev/null +++ b/modules/objdetect/src/aruco/apriltag/unionfind.hpp @@ -0,0 +1,131 @@ +// 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) 2013-2016, The Regents of The University of Michigan. +// +// This software was developed in the APRIL Robotics Lab under the +// direction of Edwin Olson, ebolson@umich.edu. This software may be +// available under alternative licensing terms; contact the address above. +// +// The views and conclusions contained in the software and documentation are those +// of the authors and should not be interpreted as representing official policies, +// either expressed or implied, of the Regents of The University of Michigan. +#ifndef _OPENCV_UNIONFIND_HPP_ +#define _OPENCV_UNIONFIND_HPP_ + +namespace cv { +namespace aruco { + +typedef struct unionfind unionfind_t; +struct unionfind{ + uint32_t maxid; + struct ufrec *data; +}; + +struct ufrec{ + // the parent of this node. If a node's parent is its own index, + // then it is a root. + uint32_t parent; + + // for the root of a connected component, the number of components + // connected to it. For intermediate values, it's not meaningful. + uint32_t size; +}; + +static inline unionfind_t *unionfind_create(uint32_t maxid){ + unionfind_t *uf = (unionfind_t*) calloc(1, sizeof(unionfind_t)); + uf->maxid = maxid; + uf->data = (struct ufrec*) malloc((maxid+1) * sizeof(struct ufrec)); + for (unsigned int i = 0; i <= maxid; i++) { + uf->data[i].size = 1; + uf->data[i].parent = i; + } + return uf; +} + +static inline void unionfind_destroy(unionfind_t *uf){ + free(uf->data); + free(uf); +} + +/* +static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id) +{ + // base case: a node is its own parent + if (uf->data[id].parent == id) + return id; + + // otherwise, recurse + uint32_t root = unionfind_get_representative(uf, uf->data[id].parent); + + // short circuit the path. [XXX This write prevents tail recursion] + uf->data[id].parent = root; + + return root; +} + */ + +// this one seems to be every-so-slightly faster than the recursive +// version above. +static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id){ + uint32_t root = id; + + // chase down the root + while (uf->data[root].parent != root) { + root = uf->data[root].parent; + } + + // go back and collapse the tree. + // + // XXX: on some of our workloads that have very shallow trees + // (e.g. image segmentation), we are actually faster not doing + // this... + while (uf->data[id].parent != root) { + uint32_t tmp = uf->data[id].parent; + uf->data[id].parent = root; + id = tmp; + } + + return root; +} + +static inline uint32_t unionfind_get_set_size(unionfind_t *uf, uint32_t id){ + uint32_t repid = unionfind_get_representative(uf, id); + return uf->data[repid].size; +} + +static inline uint32_t unionfind_connect(unionfind_t *uf, uint32_t aid, uint32_t bid){ + uint32_t aroot = unionfind_get_representative(uf, aid); + uint32_t broot = unionfind_get_representative(uf, bid); + + if (aroot == broot) + return aroot; + + // we don't perform "union by rank", but we perform a similar + // operation (but probably without the same asymptotic guarantee): + // We join trees based on the number of *elements* (as opposed to + // rank) contained within each tree. I.e., we use size as a proxy + // for rank. In my testing, it's often *faster* to use size than + // rank, perhaps because the rank of the tree isn't that critical + // if there are very few nodes in it. + uint32_t asize = uf->data[aroot].size; + uint32_t bsize = uf->data[broot].size; + + // optimization idea: We could shortcut some or all of the tree + // that is grafted onto the other tree. Pro: those nodes were just + // read and so are probably in cache. Con: it might end up being + // wasted effort -- the tree might be grafted onto another tree in + // a moment! + if (asize > bsize) { + uf->data[broot].parent = aroot; + uf->data[aroot].size += bsize; + return aroot; + } else { + uf->data[aroot].parent = broot; + uf->data[broot].size += asize; + return broot; + } +} +}} +#endif diff --git a/modules/objdetect/src/aruco/apriltag/zarray.hpp b/modules/objdetect/src/aruco/apriltag/zarray.hpp new file mode 100644 index 0000000000..4c05c925b6 --- /dev/null +++ b/modules/objdetect/src/aruco/apriltag/zarray.hpp @@ -0,0 +1,148 @@ +// 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) 2013-2016, The Regents of The University of Michigan. +// +// This software was developed in the APRIL Robotics Lab under the +// direction of Edwin Olson, ebolson@umich.edu. This software may be +// available under alternative licensing terms; contact the address above. +// +// The views and conclusions contained in the software and documentation are those +// of the authors and should not be interpreted as representing official policies, +// either expressed or implied, of the Regents of The University of Michigan. +#ifndef _OPENCV_ZARRAY_HPP_ +#define _OPENCV_ZARRAY_HPP_ + + +namespace cv { +namespace aruco { + + +struct sQuad{ + float p[4][2]; // corners +}; + +/** + * Defines a structure which acts as a resize-able array ala Java's ArrayList. + */ +typedef struct zarray zarray_t; +struct zarray{ + size_t el_sz; // size of each element + + int size; // how many elements? + int alloc; // we've allocated storage for how many elements? + char *data; +}; + +/** + * Creates and returns a variable array structure capable of holding elements of + * the specified size. It is the caller's responsibility to call zarray_destroy() + * on the returned array when it is no longer needed. + */ +inline static zarray_t *_zarray_create(size_t el_sz){ + zarray_t *za = (zarray_t*) calloc(1, sizeof(zarray_t)); + za->el_sz = el_sz; + return za; +} + +/** + * Frees all resources associated with the variable array structure which was + * created by zarray_create(). After calling, 'za' will no longer be valid for storage. + */ +inline static void _zarray_destroy(zarray_t *za){ + if (za == NULL) + return; + + if (za->data != NULL) + free(za->data); + memset(za, 0, sizeof(zarray_t)); + free(za); +} + +/** + * Retrieves the number of elements currently being contained by the passed + * array, which may be different from its capacity. The index of the last element + * in the array will be one less than the returned value. + */ +inline static int _zarray_size(const zarray_t *za){ + return za->size; +} + +/** + * Allocates enough internal storage in the supplied variable array structure to + * guarantee that the supplied number of elements (capacity) can be safely stored. + */ +inline static void _zarray_ensure_capacity(zarray_t *za, int capacity){ + if (capacity <= za->alloc) + return; + + while (za->alloc < capacity) { + za->alloc *= 2; + if (za->alloc < 8) + za->alloc = 8; + } + + za->data = (char*) realloc(za->data, za->alloc * za->el_sz); +} + +/** + * Adds a new element to the end of the supplied array, and sets its value + * (by copying) from the data pointed to by the supplied pointer 'p'. + * Automatically ensures that enough storage space is available for the new element. + */ +inline static void _zarray_add(zarray_t *za, const void *p){ + _zarray_ensure_capacity(za, za->size + 1); + + memcpy(&za->data[za->size*za->el_sz], p, za->el_sz); + za->size++; +} + +/** + * Retrieves the element from the supplied array located at the zero-based + * index of 'idx' and copies its value into the variable pointed to by the pointer + * 'p'. + */ +inline static void _zarray_get(const zarray_t *za, int idx, void *p){ + CV_DbgAssert(idx >= 0); + CV_DbgAssert(idx < za->size); + + memcpy(p, &za->data[idx*za->el_sz], za->el_sz); +} + +/** + * Similar to zarray_get(), but returns a "live" pointer to the internal + * storage, avoiding a memcpy. This pointer is not valid across + * operations which might move memory around (i.e. zarray_remove_value(), + * zarray_remove_index(), zarray_insert(), zarray_sort(), zarray_clear()). + * 'p' should be a pointer to the pointer which will be set to the internal address. + */ +inline static void _zarray_get_volatile(const zarray_t *za, int idx, void *p){ + CV_DbgAssert(idx >= 0); + CV_DbgAssert(idx < za->size); + + *((void**) p) = &za->data[idx*za->el_sz]; +} + +inline static void _zarray_truncate(zarray_t *za, int sz){ + za->size = sz; +} + +/** + * Sets the value of the current element at index 'idx' by copying its value from + * the data pointed to by 'p'. The previous value of the changed element will be + * copied into the data pointed to by 'outp' if it is not null. + */ +static inline void _zarray_set(zarray_t *za, int idx, const void *p, void *outp){ + CV_DbgAssert(idx >= 0); + CV_DbgAssert(idx < za->size); + + if (outp != NULL) + memcpy(outp, &za->data[idx*za->el_sz], za->el_sz); + + memcpy(&za->data[idx*za->el_sz], p, za->el_sz); +} + +} +} +#endif diff --git a/modules/objdetect/src/aruco/apriltag/zmaxheap.cpp b/modules/objdetect/src/aruco/apriltag/zmaxheap.cpp new file mode 100644 index 0000000000..5b0ac85dcb --- /dev/null +++ b/modules/objdetect/src/aruco/apriltag/zmaxheap.cpp @@ -0,0 +1,207 @@ +// 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) 2013-2016, The Regents of The University of Michigan. +// +// This software was developed in the APRIL Robotics Lab under the +// direction of Edwin Olson, ebolson@umich.edu. This software may be +// available under alternative licensing terms; contact the address above. +// +// The views and conclusions contained in the software and documentation are those +// of the authors and should not be interpreted as representing official policies, +// either expressed or implied, of the Regents of The University of Michigan. + +#include "../../precomp.hpp" +#include "zmaxheap.hpp" + + +// 0 +// 1 2 +// 3 4 5 6 +// 7 8 9 10 11 12 13 14 +// +// Children of node i: 2*i+1, 2*i+2 +// Parent of node i: (i-1) / 2 +// +// Heap property: a parent is greater than (or equal to) its children. + +#define MIN_CAPACITY 16 +namespace cv { +namespace aruco { +struct zmaxheap +{ + size_t el_sz; + + int size; + int alloc; + + float *values; + char *data; + + void (*swap)(zmaxheap_t *heap, int a, int b); +}; + +static inline void _swap_default(zmaxheap_t *heap, int a, int b) +{ + float t = heap->values[a]; + heap->values[a] = heap->values[b]; + heap->values[b] = t; + + cv::AutoBuffer tmp(heap->el_sz); + memcpy(tmp.data(), &heap->data[a*heap->el_sz], heap->el_sz); + memcpy(&heap->data[a*heap->el_sz], &heap->data[b*heap->el_sz], heap->el_sz); + memcpy(&heap->data[b*heap->el_sz], tmp.data(), heap->el_sz); +} + +static inline void _swap_pointer(zmaxheap_t *heap, int a, int b) +{ + float t = heap->values[a]; + heap->values[a] = heap->values[b]; + heap->values[b] = t; + + void **pp = (void**) heap->data; + void *tmp = pp[a]; + pp[a] = pp[b]; + pp[b] = tmp; +} + + +zmaxheap_t *zmaxheap_create(size_t el_sz) +{ + zmaxheap_t *heap = (zmaxheap_t*)calloc(1, sizeof(zmaxheap_t)); + heap->el_sz = el_sz; + + heap->swap = _swap_default; + + if (el_sz == sizeof(void*)) + heap->swap = _swap_pointer; + + return heap; +} + +void zmaxheap_destroy(zmaxheap_t *heap) +{ + free(heap->values); + free(heap->data); + memset(heap, 0, sizeof(zmaxheap_t)); + free(heap); +} + +static void _zmaxheap_ensure_capacity(zmaxheap_t *heap, int capacity) +{ + if (heap->alloc >= capacity) + return; + + int newcap = heap->alloc; + + while (newcap < capacity) { + if (newcap < MIN_CAPACITY) { + newcap = MIN_CAPACITY; + continue; + } + + newcap *= 2; + } + + heap->values = (float*)realloc(heap->values, newcap * sizeof(float)); + heap->data = (char*)realloc(heap->data, newcap * heap->el_sz); + heap->alloc = newcap; +} + +void zmaxheap_add(zmaxheap_t *heap, void *p, float v) +{ + _zmaxheap_ensure_capacity(heap, heap->size + 1); + + int idx = heap->size; + + heap->values[idx] = v; + memcpy(&heap->data[idx*heap->el_sz], p, heap->el_sz); + + heap->size++; + + while (idx > 0) { + + int parent = (idx - 1) / 2; + + // we're done! + if (heap->values[parent] >= v) + break; + + // else, swap and recurse upwards. + heap->swap(heap, idx, parent); + idx = parent; + } +} + +// Removes the item in the heap at the given index. Returns 1 if the +// item existed. 0 Indicates an invalid idx (heap is smaller than +// idx). This is mostly intended to be used by zmaxheap_remove_max. +static int zmaxheap_remove_index(zmaxheap_t *heap, int idx, void *p, float *v) +{ + if (idx >= heap->size) + return 0; + + // copy out the requested element from the heap. + if (v != NULL) + *v = heap->values[idx]; + if (p != NULL) + memcpy(p, &heap->data[idx*heap->el_sz], heap->el_sz); + + heap->size--; + + // If this element is already the last one, then there's nothing + // for us to do. + if (idx == heap->size) + return 1; + + // copy last element to first element. (which probably upsets + // the heap property). + heap->values[idx] = heap->values[heap->size]; + memcpy(&heap->data[idx*heap->el_sz], &heap->data[heap->el_sz * heap->size], heap->el_sz); + + // now fix the heap. Note, as we descend, we're "pushing down" + // the same node the entire time. Thus, while the index of the + // parent might change, the parent_score doesn't. + int parent = idx; + float parent_score = heap->values[idx]; + + // descend, fixing the heap. + while (parent < heap->size) { + + int left = 2*parent + 1; + int right = left + 1; + +// assert(parent_score == heap->values[parent]); + + float left_score = (left < heap->size) ? heap->values[left] : -INFINITY; + float right_score = (right < heap->size) ? heap->values[right] : -INFINITY; + + // put the biggest of (parent, left, right) as the parent. + + // already okay? + if (parent_score >= left_score && parent_score >= right_score) + break; + + // if we got here, then one of the children is bigger than the parent. + if (left_score >= right_score) { + CV_Assert(left < heap->size); + heap->swap(heap, parent, left); + parent = left; + } else { + // right_score can't be less than left_score if right_score is -INFINITY. + CV_Assert(right < heap->size); + heap->swap(heap, parent, right); + parent = right; + } + } + + return 1; +} + +int zmaxheap_remove_max(zmaxheap_t *heap, void *p, float *v) +{ + return zmaxheap_remove_index(heap, 0, p, v); +} + +}} diff --git a/modules/objdetect/src/aruco/apriltag/zmaxheap.hpp b/modules/objdetect/src/aruco/apriltag/zmaxheap.hpp new file mode 100644 index 0000000000..7d3fbc0811 --- /dev/null +++ b/modules/objdetect/src/aruco/apriltag/zmaxheap.hpp @@ -0,0 +1,38 @@ +// 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) 2013-2016, The Regents of The University of Michigan. +// +// This software was developed in the APRIL Robotics Lab under the +// direction of Edwin Olson, ebolson@umich.edu. This software may be +// available under alternative licensing terms; contact the address above. +// +// The views and conclusions contained in the software and documentation are those +// of the authors and should not be interpreted as representing official policies, +// either expressed or implied, of the Regents of The University of Michigan. +#ifndef _OPENCV_ZMAXHEAP_HPP_ +#define _OPENCV_ZMAXHEAP_HPP_ + +namespace cv { +namespace aruco { +typedef struct zmaxheap zmaxheap_t; + +typedef struct zmaxheap_iterator zmaxheap_iterator_t; +struct zmaxheap_iterator { + zmaxheap_t *heap; + int in, out; +}; + +zmaxheap_t *zmaxheap_create(size_t el_sz); + +void zmaxheap_destroy(zmaxheap_t *heap); + +void zmaxheap_add(zmaxheap_t *heap, void *p, float v); + +// returns 0 if the heap is empty, so you can do +// while (zmaxheap_remove_max(...)) { } +int zmaxheap_remove_max(zmaxheap_t *heap, void *p, float *v); + +}} +#endif diff --git a/modules/objdetect/src/aruco/aruco_board.cpp b/modules/objdetect/src/aruco/aruco_board.cpp new file mode 100644 index 0000000000..370d50dd29 --- /dev/null +++ b/modules/objdetect/src/aruco/aruco_board.cpp @@ -0,0 +1,579 @@ +// 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 "opencv2/objdetect/aruco_board.hpp" + +#include +#include + +namespace cv { +namespace aruco { +using namespace std; + +struct Board::Impl { + Dictionary dictionary; + std::vector ids; + std::vector > objPoints; + Point3f rightBottomBorder; + + explicit Impl(const Dictionary& _dictionary): + dictionary(_dictionary) + {} + + virtual ~Impl() {} + + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + virtual void matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray _objPoints, + OutputArray imgPoints) const; + + virtual void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const; +}; + +void Board::Impl::matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray _objPoints, + OutputArray imgPoints) const { + + CV_Assert(ids.size() == objPoints.size()); + CV_Assert(detectedIds.total() == detectedCorners.total()); + + size_t nDetectedMarkers = detectedIds.total(); + + vector objPnts; + objPnts.reserve(nDetectedMarkers); + + vector imgPnts; + imgPnts.reserve(nDetectedMarkers); + + // look for detected markers that belong to the board and get their information + for(unsigned int i = 0; i < nDetectedMarkers; i++) { + int currentId = detectedIds.getMat().ptr< int >(0)[i]; + for(unsigned int j = 0; j < ids.size(); j++) { + if(currentId == ids[j]) { + for(int p = 0; p < 4; p++) { + objPnts.push_back(objPoints[j][p]); + imgPnts.push_back(detectedCorners.getMat(i).ptr(0)[p]); + } + } + } + } + + // create output + Mat(objPnts).copyTo(_objPoints); + Mat(imgPnts).copyTo(imgPoints); +} + +void Board::Impl::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const { + CV_Assert(!outSize.empty()); + CV_Assert(marginSize >= 0); + + img.create(outSize, CV_8UC1); + Mat out = img.getMat(); + out.setTo(Scalar::all(255)); + out.adjustROI(-marginSize, -marginSize, -marginSize, -marginSize); + + // calculate max and min values in XY plane + CV_Assert(objPoints.size() > 0); + float minX, maxX, minY, maxY; + minX = maxX = objPoints[0][0].x; + minY = maxY = objPoints[0][0].y; + + for(unsigned int i = 0; i < objPoints.size(); i++) { + for(int j = 0; j < 4; j++) { + minX = min(minX, objPoints[i][j].x); + maxX = max(maxX, objPoints[i][j].x); + minY = min(minY, objPoints[i][j].y); + maxY = max(maxY, objPoints[i][j].y); + } + } + + float sizeX = maxX - minX; + float sizeY = maxY - minY; + + // proportion transformations + float xReduction = sizeX / float(out.cols); + float yReduction = sizeY / float(out.rows); + + // determine the zone where the markers are placed + if(xReduction > yReduction) { + int nRows = int(sizeY / xReduction); + int rowsMargins = (out.rows - nRows) / 2; + out.adjustROI(-rowsMargins, -rowsMargins, 0, 0); + } else { + int nCols = int(sizeX / yReduction); + int colsMargins = (out.cols - nCols) / 2; + out.adjustROI(0, 0, -colsMargins, -colsMargins); + } + + // now paint each marker + Mat marker; + Point2f outCorners[3]; + Point2f inCorners[3]; + for(unsigned int m = 0; m < objPoints.size(); m++) { + // transform corners to markerZone coordinates + for(int j = 0; j < 3; j++) { + Point2f pf = Point2f(objPoints[m][j].x, objPoints[m][j].y); + // move top left to 0, 0 + pf -= Point2f(minX, minY); + pf.x = pf.x / sizeX * float(out.cols); + pf.y = pf.y / sizeY * float(out.rows); + outCorners[j] = pf; + } + + // get marker + Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order + 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); + + if((outCorners[0].y == outCorners[1].y) && (outCorners[1].x == outCorners[2].x)) { + // marker is aligned to image axes + marker.copyTo(out(Rect(outCorners[0], dst_sz))); + continue; + } + + // interpolate tiny marker to marker position in markerZone + inCorners[0] = Point2f(-0.5f, -0.5f); + inCorners[1] = Point2f(marker.cols - 0.5f, -0.5f); + inCorners[2] = Point2f(marker.cols - 0.5f, marker.rows - 0.5f); + + // remove perspective + Mat transformation = getAffineTransform(inCorners, outCorners); + warpAffine(marker, out, transformation, out.size(), INTER_LINEAR, + BORDER_TRANSPARENT); + } +} + +Board::Board(const Ptr& _impl): + impl(_impl) +{ + CV_Assert(impl); +} + +Board::Board(): + impl(nullptr) +{} + +Board::Board(InputArrayOfArrays objPoints, const Dictionary &dictionary, InputArray ids): + Board(new Board::Impl(dictionary)) { + CV_Assert(ids.size() == objPoints.size()); + CV_Assert(objPoints.total() == ids.total()); + CV_Assert(objPoints.type() == CV_32FC3 || objPoints.type() == CV_32FC1); + + vector > obj_points_vector; + Point3f rightBottomBorder = Point3f(0.f, 0.f, 0.f); + for (unsigned int i = 0; i < objPoints.total(); i++) { + vector corners; + Mat corners_mat = objPoints.getMat(i); + + if (corners_mat.type() == CV_32FC1) + corners_mat = corners_mat.reshape(3); + CV_Assert(corners_mat.total() == 4); + + for (int j = 0; j < 4; j++) { + const Point3f &corner = corners_mat.at(j); + corners.push_back(corner); + rightBottomBorder.x = std::max(rightBottomBorder.x, corner.x); + rightBottomBorder.y = std::max(rightBottomBorder.y, corner.y); + rightBottomBorder.z = std::max(rightBottomBorder.z, corner.z); + } + obj_points_vector.push_back(corners); + } + + ids.copyTo(impl->ids); + impl->objPoints = obj_points_vector; + impl->rightBottomBorder = rightBottomBorder; +} + +const Dictionary& Board::getDictionary() const { + CV_Assert(this->impl); + return this->impl->dictionary; +} + +const vector >& Board::getObjPoints() const { + CV_Assert(this->impl); + return this->impl->objPoints; +} + +const Point3f& Board::getRightBottomCorner() const { + CV_Assert(this->impl); + return this->impl->rightBottomBorder; +} + +const vector& Board::getIds() const { + CV_Assert(this->impl); + return this->impl->ids; +} + +/** @brief Implementation of draw planar board that accepts a raw Board pointer. + */ +void Board::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const { + CV_Assert(this->impl); + impl->generateImage(outSize, img, marginSize, borderBits); +} + +void Board::matchImagePoints(InputArray detectedCorners, InputArray detectedIds, OutputArray objPoints, + OutputArray imgPoints) const { + CV_Assert(this->impl); + impl->matchImagePoints(detectedCorners, detectedIds, objPoints, imgPoints); +} + +struct GridBoardImpl : public Board::Impl { + GridBoardImpl(const Dictionary& _dictionary, const Size& _size, float _markerLength, float _markerSeparation): + Board::Impl(_dictionary), + size(_size), + markerLength(_markerLength), + markerSeparation(_markerSeparation) + { + CV_Assert(size.width*size.height > 0 && markerLength > 0 && markerSeparation > 0); + } + + // number of markers in X and Y directions + const Size size; + // marker side length (normally in meters) + float markerLength; + // separation between markers in the grid + float markerSeparation; +}; + +GridBoard::GridBoard() {} + +GridBoard::GridBoard(const Size& size, float markerLength, float markerSeparation, + const Dictionary &dictionary, InputArray ids): + Board(new GridBoardImpl(dictionary, size, markerLength, markerSeparation)) { + + size_t totalMarkers = (size_t) size.width*size.height; + CV_Assert(ids.empty() || totalMarkers == ids.total()); + vector > objPoints; + objPoints.reserve(totalMarkers); + + if(!ids.empty()) { + ids.copyTo(impl->ids); + } else { + impl->ids = std::vector(totalMarkers); + std::iota(impl->ids.begin(), impl->ids.end(), 0); + } + + // calculate Board objPoints + for (int y = 0; y < size.height; y++) { + for (int x = 0; x < size.width; x++) { + vector corners(4); + corners[0] = Point3f(x * (markerLength + markerSeparation), + y * (markerLength + markerSeparation), 0); + corners[1] = corners[0] + Point3f(markerLength, 0, 0); + corners[2] = corners[0] + Point3f(markerLength, markerLength, 0); + corners[3] = corners[0] + Point3f(0, markerLength, 0); + objPoints.push_back(corners); + } + } + impl->objPoints = objPoints; + impl->rightBottomBorder = Point3f(size.width * markerLength + markerSeparation * (size.width - 1), + size.height * markerLength + markerSeparation * (size.height - 1), 0.f); +} + +Size GridBoard::getGridSize() const { + CV_Assert(impl); + return static_pointer_cast(impl)->size; +} + +float GridBoard::getMarkerLength() const { + CV_Assert(impl); + return static_pointer_cast(impl)->markerLength; +} + +float GridBoard::getMarkerSeparation() const { + CV_Assert(impl); + return static_pointer_cast(impl)->markerSeparation; +} + +struct CharucoBoardImpl : Board::Impl { + CharucoBoardImpl(const Dictionary& _dictionary, const Size& _size, float _squareLength, float _markerLength): + Board::Impl(_dictionary), + size(_size), + squareLength(_squareLength), + markerLength(_markerLength) + {} + + // chessboard size + Size size; + + // Physical size of chessboard squares side (normally in meters) + float squareLength; + + // Physical marker side length (normally in meters) + float markerLength; + + // vector of chessboard 3D corners precalculated + std::vector chessboardCorners; + + // for each charuco corner, nearest marker id and nearest marker corner id of each marker + std::vector > nearestMarkerIdx; + std::vector > nearestMarkerCorners; + + void calcNearestMarkerCorners(); + + void matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds, + OutputArray objPoints, OutputArray imgPoints) const override; + + void generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const override; +}; + +/** Fill nearestMarkerIdx and nearestMarkerCorners arrays */ +void CharucoBoardImpl::calcNearestMarkerCorners() { + nearestMarkerIdx.resize(chessboardCorners.size()); + nearestMarkerCorners.resize(chessboardCorners.size()); + unsigned int nMarkers = (unsigned int)objPoints.size(); + unsigned int nCharucoCorners = (unsigned int)chessboardCorners.size(); + for(unsigned int i = 0; i < nCharucoCorners; i++) { + double minDist = -1; // distance of closest markers + Point3f charucoCorner = chessboardCorners[i]; + for(unsigned int j = 0; j < nMarkers; j++) { + // calculate distance from marker center to charuco corner + Point3f center = Point3f(0, 0, 0); + for(unsigned int k = 0; k < 4; k++) + center += objPoints[j][k]; + center /= 4.; + double sqDistance; + Point3f distVector = charucoCorner - center; + sqDistance = distVector.x * distVector.x + distVector.y * distVector.y; + if(j == 0 || fabs(sqDistance - minDist) < cv::pow(0.01 * squareLength, 2)) { + // if same minimum distance (or first iteration), add to nearestMarkerIdx vector + nearestMarkerIdx[i].push_back(j); + minDist = sqDistance; + } else if(sqDistance < minDist) { + // if finding a closest marker to the charuco corner + nearestMarkerIdx[i].clear(); // remove any previous added marker + nearestMarkerIdx[i].push_back(j); // add the new closest marker index + minDist = sqDistance; + } + } + // for each of the closest markers, search the marker corner index closer + // to the charuco corner + for(unsigned int j = 0; j < nearestMarkerIdx[i].size(); j++) { + nearestMarkerCorners[i].resize(nearestMarkerIdx[i].size()); + double minDistCorner = -1; + for(unsigned int k = 0; k < 4; k++) { + double sqDistance; + Point3f distVector = charucoCorner - objPoints[nearestMarkerIdx[i][j]][k]; + sqDistance = distVector.x * distVector.x + distVector.y * distVector.y; + if(k == 0 || sqDistance < minDistCorner) { + // if this corner is closer to the charuco corner, assing its index + // to nearestMarkerCorners + minDistCorner = sqDistance; + nearestMarkerCorners[i][j] = k; + } + } + } + } +} + +void CharucoBoardImpl::matchImagePoints(InputArrayOfArrays detectedCorners, InputArray detectedIds, + OutputArray _objPoints, OutputArray imgPoints) const { + if (detectedCorners.kind() == _InputArray::STD_VECTOR_VECTOR || + detectedCorners.isMatVector() || detectedCorners.isUMatVector()) + Board::Impl::matchImagePoints(detectedCorners, detectedIds, _objPoints, imgPoints); + else { + CV_Assert(detectedCorners.isMat() || detectedCorners.isVector()); + size_t nDetected = detectedCorners.total(); + vector objPnts(nDetected); + vector imgPnts(nDetected); + for(size_t i = 0ull; i < nDetected; i++) { + int pointId = detectedIds.getMat().at((int)i); + CV_Assert(pointId >= 0 && pointId < (int)chessboardCorners.size()); + objPnts[i] = chessboardCorners[pointId]; + imgPnts[i] = detectedCorners.getMat().at((int)i); + } + Mat(objPnts).copyTo(_objPoints); + Mat(imgPnts).copyTo(imgPoints); + } +} + +void CharucoBoardImpl::generateImage(Size outSize, OutputArray img, int marginSize, int borderBits) const { + CV_Assert(!outSize.empty()); + CV_Assert(marginSize >= 0); + + img.create(outSize, CV_8UC1); + img.setTo(255); + Mat out = img.getMat(); + Mat noMarginsImg = + out.colRange(marginSize, out.cols - marginSize).rowRange(marginSize, out.rows - marginSize); + + double totalLengthX, totalLengthY; + totalLengthX = squareLength * size.width; + totalLengthY = squareLength * size.height; + + // proportional transformation + double xReduction = totalLengthX / double(noMarginsImg.cols); + double yReduction = totalLengthY / double(noMarginsImg.rows); + + // determine the zone where the chessboard is placed + Mat chessboardZoneImg; + if(xReduction > yReduction) { + int nRows = int(totalLengthY / xReduction); + int rowsMargins = (noMarginsImg.rows - nRows) / 2; + chessboardZoneImg = noMarginsImg.rowRange(rowsMargins, noMarginsImg.rows - rowsMargins); + } else { + int nCols = int(totalLengthX / yReduction); + int colsMargins = (noMarginsImg.cols - nCols) / 2; + chessboardZoneImg = noMarginsImg.colRange(colsMargins, noMarginsImg.cols - colsMargins); + } + + // determine the margins to draw only the markers + // take the minimum just to be sure + double squareSizePixels = min(double(chessboardZoneImg.cols) / double(size.width), + double(chessboardZoneImg.rows) / double(size.height)); + + double diffSquareMarkerLength = (squareLength - markerLength) / 2; + int diffSquareMarkerLengthPixels = + int(diffSquareMarkerLength * squareSizePixels / squareLength); + + // draw markers + Mat markersImg; + Board::Impl::generateImage(chessboardZoneImg.size(), markersImg, diffSquareMarkerLengthPixels, borderBits); + markersImg.copyTo(chessboardZoneImg); + + // now draw black squares + for(int y = 0; y < size.height; y++) { + for(int x = 0; x < size.width; x++) { + + if(y % 2 != x % 2) continue; // white corner, dont do anything + + double startX, startY; + startX = squareSizePixels * double(x); + startY = squareSizePixels * double(y); + + Mat squareZone = chessboardZoneImg.rowRange(int(startY), int(startY + squareSizePixels)) + .colRange(int(startX), int(startX + squareSizePixels)); + + squareZone.setTo(0); + } + } +} + +CharucoBoard::CharucoBoard(){} + +CharucoBoard::CharucoBoard(const Size& size, float squareLength, float markerLength, + const Dictionary &dictionary, InputArray ids): + Board(new CharucoBoardImpl(dictionary, size, squareLength, markerLength)) { + + CV_Assert(size.width > 1 && size.height > 1 && markerLength > 0 && squareLength > markerLength); + + vector > objPoints; + float diffSquareMarkerLength = (squareLength - markerLength) / 2; + int totalMarkers = (int)(ids.total()); + ids.copyTo(impl->ids); + // calculate Board objPoints + int nextId = 0; + for(int y = 0; y < size.height; y++) { + for(int x = 0; x < size.width; x++) { + + if(y % 2 == x % 2) continue; // black corner, no marker here + + vector corners(4); + corners[0] = Point3f(x * squareLength + diffSquareMarkerLength, + y * squareLength + diffSquareMarkerLength, 0); + corners[1] = corners[0] + Point3f(markerLength, 0, 0); + corners[2] = corners[0] + Point3f(markerLength, markerLength, 0); + corners[3] = corners[0] + Point3f(0, markerLength, 0); + objPoints.push_back(corners); + // first ids in dictionary + if (totalMarkers == 0) + impl->ids.push_back(nextId); + nextId++; + } + } + if (totalMarkers > 0 && nextId != totalMarkers) + CV_Error(cv::Error::StsBadSize, "Size of ids must be equal to the number of markers: "+std::to_string(nextId)); + impl->objPoints = objPoints; + + // now fill chessboardCorners + std::vector & c = static_pointer_cast(impl)->chessboardCorners; + for(int y = 0; y < size.height - 1; y++) { + for(int x = 0; x < size.width - 1; x++) { + Point3f corner; + corner.x = (x + 1) * squareLength; + corner.y = (y + 1) * squareLength; + corner.z = 0; + c.push_back(corner); + } + } + impl->rightBottomBorder = Point3f(size.width * squareLength, size.height * squareLength, 0.f); + static_pointer_cast(impl)->calcNearestMarkerCorners(); +} + +Size CharucoBoard::getChessboardSize() const { + CV_Assert(impl); + return static_pointer_cast(impl)->size; +} + +float CharucoBoard::getSquareLength() const { + CV_Assert(impl); + return static_pointer_cast(impl)->squareLength; +} + +float CharucoBoard::getMarkerLength() const { + CV_Assert(impl); + return static_pointer_cast(impl)->markerLength; +} + +bool CharucoBoard::checkCharucoCornersCollinear(InputArray charucoIds) const { + CV_Assert(impl); + + unsigned int nCharucoCorners = (unsigned int)charucoIds.getMat().total(); + if (nCharucoCorners <= 2) + return true; + + // only test if there are 3 or more corners + auto board = static_pointer_cast(impl); + CV_Assert(board->chessboardCorners.size() >= charucoIds.getMat().total()); + + Vec point0(board->chessboardCorners[charucoIds.getMat().at(0)].x, + board->chessboardCorners[charucoIds.getMat().at(0)].y, 1); + + Vec point1(board->chessboardCorners[charucoIds.getMat().at(1)].x, + board->chessboardCorners[charucoIds.getMat().at(1)].y, 1); + + // create a line from the first two points. + Vec testLine = point0.cross(point1); + Vec testPoint(0, 0, 1); + + double divisor = sqrt(testLine[0]*testLine[0] + testLine[1]*testLine[1]); + CV_Assert(divisor != 0.0); + + // normalize the line with normal + testLine /= divisor; + + double dotProduct; + for (unsigned int i = 2; i < nCharucoCorners; i++){ + testPoint(0) = board->chessboardCorners[charucoIds.getMat().at(i)].x; + testPoint(1) = board->chessboardCorners[charucoIds.getMat().at(i)].y; + + // if testPoint is on testLine, dotProduct will be zero (or very, very close) + dotProduct = testPoint.dot(testLine); + + if (std::abs(dotProduct) > 1e-6){ + return false; + } + } + // no points found that were off of testLine, return true that all points collinear. + return true; +} + +std::vector CharucoBoard::getChessboardCorners() const { + CV_Assert(impl); + return static_pointer_cast(impl)->chessboardCorners; +} + +std::vector > CharucoBoard::getNearestMarkerIdx() const { + CV_Assert(impl); + return static_pointer_cast(impl)->nearestMarkerIdx; +} + +std::vector > CharucoBoard::getNearestMarkerCorners() const { + CV_Assert(impl); + return static_pointer_cast(impl)->nearestMarkerCorners; +} + +} +} diff --git a/modules/objdetect/src/aruco/aruco_detector.cpp b/modules/objdetect/src/aruco/aruco_detector.cpp new file mode 100644 index 0000000000..b2a584d78d --- /dev/null +++ b/modules/objdetect/src/aruco/aruco_detector.cpp @@ -0,0 +1,1343 @@ +// 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 + +#include "opencv2/objdetect/aruco_detector.hpp" +#include "opencv2/objdetect/aruco_board.hpp" +#include "apriltag/apriltag_quad_thresh.hpp" +#include "aruco_utils.hpp" +#include + +namespace cv { +namespace aruco { + +using namespace std; + +static inline bool readWrite(DetectorParameters ¶ms, const FileNode* readNode, + FileStorage* writeStorage = nullptr) +{ + CV_Assert(readNode || writeStorage); + bool check = false; + + check |= readWriteParameter("adaptiveThreshWinSizeMin", params.adaptiveThreshWinSizeMin, readNode, writeStorage); + check |= readWriteParameter("adaptiveThreshWinSizeMax", params.adaptiveThreshWinSizeMax, readNode, writeStorage); + check |= readWriteParameter("adaptiveThreshWinSizeStep", params.adaptiveThreshWinSizeStep, readNode, writeStorage); + check |= readWriteParameter("adaptiveThreshConstant", params.adaptiveThreshConstant, readNode, writeStorage); + check |= readWriteParameter("minMarkerPerimeterRate", params.minMarkerPerimeterRate, readNode, writeStorage); + check |= readWriteParameter("maxMarkerPerimeterRate", params.maxMarkerPerimeterRate, readNode, writeStorage); + check |= readWriteParameter("polygonalApproxAccuracyRate", params.polygonalApproxAccuracyRate, + readNode, writeStorage); + check |= readWriteParameter("minCornerDistanceRate", params.minCornerDistanceRate, readNode, writeStorage); + check |= readWriteParameter("minDistanceToBorder", params.minDistanceToBorder, readNode, writeStorage); + check |= readWriteParameter("minMarkerDistanceRate", params.minMarkerDistanceRate, readNode, writeStorage); + check |= readWriteParameter("cornerRefinementMethod", params.cornerRefinementMethod, readNode, writeStorage); + check |= readWriteParameter("cornerRefinementWinSize", params.cornerRefinementWinSize, readNode, writeStorage); + check |= readWriteParameter("cornerRefinementMaxIterations", params.cornerRefinementMaxIterations, + readNode, writeStorage); + check |= readWriteParameter("cornerRefinementMinAccuracy", params.cornerRefinementMinAccuracy, + readNode, writeStorage); + check |= readWriteParameter("markerBorderBits", params.markerBorderBits, readNode, writeStorage); + check |= readWriteParameter("perspectiveRemovePixelPerCell", params.perspectiveRemovePixelPerCell, + readNode, writeStorage); + check |= readWriteParameter("perspectiveRemoveIgnoredMarginPerCell", params.perspectiveRemoveIgnoredMarginPerCell, + readNode, writeStorage); + check |= readWriteParameter("maxErroneousBitsInBorderRate", params.maxErroneousBitsInBorderRate, + readNode, writeStorage); + check |= readWriteParameter("minOtsuStdDev", params.minOtsuStdDev, readNode, writeStorage); + check |= readWriteParameter("errorCorrectionRate", params.errorCorrectionRate, readNode, writeStorage); + // new aruco 3 functionality + check |= readWriteParameter("useAruco3Detection", params.useAruco3Detection, readNode, writeStorage); + check |= readWriteParameter("minSideLengthCanonicalImg", params.minSideLengthCanonicalImg, readNode, writeStorage); + check |= readWriteParameter("minMarkerLengthRatioOriginalImg", params.minMarkerLengthRatioOriginalImg, + readNode, writeStorage); + return check; +} + +bool DetectorParameters::readDetectorParameters(const FileNode& fn) +{ + if (fn.empty()) + return false; + return readWrite(*this, &fn); +} + +bool DetectorParameters::writeDetectorParameters(FileStorage& fs, const String& name) +{ + CV_Assert(fs.isOpened()); + if (!name.empty()) + fs << name << "{"; + bool res = readWrite(*this, nullptr, &fs); + if (!name.empty()) + fs << "}"; + return res; +} + +static inline bool readWrite(RefineParameters& refineParameters, const FileNode* readNode, + FileStorage* writeStorage = nullptr) +{ + CV_Assert(readNode || writeStorage); + bool check = false; + + check |= readWriteParameter("minRepDistance", refineParameters.minRepDistance, readNode, writeStorage); + check |= readWriteParameter("errorCorrectionRate", refineParameters.errorCorrectionRate, readNode, writeStorage); + check |= readWriteParameter("checkAllOrders", refineParameters.checkAllOrders, readNode, writeStorage); + return check; +} + +RefineParameters::RefineParameters(float _minRepDistance, float _errorCorrectionRate, bool _checkAllOrders): + minRepDistance(_minRepDistance), errorCorrectionRate(_errorCorrectionRate), + checkAllOrders(_checkAllOrders){} + +bool RefineParameters::readRefineParameters(const FileNode &fn) +{ + if (fn.empty()) + return false; + return readWrite(*this, &fn); +} + +bool RefineParameters::writeRefineParameters(FileStorage& fs, const String& name) +{ + CV_Assert(fs.isOpened()); + if (!name.empty()) + fs << name << "{"; + bool res = readWrite(*this, nullptr, &fs); + if (!name.empty()) + fs << "}"; + return res; +} + +/** + * @brief Threshold input image using adaptive thresholding + */ +static void _threshold(InputArray _in, OutputArray _out, int winSize, double constant) { + + CV_Assert(winSize >= 3); + if(winSize % 2 == 0) winSize++; // win size must be odd + adaptiveThreshold(_in, _out, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, winSize, constant); +} + + +/** + * @brief Given a tresholded image, find the contours, calculate their polygonal approximation + * and take those that accomplish some conditions + */ +static void _findMarkerContours(const Mat &in, vector > &candidates, + vector > &contoursOut, double minPerimeterRate, + double maxPerimeterRate, double accuracyRate, + double minCornerDistanceRate, int minDistanceToBorder, int minSize) { + + CV_Assert(minPerimeterRate > 0 && maxPerimeterRate > 0 && accuracyRate > 0 && + minCornerDistanceRate >= 0 && minDistanceToBorder >= 0); + + // calculate maximum and minimum sizes in pixels + unsigned int minPerimeterPixels = + (unsigned int)(minPerimeterRate * max(in.cols, in.rows)); + unsigned int maxPerimeterPixels = + (unsigned int)(maxPerimeterRate * max(in.cols, in.rows)); + + // for aruco3 functionality + if (minSize != 0) { + minPerimeterPixels = 4*minSize; + } + + Mat contoursImg; + in.copyTo(contoursImg); + vector > contours; + findContours(contoursImg, contours, RETR_LIST, CHAIN_APPROX_NONE); + // now filter list of contours + for(unsigned int i = 0; i < contours.size(); i++) { + // check perimeter + if(contours[i].size() < minPerimeterPixels || contours[i].size() > maxPerimeterPixels) + continue; + + // check is square and is convex + vector approxCurve; + approxPolyDP(contours[i], approxCurve, double(contours[i].size()) * accuracyRate, true); + if(approxCurve.size() != 4 || !isContourConvex(approxCurve)) continue; + + // check min distance between corners + double minDistSq = + max(contoursImg.cols, contoursImg.rows) * max(contoursImg.cols, contoursImg.rows); + for(int j = 0; j < 4; j++) { + double d = (double)(approxCurve[j].x - approxCurve[(j + 1) % 4].x) * + (double)(approxCurve[j].x - approxCurve[(j + 1) % 4].x) + + (double)(approxCurve[j].y - approxCurve[(j + 1) % 4].y) * + (double)(approxCurve[j].y - approxCurve[(j + 1) % 4].y); + minDistSq = min(minDistSq, d); + } + double minCornerDistancePixels = double(contours[i].size()) * minCornerDistanceRate; + if(minDistSq < minCornerDistancePixels * minCornerDistancePixels) continue; + + // check if it is too near to the image border + bool tooNearBorder = false; + for(int j = 0; j < 4; j++) { + if(approxCurve[j].x < minDistanceToBorder || approxCurve[j].y < minDistanceToBorder || + approxCurve[j].x > contoursImg.cols - 1 - minDistanceToBorder || + approxCurve[j].y > contoursImg.rows - 1 - minDistanceToBorder) + tooNearBorder = true; + } + if(tooNearBorder) continue; + + // if it passes all the test, add to candidates vector + vector currentCandidate; + currentCandidate.resize(4); + for(int j = 0; j < 4; j++) { + currentCandidate[j] = Point2f((float)approxCurve[j].x, (float)approxCurve[j].y); + } + candidates.push_back(currentCandidate); + contoursOut.push_back(contours[i]); + } +} + + +/** + * @brief Assure order of candidate corners is clockwise direction + */ +static void _reorderCandidatesCorners(vector > &candidates) { + + for(unsigned int i = 0; i < candidates.size(); i++) { + double dx1 = candidates[i][1].x - candidates[i][0].x; + double dy1 = candidates[i][1].y - candidates[i][0].y; + double dx2 = candidates[i][2].x - candidates[i][0].x; + double dy2 = candidates[i][2].y - candidates[i][0].y; + double crossProduct = (dx1 * dy2) - (dy1 * dx2); + + if(crossProduct < 0.0) { // not clockwise direction + swap(candidates[i][1], candidates[i][3]); + } + } +} + +/** + * @brief to make sure that the corner's order of both candidates (default/white) is the same + */ +static vector alignContourOrder(Point2f corner, vector candidate) { + uint8_t r=0; + double min = norm( Vec2f( corner - candidate[0] ), NORM_L2SQR); + for(uint8_t pos=1; pos < 4; pos++) { + double nDiff = norm( Vec2f( corner - candidate[pos] ), NORM_L2SQR); + if(nDiff < min){ + r = pos; + min =nDiff; + } + } + std::rotate(candidate.begin(), candidate.begin() + r, candidate.end()); + return candidate; +} + +/** + * @brief Check candidates that are too close to each other, save the potential candidates + * (i.e. biggest/smallest contour) and remove the rest + */ +static void _filterTooCloseCandidates(const vector > &candidatesIn, + vector > > &candidatesSetOut, + const vector > &contoursIn, + vector > > &contoursSetOut, + double minMarkerDistanceRate, bool detectInvertedMarker) { + + CV_Assert(minMarkerDistanceRate >= 0); + vector candGroup; + candGroup.resize(candidatesIn.size(), -1); + vector > groupedCandidates; + for(unsigned int i = 0; i < candidatesIn.size(); i++) { + bool isSingleContour = true; + for(unsigned int j = i + 1; j < candidatesIn.size(); j++) { + + int minimumPerimeter = min((int)contoursIn[i].size(), (int)contoursIn[j].size() ); + + // fc is the first corner considered on one of the markers, 4 combinations are possible + for(int fc = 0; fc < 4; fc++) { + double distSq = 0; + for(int c = 0; c < 4; c++) { + // modC is the corner considering first corner is fc + int modC = (c + fc) % 4; + distSq += (candidatesIn[i][modC].x - candidatesIn[j][c].x) * + (candidatesIn[i][modC].x - candidatesIn[j][c].x) + + (candidatesIn[i][modC].y - candidatesIn[j][c].y) * + (candidatesIn[i][modC].y - candidatesIn[j][c].y); + } + distSq /= 4.; + + // if mean square distance is too low, remove the smaller one of the two markers + double minMarkerDistancePixels = double(minimumPerimeter) * minMarkerDistanceRate; + if(distSq < minMarkerDistancePixels * minMarkerDistancePixels) { + isSingleContour = false; + // i and j are not related to a group + if(candGroup[i]<0 && candGroup[j]<0){ + // mark candidates with their corresponding group number + candGroup[i] = candGroup[j] = (int)groupedCandidates.size(); + + // create group + vector grouped; + grouped.push_back(i); + grouped.push_back(j); + groupedCandidates.push_back( grouped ); + } + // i is related to a group + else if(candGroup[i] > -1 && candGroup[j] == -1){ + int group = candGroup[i]; + candGroup[j] = group; + + // add to group + groupedCandidates[group].push_back( j ); + } + // j is related to a group + else if(candGroup[j] > -1 && candGroup[i] == -1){ + int group = candGroup[j]; + candGroup[i] = group; + + // add to group + groupedCandidates[group].push_back( i ); + } + } + } + } + if (isSingleContour && candGroup[i] < 0) + { + candGroup[i] = (int)groupedCandidates.size(); + vector grouped; + grouped.push_back(i); + grouped.push_back(i); // step "save possible candidates" require minimum 2 elements + groupedCandidates.push_back(grouped); + } + } + + // save possible candidates + candidatesSetOut.clear(); + contoursSetOut.clear(); + + vector > biggerCandidates; + vector > biggerContours; + vector > smallerCandidates; + vector > smallerContours; + + // save possible candidates + for(unsigned int i = 0; i < groupedCandidates.size(); i++) { + unsigned int smallerIdx = groupedCandidates[i][0]; + unsigned int biggerIdx = smallerIdx; + double smallerArea = contourArea(candidatesIn[smallerIdx]); + double biggerArea = smallerArea; + + // evaluate group elements + for(unsigned int j = 1; j < groupedCandidates[i].size(); j++) { + unsigned int currIdx = groupedCandidates[i][j]; + double currArea = contourArea(candidatesIn[currIdx]); + + // check if current contour is bigger + if(currArea >= biggerArea) { + biggerIdx = currIdx; + biggerArea = currArea; + } + + // check if current contour is smaller + if(currArea < smallerArea && detectInvertedMarker) { + smallerIdx = currIdx; + smallerArea = currArea; + } + } + + // add contours and candidates + biggerCandidates.push_back(candidatesIn[biggerIdx]); + biggerContours.push_back(contoursIn[biggerIdx]); + if(detectInvertedMarker) { + smallerCandidates.push_back(alignContourOrder(candidatesIn[biggerIdx][0], candidatesIn[smallerIdx])); + smallerContours.push_back(contoursIn[smallerIdx]); + } + } + // to preserve the structure :: candidateSet< defaultCandidates, whiteCandidates > + // default candidates + candidatesSetOut.push_back(biggerCandidates); + contoursSetOut.push_back(biggerContours); + // white candidates + candidatesSetOut.push_back(smallerCandidates); + contoursSetOut.push_back(smallerContours); +} + +/** + * @brief Initial steps on finding square candidates + */ +static void _detectInitialCandidates(const Mat &grey, vector > &candidates, + vector > &contours, + const DetectorParameters ¶ms) { + + CV_Assert(params.adaptiveThreshWinSizeMin >= 3 && params.adaptiveThreshWinSizeMax >= 3); + CV_Assert(params.adaptiveThreshWinSizeMax >= params.adaptiveThreshWinSizeMin); + CV_Assert(params.adaptiveThreshWinSizeStep > 0); + + // number of window sizes (scales) to apply adaptive thresholding + int nScales = (params.adaptiveThreshWinSizeMax - params.adaptiveThreshWinSizeMin) / + params.adaptiveThreshWinSizeStep + 1; + + vector > > candidatesArrays((size_t) nScales); + vector > > contoursArrays((size_t) nScales); + + ////for each value in the interval of thresholding window sizes + parallel_for_(Range(0, nScales), [&](const Range& range) { + const int begin = range.start; + const int end = range.end; + + for (int i = begin; i < end; i++) { + int currScale = params.adaptiveThreshWinSizeMin + i * params.adaptiveThreshWinSizeStep; + // threshold + Mat thresh; + _threshold(grey, thresh, currScale, params.adaptiveThreshConstant); + + // detect rectangles + _findMarkerContours(thresh, candidatesArrays[i], contoursArrays[i], + params.minMarkerPerimeterRate, params.maxMarkerPerimeterRate, + params.polygonalApproxAccuracyRate, params.minCornerDistanceRate, + params.minDistanceToBorder, params.minSideLengthCanonicalImg); + } + }); + // join candidates + for(int i = 0; i < nScales; i++) { + for(unsigned int j = 0; j < candidatesArrays[i].size(); j++) { + candidates.push_back(candidatesArrays[i][j]); + contours.push_back(contoursArrays[i][j]); + } + } +} + + +/** + * @brief Detect square candidates in the input image + */ +static void _detectCandidates(InputArray _grayImage, vector > >& candidatesSetOut, + vector > >& contoursSetOut, const DetectorParameters &_params) { + Mat grey = _grayImage.getMat(); + CV_DbgAssert(grey.total() != 0); + CV_DbgAssert(grey.type() == CV_8UC1); + + /// 1. DETECT FIRST SET OF CANDIDATES + vector > candidates; + vector > contours; + _detectInitialCandidates(grey, candidates, contours, _params); + /// 2. SORT CORNERS + _reorderCandidatesCorners(candidates); + + /// 3. FILTER OUT NEAR CANDIDATE PAIRS + // save the outter/inner border (i.e. potential candidates) + _filterTooCloseCandidates(candidates, candidatesSetOut, contours, contoursSetOut, + _params.minMarkerDistanceRate, _params.detectInvertedMarker); +} + + +/** + * @brief Given an input image and candidate corners, extract the bits of the candidate, including + * the border bits + */ +static Mat _extractBits(InputArray _image, const vector& corners, int markerSize, + int markerBorderBits, int cellSize, double cellMarginRate, double minStdDevOtsu) { + CV_Assert(_image.getMat().channels() == 1); + CV_Assert(corners.size() == 4ull); + CV_Assert(markerBorderBits > 0 && cellSize > 0 && cellMarginRate >= 0 && cellMarginRate <= 1); + CV_Assert(minStdDevOtsu >= 0); + + // number of bits in the marker + int markerSizeWithBorders = markerSize + 2 * markerBorderBits; + int cellMarginPixels = int(cellMarginRate * cellSize); + + Mat resultImg; // marker image after removing perspective + int resultImgSize = markerSizeWithBorders * cellSize; + Mat resultImgCorners(4, 1, CV_32FC2); + resultImgCorners.ptr(0)[0] = Point2f(0, 0); + resultImgCorners.ptr(0)[1] = Point2f((float)resultImgSize - 1, 0); + resultImgCorners.ptr(0)[2] = + Point2f((float)resultImgSize - 1, (float)resultImgSize - 1); + resultImgCorners.ptr(0)[3] = Point2f(0, (float)resultImgSize - 1); + + // remove perspective + Mat transformation = getPerspectiveTransform(corners, resultImgCorners); + warpPerspective(_image, resultImg, transformation, Size(resultImgSize, resultImgSize), + INTER_NEAREST); + + // output image containing the bits + Mat bits(markerSizeWithBorders, markerSizeWithBorders, CV_8UC1, Scalar::all(0)); + + // check if standard deviation is enough to apply Otsu + // if not enough, it probably means all bits are the same color (black or white) + Mat mean, stddev; + // Remove some border just to avoid border noise from perspective transformation + Mat innerRegion = resultImg.colRange(cellSize / 2, resultImg.cols - cellSize / 2) + .rowRange(cellSize / 2, resultImg.rows - cellSize / 2); + meanStdDev(innerRegion, mean, stddev); + if(stddev.ptr< double >(0)[0] < minStdDevOtsu) { + // all black or all white, depending on mean value + if(mean.ptr< double >(0)[0] > 127) + bits.setTo(1); + else + bits.setTo(0); + return bits; + } + + // now extract code, first threshold using Otsu + threshold(resultImg, resultImg, 125, 255, THRESH_BINARY | THRESH_OTSU); + + // for each cell + for(int y = 0; y < markerSizeWithBorders; y++) { + for(int x = 0; x < markerSizeWithBorders; x++) { + int Xstart = x * (cellSize) + cellMarginPixels; + int Ystart = y * (cellSize) + cellMarginPixels; + Mat square = resultImg(Rect(Xstart, Ystart, cellSize - 2 * cellMarginPixels, + cellSize - 2 * cellMarginPixels)); + // count white pixels on each cell to assign its value + size_t nZ = (size_t) countNonZero(square); + if(nZ > square.total() / 2) bits.at(y, x) = 1; + } + } + + return bits; +} + + + +/** + * @brief Return number of erroneous bits in border, i.e. number of white bits in border. + */ +static int _getBorderErrors(const Mat &bits, int markerSize, int borderSize) { + + int sizeWithBorders = markerSize + 2 * borderSize; + + CV_Assert(markerSize > 0 && bits.cols == sizeWithBorders && bits.rows == sizeWithBorders); + + int totalErrors = 0; + for(int y = 0; y < sizeWithBorders; y++) { + for(int k = 0; k < borderSize; k++) { + if(bits.ptr(y)[k] != 0) totalErrors++; + if(bits.ptr(y)[sizeWithBorders - 1 - k] != 0) totalErrors++; + } + } + for(int x = borderSize; x < sizeWithBorders - borderSize; x++) { + for(int k = 0; k < borderSize; k++) { + if(bits.ptr(k)[x] != 0) totalErrors++; + if(bits.ptr(sizeWithBorders - 1 - k)[x] != 0) totalErrors++; + } + } + return totalErrors; +} + + +/** + * @brief Tries to identify one candidate given the dictionary + * @return candidate typ. zero if the candidate is not valid, + * 1 if the candidate is a black candidate (default candidate) + * 2 if the candidate is a white candidate + */ +static uint8_t _identifyOneCandidate(const Dictionary& dictionary, InputArray _image, + const vector& _corners, int& idx, + const DetectorParameters& params, int& rotation, + const float scale = 1.f) { + CV_DbgAssert(_corners.size() == 4); + CV_DbgAssert(_image.getMat().total() != 0); + CV_DbgAssert(params.markerBorderBits > 0); + uint8_t typ=1; + // get bits + // scale corners to the correct size to search on the corresponding image pyramid + vector scaled_corners(4); + for (int i = 0; i < 4; ++i) { + scaled_corners[i].x = _corners[i].x * scale; + scaled_corners[i].y = _corners[i].y * scale; + } + + Mat candidateBits = + _extractBits(_image, scaled_corners, dictionary.markerSize, params.markerBorderBits, + params.perspectiveRemovePixelPerCell, + params.perspectiveRemoveIgnoredMarginPerCell, params.minOtsuStdDev); + + // analyze border bits + int maximumErrorsInBorder = + int(dictionary.markerSize * dictionary.markerSize * params.maxErroneousBitsInBorderRate); + int borderErrors = + _getBorderErrors(candidateBits, dictionary.markerSize, params.markerBorderBits); + + // check if it is a white marker + if(params.detectInvertedMarker){ + // to get from 255 to 1 + Mat invertedImg = ~candidateBits-254; + int invBError = _getBorderErrors(invertedImg, dictionary.markerSize, params.markerBorderBits); + // white marker + if(invBError maximumErrorsInBorder) return 0; // border is wrong + + // take only inner bits + Mat onlyBits = + candidateBits.rowRange(params.markerBorderBits, + candidateBits.rows - params.markerBorderBits) + .colRange(params.markerBorderBits, candidateBits.cols - params.markerBorderBits); + + // try to indentify the marker + if(!dictionary.identify(onlyBits, idx, rotation, params.errorCorrectionRate)) + return 0; + + return typ; +} + +/** + * @brief rotate the initial corner to get to the right position + */ +static void correctCornerPosition(vector& _candidate, int rotate){ + std::rotate(_candidate.begin(), _candidate.begin() + 4 - rotate, _candidate.end()); +} + +static size_t _findOptPyrImageForCanonicalImg( + const vector& img_pyr, + const int scaled_width, + const int cur_perimeter, + const int min_perimeter) { + CV_Assert(scaled_width > 0); + size_t optLevel = 0; + float dist = std::numeric_limits::max(); + for (size_t i = 0; i < img_pyr.size(); ++i) { + const float scale = img_pyr[i].cols / static_cast(scaled_width); + const float perimeter_scaled = cur_perimeter * scale; + // instead of std::abs() favor the larger pyramid level by checking if the distance is postive + // will slow down the algorithm but find more corners in the end + const float new_dist = perimeter_scaled - min_perimeter; + if (new_dist < dist && new_dist > 0.f) { + dist = new_dist; + optLevel = i; + } + } + return optLevel; +} + +/** + * @brief Identify square candidates according to a marker dictionary + */ + +static void _identifyCandidates(InputArray grey, + const vector& image_pyr, + vector > >& _candidatesSet, + vector > >& _contoursSet, const Dictionary &_dictionary, + vector >& _accepted, vector >& _contours, vector& ids, + const DetectorParameters ¶ms, + OutputArrayOfArrays _rejected = noArray()) { + CV_DbgAssert(grey.getMat().total() != 0); + CV_DbgAssert(grey.getMat().type() == CV_8UC1); + int ncandidates = (int)_candidatesSet[0].size(); + vector > accepted; + vector > rejected; + vector > contours; + + vector idsTmp(ncandidates, -1); + vector rotated(ncandidates, 0); + vector validCandidates(ncandidates, 0); + + //// Analyze each of the candidates + parallel_for_(Range(0, ncandidates), [&](const Range &range) { + const int begin = range.start; + const int end = range.end; + + vector >& candidates = params.detectInvertedMarker ? _candidatesSet[1] : _candidatesSet[0]; + vector >& contourS = params.detectInvertedMarker ? _contoursSet[1] : _contoursSet[0]; + + for(int i = begin; i < end; i++) { + int currId = -1; + // implements equation (4) + if (params.useAruco3Detection) { + const int perimeterOfContour = static_cast(contourS[i].size()); + const int min_perimeter = params.minSideLengthCanonicalImg * 4; + const size_t nearestImgId = _findOptPyrImageForCanonicalImg(image_pyr, grey.cols(), perimeterOfContour, min_perimeter); + const float scale = image_pyr[nearestImgId].cols / static_cast(grey.cols()); + + validCandidates[i] = _identifyOneCandidate(_dictionary, image_pyr[nearestImgId], candidates[i], currId, params, rotated[i], scale); + } + else { + validCandidates[i] = _identifyOneCandidate(_dictionary, grey, candidates[i], currId, params, rotated[i]); + } + + if(validCandidates[i] > 0) + idsTmp[i] = currId; + } + }); + + for(int i = 0; i < ncandidates; i++) { + if(validCandidates[i] > 0) { + // to choose the right set of candidates :: 0 for default, 1 for white markers + uint8_t set = validCandidates[i]-1; + + // shift corner positions to the correct rotation + correctCornerPosition(_candidatesSet[set][i], rotated[i]); + + if( !params.detectInvertedMarker && validCandidates[i] == 2 ) + continue; + + // add valid candidate + accepted.push_back(_candidatesSet[set][i]); + ids.push_back(idsTmp[i]); + + contours.push_back(_contoursSet[set][i]); + + } else { + rejected.push_back(_candidatesSet[0][i]); + } + } + + // parse output + _accepted = accepted; + + _contours= contours; + + if(_rejected.needed()) { + _copyVector2Output(rejected, _rejected); + } +} + +/** + * Line fitting A * B = C :: Called from function refineCandidateLines + * @param nContours, contour-container + */ +static Point3f _interpolate2Dline(const vector& nContours){ + CV_Assert(nContours.size() >= 2); + float minX, minY, maxX, maxY; + minX = maxX = nContours[0].x; + minY = maxY = nContours[0].y; + + for(unsigned int i = 0; i< nContours.size(); i++){ + minX = nContours[i].x < minX ? nContours[i].x : minX; + minY = nContours[i].y < minY ? nContours[i].y : minY; + maxX = nContours[i].x > maxX ? nContours[i].x : maxX; + maxY = nContours[i].y > maxY ? nContours[i].y : maxY; + } + + Mat A = Mat::ones((int)nContours.size(), 2, CV_32F); // Coefficient Matrix (N x 2) + Mat B((int)nContours.size(), 1, CV_32F); // Variables Matrix (N x 1) + Mat C; // Constant + + if(maxX - minX > maxY - minY){ + for(unsigned int i =0; i < nContours.size(); i++){ + A.at(i,0)= nContours[i].x; + B.at(i,0)= nContours[i].y; + } + + solve(A, B, C, DECOMP_NORMAL); + + return Point3f(C.at(0, 0), -1., C.at(1, 0)); + } + else{ + for(unsigned int i =0; i < nContours.size(); i++){ + A.at(i,0)= nContours[i].y; + B.at(i,0)= nContours[i].x; + } + + solve(A, B, C, DECOMP_NORMAL); + + return Point3f(-1., C.at(0, 0), C.at(1, 0)); + } + +} + +/** + * Find the Point where the lines crosses :: Called from function refineCandidateLines + * @param nLine1 + * @param nLine2 + * @return Crossed Point + */ +static Point2f _getCrossPoint(Point3f nLine1, Point3f nLine2){ + Matx22f A(nLine1.x, nLine1.y, nLine2.x, nLine2.y); + Vec2f B(-nLine1.z, -nLine2.z); + return Vec2f(A.solve(B).val); +} + +/** + * Refine Corners using the contour vector :: Called from function detectMarkers + * @param nContours, contour-container + * @param nCorners, candidate Corners + * @param camMatrix, cameraMatrix input 3x3 floating-point camera matrix + * @param distCoeff, distCoeffs vector of distortion coefficient + */ +static void _refineCandidateLines(vector& nContours, vector& nCorners){ + vector contour2f(nContours.begin(), nContours.end()); + /* 5 groups :: to group the edges + * 4 - classified by its corner + * extra group - (temporary) if contours do not begin with a corner + */ + vector cntPts[5]; + int cornerIndex[4]={-1}; + int group=4; + + for ( unsigned int i =0; i < nContours.size(); i++ ) { + for(unsigned int j=0; j<4; j++){ + if ( nCorners[j] == contour2f[i] ){ + cornerIndex[j] = i; + group=j; + } + } + cntPts[group].push_back(contour2f[i]); + } + for (int i = 0; i < 4; i++) + { + CV_Assert(cornerIndex[i] != -1); + } + // saves extra group into corresponding + if( !cntPts[4].empty() ){ + for( unsigned int i=0; i < cntPts[4].size() ; i++ ) + cntPts[group].push_back(cntPts[4].at(i)); + cntPts[4].clear(); + } + + //Evaluate contour direction :: using the position of the detected corners + int inc=1; + + inc = ( (cornerIndex[0] > cornerIndex[1]) && (cornerIndex[3] > cornerIndex[0]) ) ? -1:inc; + inc = ( (cornerIndex[2] > cornerIndex[3]) && (cornerIndex[1] > cornerIndex[2]) ) ? -1:inc; + + // calculate the line :: who passes through the grouped points + Point3f lines[4]; + for(int i=0; i<4; i++){ + lines[i]=_interpolate2Dline(cntPts[i]); + } + + /* + * calculate the corner :: where the lines crosses to each other + * clockwise direction no clockwise direction + * 0 1 + * .---. 1 .---. 2 + * | | | | + * 3 .___. 0 .___. + * 2 3 + */ + for(int i=0; i < 4; i++){ + if(inc<0) + nCorners[i] = _getCrossPoint(lines[ i ], lines[ (i+1)%4 ]); // 01 12 23 30 + else + nCorners[i] = _getCrossPoint(lines[ i ], lines[ (i+3)%4 ]); // 30 01 12 23 + } +} + +static inline void findCornerInPyrImage(const float scale_init, const int closest_pyr_image_idx, + const vector& grey_pyramid, Mat corners, + const DetectorParameters& params) { + // scale them to the closest pyramid level + if (scale_init != 1.f) + corners *= scale_init; // scale_init * scale_pyr + for (int idx = closest_pyr_image_idx - 1; idx >= 0; --idx) { + // scale them to new pyramid level + corners *= 2.f; // *= scale_pyr; + // use larger win size for larger images + const int subpix_win_size = std::max(grey_pyramid[idx].cols, grey_pyramid[idx].rows) > 1080 ? 5 : 3; + cornerSubPix(grey_pyramid[idx], corners, + Size(subpix_win_size, subpix_win_size), + Size(-1, -1), + TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, + params.cornerRefinementMaxIterations, + params.cornerRefinementMinAccuracy)); + } +} + +struct ArucoDetector::ArucoDetectorImpl { + /// dictionary indicates the type of markers that will be searched + Dictionary dictionary; + + /// marker detection parameters, check DetectorParameters docs to see available settings + DetectorParameters detectorParams; + + /// marker refine parameters + RefineParameters refineParams; + ArucoDetectorImpl() {} + + ArucoDetectorImpl(const Dictionary &_dictionary, const DetectorParameters &_detectorParams, + const RefineParameters& _refineParams): dictionary(_dictionary), + detectorParams(_detectorParams), refineParams(_refineParams) {} + +}; + +ArucoDetector::ArucoDetector(const Dictionary &_dictionary, + const DetectorParameters &_detectorParams, + const RefineParameters& _refineParams) { + arucoDetectorImpl = makePtr(_dictionary, _detectorParams, _refineParams); +} + +void ArucoDetector::detectMarkers(InputArray _image, OutputArrayOfArrays _corners, OutputArray _ids, + OutputArrayOfArrays _rejectedImgPoints) const { + CV_Assert(!_image.empty()); + DetectorParameters& detectorParams = arucoDetectorImpl->detectorParams; + const Dictionary& dictionary = arucoDetectorImpl->dictionary; + + CV_Assert(detectorParams.markerBorderBits > 0); + // check that the parameters are set correctly if Aruco3 is used + CV_Assert(!(detectorParams.useAruco3Detection == true && + detectorParams.minSideLengthCanonicalImg == 0 && + detectorParams.minMarkerLengthRatioOriginalImg == 0.0)); + + Mat grey; + _convertToGrey(_image.getMat(), grey); + + // Aruco3 functionality is the extension of Aruco. + // The description can be found in: + // [1] Speeded up detection of squared fiducial markers, 2018, FJ Romera-Ramirez et al. + // if Aruco3 functionality if not wanted + // change some parameters to be sure to turn it off + if (!detectorParams.useAruco3Detection) { + detectorParams.minMarkerLengthRatioOriginalImg = 0.0; + detectorParams.minSideLengthCanonicalImg = 0; + } + else { + // always turn on corner refinement in case of Aruco3, due to upsampling + detectorParams.cornerRefinementMethod = CORNER_REFINE_SUBPIX; + // only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection + // Todo: update other CORNER_REFINE methods + } + + /// Step 0: equation (2) from paper [1] + const float fxfy = (!detectorParams.useAruco3Detection ? 1.f : detectorParams.minSideLengthCanonicalImg / + (detectorParams.minSideLengthCanonicalImg + std::max(grey.cols, grey.rows)* + detectorParams.minMarkerLengthRatioOriginalImg)); + + /// Step 1: create image pyramid. Section 3.4. in [1] + vector grey_pyramid; + int closest_pyr_image_idx = 0, num_levels = 0; + //// Step 1.1: resize image with equation (1) from paper [1] + if (detectorParams.useAruco3Detection) { + const float scale_pyr = 2.f; + const float img_area = static_cast(grey.rows*grey.cols); + const float min_area_marker = static_cast(detectorParams.minSideLengthCanonicalImg* + detectorParams.minSideLengthCanonicalImg); + // find max level + num_levels = static_cast(log2(img_area / min_area_marker)/scale_pyr); + // the closest pyramid image to the downsampled segmentation image + // will later be used as start index for corner upsampling + const float scale_img_area = img_area * fxfy * fxfy; + closest_pyr_image_idx = cvRound(log2(img_area / scale_img_area)/scale_pyr); + } + buildPyramid(grey, grey_pyramid, num_levels); + + // resize to segmentation image + // in this reduces size the contours will be detected + if (fxfy != 1.f) + resize(grey, grey, Size(cvRound(fxfy * grey.cols), cvRound(fxfy * grey.rows))); + + /// STEP 2: Detect marker candidates + vector > candidates; + vector > contours; + vector ids; + + vector > > candidatesSet; + vector > > contoursSet; + + /// STEP 2.a Detect marker candidates :: using AprilTag + if(detectorParams.cornerRefinementMethod == CORNER_REFINE_APRILTAG){ + _apriltag(grey, detectorParams, candidates, contours); + + candidatesSet.push_back(candidates); + contoursSet.push_back(contours); + } + /// STEP 2.b Detect marker candidates :: traditional way + else + _detectCandidates(grey, candidatesSet, contoursSet, detectorParams); + + /// STEP 2: Check candidate codification (identify markers) + _identifyCandidates(grey, grey_pyramid, candidatesSet, contoursSet, dictionary, + candidates, contours, ids, detectorParams, _rejectedImgPoints); + + /// STEP 3: Corner refinement :: use corner subpix + if (detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) { + CV_Assert(detectorParams.cornerRefinementWinSize > 0 && detectorParams.cornerRefinementMaxIterations > 0 && + detectorParams.cornerRefinementMinAccuracy > 0); + // Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners + parallel_for_(Range(0, (int)candidates.size()), [&](const Range& range) { + const int begin = range.start; + const int end = range.end; + + for (int i = begin; i < end; i++) { + if (detectorParams.useAruco3Detection) { + const float scale_init = (float) grey_pyramid[closest_pyr_image_idx].cols / grey.cols; + findCornerInPyrImage(scale_init, closest_pyr_image_idx, grey_pyramid, Mat(candidates[i]), detectorParams); + } + else + cornerSubPix(grey, Mat(candidates[i]), + Size(detectorParams.cornerRefinementWinSize, detectorParams.cornerRefinementWinSize), + Size(-1, -1), + TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, + detectorParams.cornerRefinementMaxIterations, + detectorParams.cornerRefinementMinAccuracy)); + } + }); + } + + /// STEP 3, Optional : Corner refinement :: use contour container + if (detectorParams.cornerRefinementMethod == CORNER_REFINE_CONTOUR){ + + if (!_ids.empty()) { + + // do corner refinement using the contours for each detected markers + parallel_for_(Range(0, (int)candidates.size()), [&](const Range& range) { + for (int i = range.start; i < range.end; i++) { + _refineCandidateLines(contours[i], candidates[i]); + } + }); + } + } + + if (detectorParams.cornerRefinementMethod != CORNER_REFINE_SUBPIX && fxfy != 1.f) { + // only CORNER_REFINE_SUBPIX implement correctly for useAruco3Detection + // Todo: update other CORNER_REFINE methods + + // scale to orignal size, this however will lead to inaccurate detections! + for (auto &vecPoints : candidates) + for (auto &point : vecPoints) + point *= 1.f/fxfy; + } + + // copy to output arrays + _copyVector2Output(candidates, _corners); + Mat(ids).copyTo(_ids); +} + +/** + * Project board markers that are not included in the list of detected markers + */ +static inline void _projectUndetectedMarkers(const Board &board, InputOutputArrayOfArrays detectedCorners, + InputOutputArray detectedIds, InputArray cameraMatrix, InputArray distCoeffs, + vector >& undetectedMarkersProjectedCorners, + OutputArray undetectedMarkersIds) { + Mat rvec, tvec; // first estimate board pose with the current avaible markers + Mat objPoints, imgPoints; // object and image points for the solvePnP function + board.matchImagePoints(detectedCorners, detectedIds, objPoints, imgPoints); + if (objPoints.total() < 4ull) // at least one marker from board so rvec and tvec are valid + return; + solvePnP(objPoints, imgPoints, cameraMatrix, distCoeffs, rvec, tvec); + + // search undetected markers and project them using the previous pose + vector > undetectedCorners; + const std::vector& ids = board.getIds(); + vector undetectedIds; + for(unsigned int i = 0; i < ids.size(); i++) { + int foundIdx = -1; + for(unsigned int j = 0; j < detectedIds.total(); j++) { + if(ids[i] == detectedIds.getMat().ptr()[j]) { + foundIdx = j; + break; + } + } + + // not detected + if(foundIdx == -1) { + undetectedCorners.push_back(vector()); + undetectedIds.push_back(ids[i]); + projectPoints(board.getObjPoints()[i], rvec, tvec, cameraMatrix, distCoeffs, + undetectedCorners.back()); + } + } + // parse output + Mat(undetectedIds).copyTo(undetectedMarkersIds); + undetectedMarkersProjectedCorners = undetectedCorners; +} + +/** + * Interpolate board markers that are not included in the list of detected markers using + * global homography + */ +static void _projectUndetectedMarkers(const Board &_board, InputOutputArrayOfArrays _detectedCorners, + InputOutputArray _detectedIds, + vector >& _undetectedMarkersProjectedCorners, + OutputArray _undetectedMarkersIds) { + // check board points are in the same plane, if not, global homography cannot be applied + CV_Assert(_board.getObjPoints().size() > 0); + CV_Assert(_board.getObjPoints()[0].size() > 0); + float boardZ = _board.getObjPoints()[0][0].z; + for(unsigned int i = 0; i < _board.getObjPoints().size(); i++) { + for(unsigned int j = 0; j < _board.getObjPoints()[i].size(); j++) + CV_Assert(boardZ == _board.getObjPoints()[i][j].z); + } + + vector detectedMarkersObj2DAll; // Object coordinates (without Z) of all the detected + // marker corners in a single vector + vector imageCornersAll; // Image corners of all detected markers in a single vector + vector > undetectedMarkersObj2D; // Object coordinates (without Z) of all + // missing markers in different vectors + vector undetectedMarkersIds; // ids of missing markers + // find markers included in board, and missing markers from board. Fill the previous vectors + for(unsigned int j = 0; j < _board.getIds().size(); j++) { + bool found = false; + for(unsigned int i = 0; i < _detectedIds.total(); i++) { + if(_detectedIds.getMat().ptr()[i] == _board.getIds()[j]) { + for(int c = 0; c < 4; c++) { + imageCornersAll.push_back(_detectedCorners.getMat(i).ptr()[c]); + detectedMarkersObj2DAll.push_back( + Point2f(_board.getObjPoints()[j][c].x, _board.getObjPoints()[j][c].y)); + } + found = true; + break; + } + } + if(!found) { + undetectedMarkersObj2D.push_back(vector()); + for(int c = 0; c < 4; c++) { + undetectedMarkersObj2D.back().push_back( + Point2f(_board.getObjPoints()[j][c].x, _board.getObjPoints()[j][c].y)); + } + undetectedMarkersIds.push_back(_board.getIds()[j]); + } + } + if(imageCornersAll.size() == 0) return; + + // get homography from detected markers + Mat transformation = findHomography(detectedMarkersObj2DAll, imageCornersAll); + + _undetectedMarkersProjectedCorners.resize(undetectedMarkersIds.size()); + + // for each undetected marker, apply transformation + for(unsigned int i = 0; i < undetectedMarkersObj2D.size(); i++) { + perspectiveTransform(undetectedMarkersObj2D[i], _undetectedMarkersProjectedCorners[i], transformation); + } + Mat(undetectedMarkersIds).copyTo(_undetectedMarkersIds); +} + +void ArucoDetector::refineDetectedMarkers(InputArray _image, const Board& _board, + InputOutputArrayOfArrays _detectedCorners, InputOutputArray _detectedIds, + InputOutputArrayOfArrays _rejectedCorners, InputArray _cameraMatrix, + InputArray _distCoeffs, OutputArray _recoveredIdxs) const { + DetectorParameters& detectorParams = arucoDetectorImpl->detectorParams; + const Dictionary& dictionary = arucoDetectorImpl->dictionary; + RefineParameters& refineParams = arucoDetectorImpl->refineParams; + CV_Assert(refineParams.minRepDistance > 0); + + if(_detectedIds.total() == 0 || _rejectedCorners.total() == 0) return; + + // get projections of missing markers in the board + vector > undetectedMarkersCorners; + vector undetectedMarkersIds; + if(_cameraMatrix.total() != 0) { + // reproject based on camera projection model + _projectUndetectedMarkers(_board, _detectedCorners, _detectedIds, _cameraMatrix, _distCoeffs, + undetectedMarkersCorners, undetectedMarkersIds); + + } else { + // reproject based on global homography + _projectUndetectedMarkers(_board, _detectedCorners, _detectedIds, undetectedMarkersCorners, + undetectedMarkersIds); + } + + // list of missing markers indicating if they have been assigned to a candidate + vector alreadyIdentified(_rejectedCorners.total(), false); + + // maximum bits that can be corrected + int maxCorrectionRecalculated = + int(double(dictionary.maxCorrectionBits) * refineParams.errorCorrectionRate); + + Mat grey; + _convertToGrey(_image, grey); + + // vector of final detected marker corners and ids + vector > finalAcceptedCorners; + vector finalAcceptedIds; + // fill with the current markers + finalAcceptedCorners.resize(_detectedCorners.total()); + finalAcceptedIds.resize(_detectedIds.total()); + for(unsigned int i = 0; i < _detectedIds.total(); i++) { + finalAcceptedCorners[i] = _detectedCorners.getMat(i).clone(); + finalAcceptedIds[i] = _detectedIds.getMat().ptr()[i]; + } + vector recoveredIdxs; // original indexes of accepted markers in _rejectedCorners + + // for each missing marker, try to find a correspondence + for(unsigned int i = 0; i < undetectedMarkersIds.size(); i++) { + + // best match at the moment + int closestCandidateIdx = -1; + double closestCandidateDistance = refineParams.minRepDistance * refineParams.minRepDistance + 1; + Mat closestRotatedMarker; + + for(unsigned int j = 0; j < _rejectedCorners.total(); j++) { + if(alreadyIdentified[j]) continue; + + // check distance + double minDistance = closestCandidateDistance + 1; + bool valid = false; + int validRot = 0; + for(int c = 0; c < 4; c++) { // first corner in rejected candidate + double currentMaxDistance = 0; + for(int k = 0; k < 4; k++) { + Point2f rejCorner = _rejectedCorners.getMat(j).ptr()[(c + k) % 4]; + Point2f distVector = undetectedMarkersCorners[i][k] - rejCorner; + double cornerDist = distVector.x * distVector.x + distVector.y * distVector.y; + currentMaxDistance = max(currentMaxDistance, cornerDist); + } + // if distance is better than current best distance + if(currentMaxDistance < closestCandidateDistance) { + valid = true; + validRot = c; + minDistance = currentMaxDistance; + } + if(!refineParams.checkAllOrders) break; + } + + if(!valid) continue; + + // apply rotation + Mat rotatedMarker; + if(refineParams.checkAllOrders) { + rotatedMarker = Mat(4, 1, CV_32FC2); + for(int c = 0; c < 4; c++) + rotatedMarker.ptr()[c] = + _rejectedCorners.getMat(j).ptr()[(c + 4 + validRot) % 4]; + } + else rotatedMarker = _rejectedCorners.getMat(j); + + // last filter, check if inner code is close enough to the assigned marker code + int codeDistance = 0; + // if errorCorrectionRate, dont check code + if(refineParams.errorCorrectionRate >= 0) { + + // extract bits + Mat bits = _extractBits( + grey, rotatedMarker, dictionary.markerSize, detectorParams.markerBorderBits, + detectorParams.perspectiveRemovePixelPerCell, + detectorParams.perspectiveRemoveIgnoredMarginPerCell, detectorParams.minOtsuStdDev); + + Mat onlyBits = + bits.rowRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits) + .colRange(detectorParams.markerBorderBits, bits.rows - detectorParams.markerBorderBits); + + codeDistance = + dictionary.getDistanceToId(onlyBits, undetectedMarkersIds[i], false); + } + + // if everythin is ok, assign values to current best match + if(refineParams.errorCorrectionRate < 0 || codeDistance < maxCorrectionRecalculated) { + closestCandidateIdx = j; + closestCandidateDistance = minDistance; + closestRotatedMarker = rotatedMarker; + } + } + + // if at least one good match, we have rescue the missing marker + if(closestCandidateIdx >= 0) { + + // subpixel refinement + if(detectorParams.cornerRefinementMethod == CORNER_REFINE_SUBPIX) { + CV_Assert(detectorParams.cornerRefinementWinSize > 0 && + detectorParams.cornerRefinementMaxIterations > 0 && + detectorParams.cornerRefinementMinAccuracy > 0); + cornerSubPix(grey, closestRotatedMarker, + Size(detectorParams.cornerRefinementWinSize, detectorParams.cornerRefinementWinSize), + Size(-1, -1), TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, + detectorParams.cornerRefinementMaxIterations, + detectorParams.cornerRefinementMinAccuracy)); + } + + // remove from rejected + alreadyIdentified[closestCandidateIdx] = true; + + // add to detected + finalAcceptedCorners.push_back(closestRotatedMarker); + finalAcceptedIds.push_back(undetectedMarkersIds[i]); + + // add the original index of the candidate + recoveredIdxs.push_back(closestCandidateIdx); + } + } + + // parse output + if(finalAcceptedIds.size() != _detectedIds.total()) { + // parse output + Mat(finalAcceptedIds).copyTo(_detectedIds); + _copyVector2Output(finalAcceptedCorners, _detectedCorners); + + // recalculate _rejectedCorners based on alreadyIdentified + vector > finalRejected; + for(unsigned int i = 0; i < alreadyIdentified.size(); i++) { + if(!alreadyIdentified[i]) { + finalRejected.push_back(_rejectedCorners.getMat(i).clone()); + } + } + _copyVector2Output(finalRejected, _rejectedCorners); + + if(_recoveredIdxs.needed()) { + Mat(recoveredIdxs).copyTo(_recoveredIdxs); + } + } +} + +void ArucoDetector::write(FileStorage &fs) const +{ + arucoDetectorImpl->dictionary.writeDictionary(fs); + arucoDetectorImpl->detectorParams.writeDetectorParameters(fs); + arucoDetectorImpl->refineParams.writeRefineParameters(fs); +} + +void ArucoDetector::read(const FileNode &fn) { + arucoDetectorImpl->dictionary.readDictionary(fn); + arucoDetectorImpl->detectorParams.readDetectorParameters(fn); + arucoDetectorImpl->refineParams.readRefineParameters(fn); +} + +const Dictionary& ArucoDetector::getDictionary() const { + return arucoDetectorImpl->dictionary; +} + +void ArucoDetector::setDictionary(const Dictionary& dictionary) { + arucoDetectorImpl->dictionary = dictionary; +} + +const DetectorParameters& ArucoDetector::getDetectorParameters() const { + return arucoDetectorImpl->detectorParams; +} + +void ArucoDetector::setDetectorParameters(const DetectorParameters& detectorParameters) { + arucoDetectorImpl->detectorParams = detectorParameters; +} + +const RefineParameters& ArucoDetector::getRefineParameters() const { + return arucoDetectorImpl->refineParams; +} + +void ArucoDetector::setRefineParameters(const RefineParameters& refineParameters) { + arucoDetectorImpl->refineParams = refineParameters; +} + +void drawDetectedMarkers(InputOutputArray _image, InputArrayOfArrays _corners, + InputArray _ids, Scalar borderColor) { + CV_Assert(_image.getMat().total() != 0 && + (_image.getMat().channels() == 1 || _image.getMat().channels() == 3)); + CV_Assert((_corners.total() == _ids.total()) || _ids.total() == 0); + + // calculate colors + Scalar textColor, cornerColor; + textColor = cornerColor = borderColor; + swap(textColor.val[0], textColor.val[1]); // text color just sawp G and R + swap(cornerColor.val[1], cornerColor.val[2]); // corner color just sawp G and B + + int nMarkers = (int)_corners.total(); + for(int i = 0; i < nMarkers; i++) { + Mat currentMarker = _corners.getMat(i); + CV_Assert(currentMarker.total() == 4 && currentMarker.type() == CV_32FC2); + + // draw marker sides + for(int j = 0; j < 4; j++) { + Point2f p0, p1; + p0 = currentMarker.ptr(0)[j]; + p1 = currentMarker.ptr(0)[(j + 1) % 4]; + line(_image, p0, p1, borderColor, 1); + } + // draw first corner mark + rectangle(_image, currentMarker.ptr(0)[0] - Point2f(3, 3), + currentMarker.ptr(0)[0] + Point2f(3, 3), cornerColor, 1, LINE_AA); + + // draw ID + if(_ids.total() != 0) { + Point2f cent(0, 0); + for(int p = 0; p < 4; p++) + cent += currentMarker.ptr(0)[p]; + cent = cent / 4.; + stringstream s; + s << "id=" << _ids.getMat().ptr(0)[i]; + putText(_image, s.str(), cent, FONT_HERSHEY_SIMPLEX, 0.5, textColor, 2); + } + } +} + +void generateImageMarker(const Dictionary &dictionary, int id, int sidePixels, OutputArray _img, int borderBits) { + dictionary.generateImageMarker(id, sidePixels, _img, borderBits); +} + +} +} diff --git a/modules/objdetect/src/aruco/aruco_dictionary.cpp b/modules/objdetect/src/aruco/aruco_dictionary.cpp new file mode 100644 index 0000000000..79eac9a649 --- /dev/null +++ b/modules/objdetect/src/aruco/aruco_dictionary.cpp @@ -0,0 +1,442 @@ +// 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 "opencv2/core/hal/hal.hpp" + +#include "aruco_utils.hpp" +#include "predefined_dictionaries.hpp" +#include "apriltag/predefined_dictionaries_apriltag.hpp" +#include + +namespace cv { +namespace aruco { + +using namespace std; + +Dictionary::Dictionary(): markerSize(0), maxCorrectionBits(0) {} + + +Dictionary::Dictionary(const Mat &_bytesList, int _markerSize, int _maxcorr) { + markerSize = _markerSize; + maxCorrectionBits = _maxcorr; + bytesList = _bytesList; +} + + +bool Dictionary::readDictionary(const cv::FileNode& fn) { + int nMarkers = 0, _markerSize = 0; + if (fn.empty() || !readParameter("nmarkers", nMarkers, fn) || !readParameter("markersize", _markerSize, fn)) + return false; + Mat bytes(0, 0, CV_8UC1), marker(_markerSize, _markerSize, CV_8UC1); + std::string markerString; + for (int i = 0; i < nMarkers; i++) { + std::ostringstream ostr; + ostr << i; + if (!readParameter("marker_" + ostr.str(), markerString, fn)) + return false; + for (int j = 0; j < (int) markerString.size(); j++) + marker.at(j) = (markerString[j] == '0') ? 0 : 1; + bytes.push_back(Dictionary::getByteListFromBits(marker)); + } + int _maxCorrectionBits = 0; + readParameter("maxCorrectionBits", _maxCorrectionBits, fn); + *this = Dictionary(bytes, _markerSize, _maxCorrectionBits); + return true; +} + +void Dictionary::writeDictionary(FileStorage& fs, const String &name) +{ + CV_Assert(fs.isOpened()); + + if (!name.empty()) + fs << name << "{"; + + fs << "nmarkers" << bytesList.rows; + fs << "markersize" << markerSize; + fs << "maxCorrectionBits" << maxCorrectionBits; + for (int i = 0; i < bytesList.rows; i++) { + Mat row = bytesList.row(i);; + Mat bitMarker = getBitsFromByteList(row, markerSize); + std::ostringstream ostr; + ostr << i; + string markerName = "marker_" + ostr.str(); + string marker; + for (int j = 0; j < markerSize * markerSize; j++) + marker.push_back(bitMarker.at(j) + '0'); + fs << markerName << marker; + } + + if (!name.empty()) + fs << "}"; +} + + +bool Dictionary::identify(const Mat &onlyBits, int &idx, int &rotation, double maxCorrectionRate) const { + CV_Assert(onlyBits.rows == markerSize && onlyBits.cols == markerSize); + + int maxCorrectionRecalculed = int(double(maxCorrectionBits) * maxCorrectionRate); + + // get as a byte list + Mat candidateBytes = getByteListFromBits(onlyBits); + + idx = -1; // by default, not found + + // search closest marker in dict + for(int m = 0; m < bytesList.rows; m++) { + int currentMinDistance = markerSize * markerSize + 1; + int currentRotation = -1; + for(unsigned int r = 0; r < 4; r++) { + int currentHamming = cv::hal::normHamming( + bytesList.ptr(m)+r*candidateBytes.cols, + candidateBytes.ptr(), + candidateBytes.cols); + + if(currentHamming < currentMinDistance) { + currentMinDistance = currentHamming; + currentRotation = r; + } + } + + // if maxCorrection is fulfilled, return this one + if(currentMinDistance <= maxCorrectionRecalculed) { + idx = m; + rotation = currentRotation; + break; + } + } + + return idx != -1; +} + + +int Dictionary::getDistanceToId(InputArray bits, int id, bool allRotations) const { + + CV_Assert(id >= 0 && id < bytesList.rows); + + unsigned int nRotations = 4; + if(!allRotations) nRotations = 1; + + Mat candidateBytes = getByteListFromBits(bits.getMat()); + int currentMinDistance = int(bits.total() * bits.total()); + for(unsigned int r = 0; r < nRotations; r++) { + int currentHamming = cv::hal::normHamming( + bytesList.ptr(id) + r*candidateBytes.cols, + candidateBytes.ptr(), + candidateBytes.cols); + + if(currentHamming < currentMinDistance) { + currentMinDistance = currentHamming; + } + } + return currentMinDistance; +} + + +void Dictionary::generateImageMarker(int id, int sidePixels, OutputArray _img, int borderBits) const { + CV_Assert(sidePixels >= (markerSize + 2*borderBits)); + CV_Assert(id < bytesList.rows); + CV_Assert(borderBits > 0); + + _img.create(sidePixels, sidePixels, CV_8UC1); + + // create small marker with 1 pixel per bin + Mat tinyMarker(markerSize + 2 * borderBits, markerSize + 2 * borderBits, CV_8UC1, + Scalar::all(0)); + Mat innerRegion = tinyMarker.rowRange(borderBits, tinyMarker.rows - borderBits) + .colRange(borderBits, tinyMarker.cols - borderBits); + // put inner bits + Mat bits = 255 * getBitsFromByteList(bytesList.rowRange(id, id + 1), markerSize); + CV_Assert(innerRegion.total() == bits.total()); + bits.copyTo(innerRegion); + + // resize tiny marker to output size + cv::resize(tinyMarker, _img.getMat(), _img.getMat().size(), 0, 0, INTER_NEAREST); +} + + +Mat Dictionary::getByteListFromBits(const Mat &bits) { + // integer ceil + int nbytes = (bits.cols * bits.rows + 8 - 1) / 8; + + Mat candidateByteList(1, nbytes, CV_8UC4, Scalar::all(0)); + unsigned char currentBit = 0; + int currentByte = 0; + + // the 4 rotations + uchar* rot0 = candidateByteList.ptr(); + uchar* rot1 = candidateByteList.ptr() + 1*nbytes; + uchar* rot2 = candidateByteList.ptr() + 2*nbytes; + uchar* rot3 = candidateByteList.ptr() + 3*nbytes; + + for(int row = 0; row < bits.rows; row++) { + for(int col = 0; col < bits.cols; col++) { + // circular shift + rot0[currentByte] <<= 1; + rot1[currentByte] <<= 1; + rot2[currentByte] <<= 1; + rot3[currentByte] <<= 1; + // set bit + rot0[currentByte] |= bits.at(row, col); + rot1[currentByte] |= bits.at(col, bits.cols - 1 - row); + rot2[currentByte] |= bits.at(bits.rows - 1 - row, bits.cols - 1 - col); + rot3[currentByte] |= bits.at(bits.rows - 1 - col, row); + currentBit++; + if(currentBit == 8) { + // next byte + currentBit = 0; + currentByte++; + } + } + } + return candidateByteList; +} + + +Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize) { + CV_Assert(byteList.total() > 0 && + byteList.total() >= (unsigned int)markerSize * markerSize / 8 && + byteList.total() <= (unsigned int)markerSize * markerSize / 8 + 1); + Mat bits(markerSize, markerSize, CV_8UC1, Scalar::all(0)); + + unsigned char base2List[] = { 128, 64, 32, 16, 8, 4, 2, 1 }; + int currentByteIdx = 0; + // we only need the bytes in normal rotation + unsigned char currentByte = byteList.ptr()[0]; + int currentBit = 0; + for(int row = 0; row < bits.rows; row++) { + for(int col = 0; col < bits.cols; col++) { + if(currentByte >= base2List[currentBit]) { + bits.at(row, col) = 1; + currentByte -= base2List[currentBit]; + } + currentBit++; + if(currentBit == 8) { + currentByteIdx++; + currentByte = byteList.ptr()[currentByteIdx]; + // if not enough bits for one more byte, we are in the end + // update bit position accordingly + if(8 * (currentByteIdx + 1) > (int)bits.total()) + currentBit = 8 * (currentByteIdx + 1) - (int)bits.total(); + else + currentBit = 0; // ok, bits enough for next byte + } + } + } + return bits; +} + + +Dictionary getPredefinedDictionary(PredefinedDictionaryType name) { + // DictionaryData constructors calls + // moved out of globals so construted on first use, which allows lazy-loading of opencv dll + static const Dictionary DICT_ARUCO_DATA = Dictionary(Mat(1024, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_BYTES), 5, 0); + + static const Dictionary DICT_4X4_50_DATA = Dictionary(Mat(50, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1); + static const Dictionary DICT_4X4_100_DATA = Dictionary(Mat(100, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1); + static const Dictionary DICT_4X4_250_DATA = Dictionary(Mat(250, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1); + static const Dictionary DICT_4X4_1000_DATA = Dictionary(Mat(1000, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 0); + + static const Dictionary DICT_5X5_50_DATA = Dictionary(Mat(50, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3); + static const Dictionary DICT_5X5_100_DATA = Dictionary(Mat(100, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3); + static const Dictionary DICT_5X5_250_DATA = Dictionary(Mat(250, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2); + static const Dictionary DICT_5X5_1000_DATA = Dictionary(Mat(1000, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2); + + static const Dictionary DICT_6X6_50_DATA = Dictionary(Mat(50, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 6); + static const Dictionary DICT_6X6_100_DATA = Dictionary(Mat(100, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5); + static const Dictionary DICT_6X6_250_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5); + static const Dictionary DICT_6X6_1000_DATA = Dictionary(Mat(1000, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 4); + + static const Dictionary DICT_7X7_50_DATA = Dictionary(Mat(50, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 9); + static const Dictionary DICT_7X7_100_DATA = Dictionary(Mat(100, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8); + static const Dictionary DICT_7X7_250_DATA = Dictionary(Mat(250, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8); + static const Dictionary DICT_7X7_1000_DATA = Dictionary(Mat(1000, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 6); + + static const Dictionary DICT_APRILTAG_16h5_DATA = Dictionary(Mat(30, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_16h5_BYTES), 4, 0); + static const Dictionary DICT_APRILTAG_25h9_DATA = Dictionary(Mat(35, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_25h9_BYTES), 5, 0); + static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0); + static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0); + + switch(name) { + + case DICT_ARUCO_ORIGINAL: + return Dictionary(DICT_ARUCO_DATA); + + case DICT_4X4_50: + return Dictionary(DICT_4X4_50_DATA); + case DICT_4X4_100: + return Dictionary(DICT_4X4_100_DATA); + case DICT_4X4_250: + return Dictionary(DICT_4X4_250_DATA); + case DICT_4X4_1000: + return Dictionary(DICT_4X4_1000_DATA); + + case DICT_5X5_50: + return Dictionary(DICT_5X5_50_DATA); + case DICT_5X5_100: + return Dictionary(DICT_5X5_100_DATA); + case DICT_5X5_250: + return Dictionary(DICT_5X5_250_DATA); + case DICT_5X5_1000: + return Dictionary(DICT_5X5_1000_DATA); + + case DICT_6X6_50: + return Dictionary(DICT_6X6_50_DATA); + case DICT_6X6_100: + return Dictionary(DICT_6X6_100_DATA); + case DICT_6X6_250: + return Dictionary(DICT_6X6_250_DATA); + case DICT_6X6_1000: + return Dictionary(DICT_6X6_1000_DATA); + + case DICT_7X7_50: + return Dictionary(DICT_7X7_50_DATA); + case DICT_7X7_100: + return Dictionary(DICT_7X7_100_DATA); + case DICT_7X7_250: + return Dictionary(DICT_7X7_250_DATA); + case DICT_7X7_1000: + return Dictionary(DICT_7X7_1000_DATA); + + case DICT_APRILTAG_16h5: + return Dictionary(DICT_APRILTAG_16h5_DATA); + case DICT_APRILTAG_25h9: + return Dictionary(DICT_APRILTAG_25h9_DATA); + case DICT_APRILTAG_36h10: + return Dictionary(DICT_APRILTAG_36h10_DATA); + case DICT_APRILTAG_36h11: + return Dictionary(DICT_APRILTAG_36h11_DATA); + + } + return Dictionary(DICT_4X4_50_DATA); +} + + +Dictionary getPredefinedDictionary(int dict) { + return getPredefinedDictionary(PredefinedDictionaryType(dict)); +} + + +/** + * @brief Generates a random marker Mat of size markerSize x markerSize + */ +static Mat _generateRandomMarker(int markerSize, RNG &rng) { + Mat marker(markerSize, markerSize, CV_8UC1, Scalar::all(0)); + for(int i = 0; i < markerSize; i++) { + for(int j = 0; j < markerSize; j++) { + unsigned char bit = (unsigned char) (rng.uniform(0,2)); + marker.at(i, j) = bit; + } + } + return marker; +} + +/** + * @brief Calculate selfDistance of the codification of a marker Mat. Self distance is the Hamming + * distance of the marker to itself in the other rotations. + * See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014. + * "Automatic generation and detection of highly reliable fiducial markers under occlusion". + * Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005 + */ +static int _getSelfDistance(const Mat &marker) { + Mat bytes = Dictionary::getByteListFromBits(marker); + int minHamming = (int)marker.total() + 1; + for(int r = 1; r < 4; r++) { + int currentHamming = cv::hal::normHamming(bytes.ptr(), bytes.ptr() + bytes.cols*r, bytes.cols); + if(currentHamming < minHamming) minHamming = currentHamming; + } + return minHamming; +} + + +Dictionary extendDictionary(int nMarkers, int markerSize, const Dictionary &baseDictionary, int randomSeed) { + RNG rng((uint64)(randomSeed)); + + Dictionary out = Dictionary(Mat(), markerSize); + out.markerSize = markerSize; + + // theoretical maximum intermarker distance + // See S. Garrido-Jurado, R. Muñoz-Salinas, F. J. Madrid-Cuevas, and M. J. Marín-Jiménez. 2014. + // "Automatic generation and detection of highly reliable fiducial markers under occlusion". + // Pattern Recogn. 47, 6 (June 2014), 2280-2292. DOI=10.1016/j.patcog.2014.01.005 + int C = (int)std::floor(float(markerSize * markerSize) / 4.f); + int tau = 2 * (int)std::floor(float(C) * 4.f / 3.f); + + // if baseDictionary is provided, calculate its intermarker distance + if(baseDictionary.bytesList.rows > 0) { + CV_Assert(baseDictionary.markerSize == markerSize); + out.bytesList = baseDictionary.bytesList.clone(); + + int minDistance = markerSize * markerSize + 1; + for(int i = 0; i < out.bytesList.rows; i++) { + Mat markerBytes = out.bytesList.rowRange(i, i + 1); + Mat markerBits = Dictionary::getBitsFromByteList(markerBytes, markerSize); + minDistance = min(minDistance, _getSelfDistance(markerBits)); + for(int j = i + 1; j < out.bytesList.rows; j++) { + minDistance = min(minDistance, out.getDistanceToId(markerBits, j)); + } + } + tau = minDistance; + } + + // current best option + int bestTau = 0; + Mat bestMarker; + + // after these number of unproductive iterations, the best option is accepted + const int maxUnproductiveIterations = 5000; + int unproductiveIterations = 0; + + while(out.bytesList.rows < nMarkers) { + Mat currentMarker = _generateRandomMarker(markerSize, rng); + + int selfDistance = _getSelfDistance(currentMarker); + int minDistance = selfDistance; + + // if self distance is better or equal than current best option, calculate distance + // to previous accepted markers + if(selfDistance >= bestTau) { + for(int i = 0; i < out.bytesList.rows; i++) { + int currentDistance = out.getDistanceToId(currentMarker, i); + minDistance = min(currentDistance, minDistance); + if(minDistance <= bestTau) { + break; + } + } + } + + // if distance is high enough, accept the marker + if(minDistance >= tau) { + unproductiveIterations = 0; + bestTau = 0; + Mat bytes = Dictionary::getByteListFromBits(currentMarker); + out.bytesList.push_back(bytes); + } else { + unproductiveIterations++; + + // if distance is not enough, but is better than the current best option + if(minDistance > bestTau) { + bestTau = minDistance; + bestMarker = currentMarker; + } + + // if number of unproductive iterarions has been reached, accept the current best option + if(unproductiveIterations == maxUnproductiveIterations) { + unproductiveIterations = 0; + tau = bestTau; + bestTau = 0; + Mat bytes = Dictionary::getByteListFromBits(bestMarker); + out.bytesList.push_back(bytes); + } + } + } + + // update the maximum number of correction bits for the generated dictionary + out.maxCorrectionBits = (tau - 1) / 2; + + return out; +} + +} +} diff --git a/modules/objdetect/src/aruco/aruco_utils.cpp b/modules/objdetect/src/aruco/aruco_utils.cpp new file mode 100644 index 0000000000..4c0d2dc3f7 --- /dev/null +++ b/modules/objdetect/src/aruco/aruco_utils.cpp @@ -0,0 +1,50 @@ +// 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 "aruco_utils.hpp" + +namespace cv { +namespace aruco { +using namespace std; + +void _copyVector2Output(vector > &vec, OutputArrayOfArrays out, const float scale) { + out.create((int)vec.size(), 1, CV_32FC2); + if(out.isMatVector()) { + for (unsigned int i = 0; i < vec.size(); i++) { + out.create(4, 1, CV_32FC2, i); + Mat &m = out.getMatRef(i); + Mat(Mat(vec[i]).t()*scale).copyTo(m); + } + } + else if(out.isUMatVector()) { + for (unsigned int i = 0; i < vec.size(); i++) { + out.create(4, 1, CV_32FC2, i); + UMat &m = out.getUMatRef(i); + Mat(Mat(vec[i]).t()*scale).copyTo(m); + } + } + else if(out.kind() == _OutputArray::STD_VECTOR_VECTOR){ + for (unsigned int i = 0; i < vec.size(); i++) { + out.create(4, 1, CV_32FC2, i); + Mat m = out.getMat(i); + Mat(Mat(vec[i]).t()*scale).copyTo(m); + } + } + else { + CV_Error(cv::Error::StsNotImplemented, + "Only Mat vector, UMat vector, and vector OutputArrays are currently supported."); + } +} + +void _convertToGrey(InputArray _in, OutputArray _out) { + CV_Assert(_in.type() == CV_8UC1 || _in.type() == CV_8UC3); + if(_in.type() == CV_8UC3) + cvtColor(_in, _out, COLOR_BGR2GRAY); + else + _in.copyTo(_out); +} + +} +} diff --git a/modules/objdetect/src/aruco/aruco_utils.hpp b/modules/objdetect/src/aruco/aruco_utils.hpp new file mode 100644 index 0000000000..d7d29a7d18 --- /dev/null +++ b/modules/objdetect/src/aruco/aruco_utils.hpp @@ -0,0 +1,45 @@ +// 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_OBJDETECT_ARUCO_UTILS_HPP__ +#define __OPENCV_OBJDETECT_ARUCO_UTILS_HPP__ + +#include +#include + +namespace cv { +namespace aruco { + +/** + * @brief Copy the contents of a corners vector to an OutputArray, settings its size. + */ +void _copyVector2Output(std::vector > &vec, OutputArrayOfArrays out, const float scale = 1.f); + +/** + * @brief Convert input image to gray if it is a 3-channels image + */ +void _convertToGrey(InputArray _in, OutputArray _out); + +template +inline bool readParameter(const std::string& name, T& parameter, const FileNode& node) +{ + if (!node.empty() && !node[name].empty()) { + node[name] >> parameter; + return true; + } + return false; +} + +template +inline bool readWriteParameter(const std::string& name, T& parameter, const FileNode* readNode, FileStorage* writeStorage) +{ + if (readNode) + return readParameter(name, parameter, *readNode); + CV_Assert(writeStorage); + *writeStorage << name << parameter; + return true; +} + +} +} +#endif diff --git a/modules/objdetect/src/aruco/charuco_detector.cpp b/modules/objdetect/src/aruco/charuco_detector.cpp new file mode 100644 index 0000000000..9a7596ab68 --- /dev/null +++ b/modules/objdetect/src/aruco/charuco_detector.cpp @@ -0,0 +1,521 @@ +// 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 +#include "opencv2/objdetect/charuco_detector.hpp" +#include "aruco_utils.hpp" + +namespace cv { +namespace aruco { + +using namespace std; + +struct CharucoDetector::CharucoDetectorImpl { + CharucoBoard board; + CharucoParameters charucoParameters; + ArucoDetector arucoDetector; + + CharucoDetectorImpl(const CharucoBoard& _board, const CharucoParameters _charucoParameters, + const ArucoDetector& _arucoDetector): board(_board), charucoParameters(_charucoParameters), + arucoDetector(_arucoDetector) + {} + + /** Calculate the maximum window sizes for corner refinement for each charuco corner based on the distance + * to their closest markers */ + vector getMaximumSubPixWindowSizes(InputArrayOfArrays markerCorners, InputArray markerIds, + InputArray charucoCorners) { + size_t nCharucoCorners = charucoCorners.getMat().total(); + + CV_Assert(board.getNearestMarkerIdx().size() == nCharucoCorners); + + vector winSizes(nCharucoCorners, Size(-1, -1)); + for(size_t i = 0ull; i < nCharucoCorners; i++) { + if(charucoCorners.getMat().at((int)i) == Point2f(-1.f, -1.f)) continue; + if(board.getNearestMarkerIdx()[i].empty()) continue; + double minDist = -1; + int counter = 0; + // calculate the distance to each of the closest corner of each closest marker + for(size_t j = 0; j < board.getNearestMarkerIdx()[i].size(); j++) { + // find marker + int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]]; + int markerIdx = -1; + for(size_t k = 0; k < markerIds.getMat().total(); k++) { + if(markerIds.getMat().at((int)k) == markerId) { + markerIdx = (int)k; + break; + } + } + if(markerIdx == -1) continue; + Point2f markerCorner = + markerCorners.getMat(markerIdx).at(board.getNearestMarkerCorners()[i][j]); + Point2f charucoCorner = charucoCorners.getMat().at((int)i); + double dist = norm(markerCorner - charucoCorner); + if(minDist == -1) minDist = dist; // if first distance, just assign it + minDist = min(dist, minDist); + counter++; + } + // if this is the first closest marker, dont do anything + if(counter == 0) + continue; + else { + // else, calculate the maximum window size + int winSizeInt = int(minDist - 2); // remove 2 pixels for safety + if(winSizeInt < 1) winSizeInt = 1; // minimum size is 1 + if(winSizeInt > 10) winSizeInt = 10; // maximum size is 10 + winSizes[i] = Size(winSizeInt, winSizeInt); + } + } + return winSizes; + } + + /** @brief From all projected chessboard corners, select those inside the image and apply subpixel refinement */ + void selectAndRefineChessboardCorners(InputArray allCorners, InputArray image, OutputArray selectedCorners, + OutputArray selectedIds, const vector &winSizes) { + const int minDistToBorder = 2; // minimum distance of the corner to the image border + // remaining corners, ids and window refinement sizes after removing corners outside the image + vector filteredChessboardImgPoints; + vector filteredWinSizes; + vector filteredIds; + // filter corners outside the image + Rect innerRect(minDistToBorder, minDistToBorder, image.getMat().cols - 2 * minDistToBorder, + image.getMat().rows - 2 * minDistToBorder); + for(unsigned int i = 0; i < allCorners.getMat().total(); i++) { + if(innerRect.contains(allCorners.getMat().at(i))) { + filteredChessboardImgPoints.push_back(allCorners.getMat().at(i)); + filteredIds.push_back(i); + filteredWinSizes.push_back(winSizes[i]); + } + } + // if none valid, return 0 + if(filteredChessboardImgPoints.empty()) return; + // corner refinement, first convert input image to grey + Mat grey; + if(image.type() == CV_8UC3) + cvtColor(image, grey, COLOR_BGR2GRAY); + else + grey = image.getMat(); + //// For each of the charuco corners, apply subpixel refinement using its correspondind winSize + parallel_for_(Range(0, (int)filteredChessboardImgPoints.size()), [&](const Range& range) { + const int begin = range.start; + const int end = range.end; + for (int i = begin; i < end; i++) { + vector in; + in.push_back(filteredChessboardImgPoints[i] - Point2f(0.5, 0.5)); // adjust sub-pixel coordinates for cornerSubPix + Size winSize = filteredWinSizes[i]; + if (winSize.height == -1 || winSize.width == -1) + winSize = Size(arucoDetector.getDetectorParameters().cornerRefinementWinSize, + arucoDetector.getDetectorParameters().cornerRefinementWinSize); + cornerSubPix(grey, in, winSize, Size(), + TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, + arucoDetector.getDetectorParameters().cornerRefinementMaxIterations, + arucoDetector.getDetectorParameters().cornerRefinementMinAccuracy)); + filteredChessboardImgPoints[i] = in[0] + Point2f(0.5, 0.5); + } + }); + // parse output + Mat(filteredChessboardImgPoints).copyTo(selectedCorners); + Mat(filteredIds).copyTo(selectedIds); + } + + /** Interpolate charuco corners using approximated pose estimation */ + void interpolateCornersCharucoApproxCalib(InputArrayOfArrays markerCorners, InputArray markerIds, + InputArray image, OutputArray charucoCorners, OutputArray charucoIds) { + CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3); + CV_Assert(markerCorners.total() == markerIds.getMat().total()); + + // approximated pose estimation using marker corners + Mat approximatedRvec, approximatedTvec; + Mat objPoints, imgPoints; // object and image points for the solvePnP function + printf("before board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);\n"); + board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints); + printf("after board.matchImagePoints(markerCorners, markerIds, objPoints, imgPoints);\n"); + if (objPoints.total() < 4ull) // need, at least, 4 corners + return; + + solvePnP(objPoints, imgPoints, charucoParameters.cameraMatrix, charucoParameters.distCoeffs, approximatedRvec, approximatedTvec); + printf("after solvePnP\n"); + + // project chessboard corners + vector allChessboardImgPoints; + projectPoints(board.getChessboardCorners(), approximatedRvec, approximatedTvec, charucoParameters.cameraMatrix, + charucoParameters.distCoeffs, allChessboardImgPoints); + printf("after projectPoints\n"); + // calculate maximum window sizes for subpixel refinement. The size is limited by the distance + // to the closes marker corner to avoid erroneous displacements to marker corners + vector subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, allChessboardImgPoints); + // filter corners outside the image and subpixel-refine charuco corners + printf("before selectAndRefineChessboardCorners\n"); + selectAndRefineChessboardCorners(allChessboardImgPoints, image, charucoCorners, charucoIds, subPixWinSizes); + } + + /** Interpolate charuco corners using local homography */ + void interpolateCornersCharucoLocalHom(InputArrayOfArrays markerCorners, InputArray markerIds, InputArray image, + OutputArray charucoCorners, OutputArray charucoIds) { + CV_Assert(image.getMat().channels() == 1 || image.getMat().channels() == 3); + CV_Assert(markerCorners.total() == markerIds.getMat().total()); + size_t nMarkers = markerIds.getMat().total(); + // calculate local homographies for each marker + vector transformations(nMarkers); + vector validTransform(nMarkers, false); + const auto& ids = board.getIds(); + for(size_t i = 0ull; i < nMarkers; i++) { + vector markerObjPoints2D; + int markerId = markerIds.getMat().at((int)i); + auto it = find(ids.begin(), ids.end(), markerId); + if(it == ids.end()) continue; + auto boardIdx = it - ids.begin(); + markerObjPoints2D.resize(4ull); + for(size_t j = 0ull; j < 4ull; j++) + markerObjPoints2D[j] = + Point2f(board.getObjPoints()[boardIdx][j].x, board.getObjPoints()[boardIdx][j].y); + transformations[i] = getPerspectiveTransform(markerObjPoints2D, markerCorners.getMat((int)i)); + // set transform as valid if transformation is non-singular + double det = determinant(transformations[i]); + validTransform[i] = std::abs(det) > 1e-6; + } + size_t nCharucoCorners = (size_t)board.getChessboardCorners().size(); + vector allChessboardImgPoints(nCharucoCorners, Point2f(-1, -1)); + // for each charuco corner, calculate its interpolation position based on the closest markers + // homographies + for(size_t i = 0ull; i < nCharucoCorners; i++) { + Point2f objPoint2D = Point2f(board.getChessboardCorners()[i].x, board.getChessboardCorners()[i].y); + vector interpolatedPositions; + for(size_t j = 0ull; j < board.getNearestMarkerIdx()[i].size(); j++) { + int markerId = board.getIds()[board.getNearestMarkerIdx()[i][j]]; + int markerIdx = -1; + for(size_t k = 0ull; k < markerIds.getMat().total(); k++) { + if(markerIds.getMat().at((int)k) == markerId) { + markerIdx = (int)k; + break; + } + } + if (markerIdx != -1 && + validTransform[markerIdx]) + { + vector in, out; + in.push_back(objPoint2D); + perspectiveTransform(in, out, transformations[markerIdx]); + interpolatedPositions.push_back(out[0]); + } + } + // none of the closest markers detected + if(interpolatedPositions.empty()) continue; + // more than one closest marker detected, take middle point + if(interpolatedPositions.size() > 1ull) { + allChessboardImgPoints[i] = (interpolatedPositions[0] + interpolatedPositions[1]) / 2.; + } + // a single closest marker detected + else allChessboardImgPoints[i] = interpolatedPositions[0]; + } + // calculate maximum window sizes for subpixel refinement. The size is limited by the distance + // to the closes marker corner to avoid erroneous displacements to marker corners + vector subPixWinSizes = getMaximumSubPixWindowSizes(markerCorners, markerIds, allChessboardImgPoints); + // filter corners outside the image and subpixel-refine charuco corners + selectAndRefineChessboardCorners(allChessboardImgPoints, image, charucoCorners, charucoIds, subPixWinSizes); + } + + /** Remove charuco corners if any of their minMarkers closest markers has not been detected */ + int filterCornersWithoutMinMarkers(InputArray _allCharucoCorners, InputArray allCharucoIds, InputArray allArucoIds, + OutputArray _filteredCharucoCorners, OutputArray _filteredCharucoIds) { + CV_Assert(charucoParameters.minMarkers >= 0 && charucoParameters.minMarkers <= 2); + vector filteredCharucoCorners; + vector filteredCharucoIds; + // for each charuco corner + for(unsigned int i = 0; i < allCharucoIds.getMat().total(); i++) { + int currentCharucoId = allCharucoIds.getMat().at(i); + int totalMarkers = 0; // nomber of closest marker detected + // look for closest markers + for(unsigned int m = 0; m < board.getNearestMarkerIdx()[currentCharucoId].size(); m++) { + int markerId = board.getIds()[board.getNearestMarkerIdx()[currentCharucoId][m]]; + bool found = false; + for(unsigned int k = 0; k < allArucoIds.getMat().total(); k++) { + if(allArucoIds.getMat().at(k) == markerId) { + found = true; + break; + } + } + if(found) totalMarkers++; + } + // if enough markers detected, add the charuco corner to the final list + if(totalMarkers >= charucoParameters.minMarkers) { + filteredCharucoIds.push_back(currentCharucoId); + filteredCharucoCorners.push_back(_allCharucoCorners.getMat().at(i)); + } + } + // parse output + Mat(filteredCharucoCorners).copyTo(_filteredCharucoCorners); + Mat(filteredCharucoIds).copyTo(_filteredCharucoIds); + return (int)_filteredCharucoIds.total(); + } +}; + +CharucoDetector::CharucoDetector(const CharucoBoard &board, const CharucoParameters &charucoParams, + const DetectorParameters &detectorParams, const RefineParameters& refineParams) { + this->charucoDetectorImpl = makePtr(board, charucoParams, ArucoDetector(board.getDictionary(), detectorParams, refineParams)); +} + +const CharucoBoard& CharucoDetector::getBoard() const { + return charucoDetectorImpl->board; +} + +void CharucoDetector::setBoard(const CharucoBoard& board) { + this->charucoDetectorImpl->board = board; + charucoDetectorImpl->arucoDetector.setDictionary(board.getDictionary()); +} + +const CharucoParameters &CharucoDetector::getCharucoParameters() const { + return charucoDetectorImpl->charucoParameters; +} + +void CharucoDetector::setCharucoParameters(CharucoParameters &charucoParameters) { + charucoDetectorImpl->charucoParameters = charucoParameters; +} + +const DetectorParameters& CharucoDetector::getDetectorParameters() const { + return charucoDetectorImpl->arucoDetector.getDetectorParameters(); +} + +void CharucoDetector::setDetectorParameters(const DetectorParameters& detectorParameters) { + charucoDetectorImpl->arucoDetector.setDetectorParameters(detectorParameters); +} + +const RefineParameters& CharucoDetector::getRefineParameters() const { + return charucoDetectorImpl->arucoDetector.getRefineParameters(); +} + +void CharucoDetector::setRefineParameters(const RefineParameters& refineParameters) { + charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters); +} + +void CharucoDetector::detectBoard(InputArray image, OutputArray charucoCorners, OutputArray charucoIds, + InputOutputArrayOfArrays markerCorners, InputOutputArray markerIds) const { + CV_Assert((markerCorners.empty() && markerIds.empty() && !image.empty()) || (markerCorners.size() == markerIds.size())); + vector> tmpMarkerCorners; + vector tmpMarkerIds; + InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners; + InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds; + + if (markerCorners.empty() && markerIds.empty()) { + vector > rejectedMarkers; + charucoDetectorImpl->arucoDetector.detectMarkers(image, _markerCorners, _markerIds, rejectedMarkers); + if (charucoDetectorImpl->charucoParameters.tryRefineMarkers) + charucoDetectorImpl->arucoDetector.refineDetectedMarkers(image, charucoDetectorImpl->board, _markerCorners, + _markerIds, rejectedMarkers); + } + // if camera parameters are avaible, use approximated calibration + if(!charucoDetectorImpl->charucoParameters.cameraMatrix.empty()) + charucoDetectorImpl->interpolateCornersCharucoApproxCalib(_markerCorners, _markerIds, image, charucoCorners, + charucoIds); + // else use local homography + else + charucoDetectorImpl->interpolateCornersCharucoLocalHom(_markerCorners, _markerIds, image, charucoCorners, + charucoIds); + // to return a charuco corner, its closest aruco markers should have been detected + charucoDetectorImpl->filterCornersWithoutMinMarkers(charucoCorners, charucoIds, _markerIds, charucoCorners, + charucoIds); +} + +void CharucoDetector::detectDiamonds(InputArray image, OutputArrayOfArrays _diamondCorners, OutputArray _diamondIds, + InputOutputArrayOfArrays inMarkerCorners, InputOutputArrayOfArrays inMarkerIds) const { + CV_Assert(getBoard().getChessboardSize() == Size(3, 3)); + CV_Assert((inMarkerCorners.empty() && inMarkerIds.empty() && !image.empty()) || (inMarkerCorners.size() == inMarkerIds.size())); + + vector> tmpMarkerCorners; + vector tmpMarkerIds; + InputOutputArrayOfArrays _markerCorners = inMarkerCorners.needed() ? inMarkerCorners : tmpMarkerCorners; + InputOutputArray _markerIds = inMarkerIds.needed() ? inMarkerIds : tmpMarkerIds; + if (_markerCorners.empty() && _markerIds.empty()) { + charucoDetectorImpl->arucoDetector.detectMarkers(image, _markerCorners, _markerIds); + } + + const float minRepDistanceRate = 1.302455f; + vector> diamondCorners; + vector diamondIds; + + // stores if the detected markers have been assigned or not to a diamond + vector assigned(_markerIds.total(), false); + if(_markerIds.total() < 4ull) return; // a diamond need at least 4 markers + + // convert input image to grey + Mat grey; + if(image.type() == CV_8UC3) + cvtColor(image, grey, COLOR_BGR2GRAY); + else + grey = image.getMat(); + auto board = getBoard(); + + // for each of the detected markers, try to find a diamond + for(unsigned int i = 0; i < (unsigned int)_markerIds.total(); i++) { + if(assigned[i]) continue; + + // calculate marker perimeter + float perimeterSq = 0; + Mat corners = _markerCorners.getMat(i); + for(int c = 0; c < 4; c++) { + Point2f edge = corners.at(c) - corners.at((c + 1) % 4); + perimeterSq += edge.x*edge.x + edge.y*edge.y; + } + // maximum reprojection error relative to perimeter + float minRepDistance = sqrt(perimeterSq) * minRepDistanceRate; + + int currentId = _markerIds.getMat().at(i); + + // prepare data to call refineDetectedMarkers() + // detected markers (only the current one) + vector currentMarker; + vector currentMarkerId; + currentMarker.push_back(_markerCorners.getMat(i)); + currentMarkerId.push_back(currentId); + + // marker candidates (the rest of markers if they have not been assigned) + vector candidates; + vector candidatesIdxs; + for(unsigned int k = 0; k < assigned.size(); k++) { + if(k == i) continue; + if(!assigned[k]) { + candidates.push_back(_markerCorners.getMat(k)); + candidatesIdxs.push_back(k); + } + } + if(candidates.size() < 3ull) break; // we need at least 3 free markers + // modify charuco layout id to make sure all the ids are different than current id + vector tmpIds(4ull); + for(int k = 1; k < 4; k++) + tmpIds[k] = currentId + 1 + k; + // current id is assigned to [0], so it is the marker on the top + tmpIds[0] = currentId; + + // create Charuco board layout for diamond (3x3 layout) + charucoDetectorImpl->board = CharucoBoard(Size(3, 3), board.getSquareLength(), + board.getMarkerLength(), board.getDictionary(), tmpIds); + + // try to find the rest of markers in the diamond + vector acceptedIdxs; + if (currentMarker.size() != 4ull) + { + RefineParameters refineParameters(minRepDistance, -1.f, false); + RefineParameters tmp = charucoDetectorImpl->arucoDetector.getRefineParameters(); + charucoDetectorImpl->arucoDetector.setRefineParameters(refineParameters); + charucoDetectorImpl->arucoDetector.refineDetectedMarkers(grey, getBoard(), currentMarker, currentMarkerId, + candidates, + noArray(), noArray(), acceptedIdxs); + charucoDetectorImpl->arucoDetector.setRefineParameters(tmp); + } + + // if found, we have a diamond + if(currentMarker.size() == 4ull) { + assigned[i] = true; + // calculate diamond id, acceptedIdxs array indicates the markers taken from candidates array + Vec4i markerId; + markerId[0] = currentId; + for(int k = 1; k < 4; k++) { + int currentMarkerIdx = candidatesIdxs[acceptedIdxs[k - 1]]; + markerId[k] = _markerIds.getMat().at(currentMarkerIdx); + assigned[currentMarkerIdx] = true; + } + + // interpolate the charuco corners of the diamond + vector currentMarkerCorners; + Mat aux; + detectBoard(grey, currentMarkerCorners, aux, currentMarker, currentMarkerId); + + // if everything is ok, save the diamond + if(currentMarkerCorners.size() > 0ull) { + // reorder corners + vector currentMarkerCornersReorder; + currentMarkerCornersReorder.resize(4); + currentMarkerCornersReorder[0] = currentMarkerCorners[0]; + currentMarkerCornersReorder[1] = currentMarkerCorners[1]; + currentMarkerCornersReorder[2] = currentMarkerCorners[3]; + currentMarkerCornersReorder[3] = currentMarkerCorners[2]; + + diamondCorners.push_back(currentMarkerCornersReorder); + diamondIds.push_back(markerId); + } + } + } + charucoDetectorImpl->board = board; + + if(diamondIds.size() > 0ull) { + // parse output + Mat(diamondIds).copyTo(_diamondIds); + + _diamondCorners.create((int)diamondCorners.size(), 1, CV_32FC2); + for(unsigned int i = 0; i < diamondCorners.size(); i++) { + _diamondCorners.create(4, 1, CV_32FC2, i, true); + for(int j = 0; j < 4; j++) { + _diamondCorners.getMat(i).at(j) = diamondCorners[i][j]; + } + } + } +} + +void drawDetectedCornersCharuco(InputOutputArray _image, InputArray _charucoCorners, + InputArray _charucoIds, Scalar cornerColor) { + CV_Assert(!_image.getMat().empty() && + (_image.getMat().channels() == 1 || _image.getMat().channels() == 3)); + CV_Assert((_charucoCorners.getMat().total() == _charucoIds.getMat().total()) || + _charucoIds.getMat().total() == 0); + + size_t nCorners = _charucoCorners.getMat().total(); + for(size_t i = 0; i < nCorners; i++) { + Point2f corner = _charucoCorners.getMat().at((int)i); + // draw first corner mark + rectangle(_image, corner - Point2f(3, 3), corner + Point2f(3, 3), cornerColor, 1, LINE_AA); + // draw ID + if(!_charucoIds.empty()) { + int id = _charucoIds.getMat().at((int)i); + stringstream s; + s << "id=" << id; + putText(_image, s.str(), corner + Point2f(5, -5), FONT_HERSHEY_SIMPLEX, 0.5, + cornerColor, 2); + } + } +} + +void drawDetectedDiamonds(InputOutputArray _image, InputArrayOfArrays _corners, InputArray _ids, Scalar borderColor) { + CV_Assert(_image.getMat().total() != 0 && + (_image.getMat().channels() == 1 || _image.getMat().channels() == 3)); + CV_Assert((_corners.total() == _ids.total()) || _ids.total() == 0); + + // calculate colors + Scalar textColor, cornerColor; + textColor = cornerColor = borderColor; + swap(textColor.val[0], textColor.val[1]); // text color just sawp G and R + swap(cornerColor.val[1], cornerColor.val[2]); // corner color just sawp G and B + + int nMarkers = (int)_corners.total(); + for(int i = 0; i < nMarkers; i++) { + Mat currentMarker = _corners.getMat(i); + CV_Assert(currentMarker.total() == 4 && currentMarker.type() == CV_32FC2); + + // draw marker sides + for(int j = 0; j < 4; j++) { + Point2f p0, p1; + p0 = currentMarker.at< Point2f >(j); + p1 = currentMarker.at< Point2f >((j + 1) % 4); + line(_image, p0, p1, borderColor, 1); + } + + // draw first corner mark + rectangle(_image, currentMarker.at< Point2f >(0) - Point2f(3, 3), + currentMarker.at< Point2f >(0) + Point2f(3, 3), cornerColor, 1, LINE_AA); + + // draw id composed by four numbers + if(_ids.total() != 0) { + Point2f cent(0, 0); + for(int p = 0; p < 4; p++) + cent += currentMarker.at< Point2f >(p); + cent = cent / 4.; + stringstream s; + s << "id=" << _ids.getMat().at< Vec4i >(i); + putText(_image, s.str(), cent, FONT_HERSHEY_SIMPLEX, 0.5, textColor, 2); + } + } +} + +} +} diff --git a/modules/objdetect/src/aruco/predefined_dictionaries.hpp b/modules/objdetect/src/aruco/predefined_dictionaries.hpp new file mode 100644 index 0000000000..f343183059 --- /dev/null +++ b/modules/objdetect/src/aruco/predefined_dictionaries.hpp @@ -0,0 +1,20127 @@ +// 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 + +namespace { + + + +/** + * Dictionaries are stored as a list of bytes in its four rotations + * On each rotation, the marker is divided in bytes assuming a row-major order + * This format allows a faster marker identification. + * For a dictionary composed by M markers of NxN bits, the structure dimensions should be: + * const char name[nMarkers][4rotations][nBytes], or more specifically: + * const char name[M][4][ceil(NxN/8)] + * The element [i][j][k] represents the k-th byte of the i-th marker in the dictionary + * in its j-th rotation. + * Each rotation implies a 90 degree rotation of the marker in anticlockwise direction. + */ + +static unsigned char DICT_ARUCO_BYTES[][4][4] = { + { { 132, 33, 8, 0 }, + { 0, 0, 15, 1 }, + { 8, 66, 16, 1 }, + { 248, 0, 0, 0 }, }, + { { 132, 33, 11, 1 }, + { 8, 66, 15, 1 }, + { 232, 66, 16, 1 }, + { 248, 33, 8, 0 }, }, + { { 132, 33, 4, 1 }, + { 8, 0, 31, 0 }, + { 144, 66, 16, 1 }, + { 124, 0, 8, 0 }, }, + { { 132, 33, 7, 0 }, + { 0, 66, 31, 0 }, + { 112, 66, 16, 1 }, + { 124, 33, 0, 0 }, }, + { { 132, 33, 120, 0 }, + { 16, 132, 15, 1 }, + { 15, 66, 16, 1 }, + { 248, 16, 132, 0 }, }, + { { 132, 33, 123, 1 }, + { 24, 198, 15, 1 }, + { 239, 66, 16, 1 }, + { 248, 49, 140, 0 }, }, + { { 132, 33, 116, 1 }, + { 24, 132, 31, 0 }, + { 151, 66, 16, 1 }, + { 124, 16, 140, 0 }, }, + { { 132, 33, 119, 0 }, + { 16, 198, 31, 0 }, + { 119, 66, 16, 1 }, + { 124, 49, 132, 0 }, }, + { { 132, 32, 152, 0 }, + { 16, 0, 46, 1 }, + { 12, 130, 16, 1 }, + { 186, 0, 4, 0 }, }, + { { 132, 32, 155, 1 }, + { 24, 66, 46, 1 }, + { 236, 130, 16, 1 }, + { 186, 33, 12, 0 }, }, + { { 132, 32, 148, 1 }, + { 24, 0, 62, 0 }, + { 148, 130, 16, 1 }, + { 62, 0, 12, 0 }, }, + { { 132, 32, 151, 0 }, + { 16, 66, 62, 0 }, + { 116, 130, 16, 1 }, + { 62, 33, 4, 0 }, }, + { { 132, 32, 232, 0 }, + { 0, 132, 46, 1 }, + { 11, 130, 16, 1 }, + { 186, 16, 128, 0 }, }, + { { 132, 32, 235, 1 }, + { 8, 198, 46, 1 }, + { 235, 130, 16, 1 }, + { 186, 49, 136, 0 }, }, + { { 132, 32, 228, 1 }, + { 8, 132, 62, 0 }, + { 147, 130, 16, 1 }, + { 62, 16, 136, 0 }, }, + { { 132, 32, 231, 0 }, + { 0, 198, 62, 0 }, + { 115, 130, 16, 1 }, + { 62, 49, 128, 0 }, }, + { { 132, 47, 8, 0 }, + { 33, 8, 15, 1 }, + { 8, 122, 16, 1 }, + { 248, 8, 66, 0 }, }, + { { 132, 47, 11, 1 }, + { 41, 74, 15, 1 }, + { 232, 122, 16, 1 }, + { 248, 41, 74, 0 }, }, + { { 132, 47, 4, 1 }, + { 41, 8, 31, 0 }, + { 144, 122, 16, 1 }, + { 124, 8, 74, 0 }, }, + { { 132, 47, 7, 0 }, + { 33, 74, 31, 0 }, + { 112, 122, 16, 1 }, + { 124, 41, 66, 0 }, }, + { { 132, 47, 120, 0 }, + { 49, 140, 15, 1 }, + { 15, 122, 16, 1 }, + { 248, 24, 198, 0 }, }, + { { 132, 47, 123, 1 }, + { 57, 206, 15, 1 }, + { 239, 122, 16, 1 }, + { 248, 57, 206, 0 }, }, + { { 132, 47, 116, 1 }, + { 57, 140, 31, 0 }, + { 151, 122, 16, 1 }, + { 124, 24, 206, 0 }, }, + { { 132, 47, 119, 0 }, + { 49, 206, 31, 0 }, + { 119, 122, 16, 1 }, + { 124, 57, 198, 0 }, }, + { { 132, 46, 152, 0 }, + { 49, 8, 46, 1 }, + { 12, 186, 16, 1 }, + { 186, 8, 70, 0 }, }, + { { 132, 46, 155, 1 }, + { 57, 74, 46, 1 }, + { 236, 186, 16, 1 }, + { 186, 41, 78, 0 }, }, + { { 132, 46, 148, 1 }, + { 57, 8, 62, 0 }, + { 148, 186, 16, 1 }, + { 62, 8, 78, 0 }, }, + { { 132, 46, 151, 0 }, + { 49, 74, 62, 0 }, + { 116, 186, 16, 1 }, + { 62, 41, 70, 0 }, }, + { { 132, 46, 232, 0 }, + { 33, 140, 46, 1 }, + { 11, 186, 16, 1 }, + { 186, 24, 194, 0 }, }, + { { 132, 46, 235, 1 }, + { 41, 206, 46, 1 }, + { 235, 186, 16, 1 }, + { 186, 57, 202, 0 }, }, + { { 132, 46, 228, 1 }, + { 41, 140, 62, 0 }, + { 147, 186, 16, 1 }, + { 62, 24, 202, 0 }, }, + { { 132, 46, 231, 0 }, + { 33, 206, 62, 0 }, + { 115, 186, 16, 1 }, + { 62, 57, 194, 0 }, }, + { { 132, 19, 8, 0 }, + { 32, 0, 77, 1 }, + { 8, 100, 16, 1 }, + { 217, 0, 2, 0 }, }, + { { 132, 19, 11, 1 }, + { 40, 66, 77, 1 }, + { 232, 100, 16, 1 }, + { 217, 33, 10, 0 }, }, + { { 132, 19, 4, 1 }, + { 40, 0, 93, 0 }, + { 144, 100, 16, 1 }, + { 93, 0, 10, 0 }, }, + { { 132, 19, 7, 0 }, + { 32, 66, 93, 0 }, + { 112, 100, 16, 1 }, + { 93, 33, 2, 0 }, }, + { { 132, 19, 120, 0 }, + { 48, 132, 77, 1 }, + { 15, 100, 16, 1 }, + { 217, 16, 134, 0 }, }, + { { 132, 19, 123, 1 }, + { 56, 198, 77, 1 }, + { 239, 100, 16, 1 }, + { 217, 49, 142, 0 }, }, + { { 132, 19, 116, 1 }, + { 56, 132, 93, 0 }, + { 151, 100, 16, 1 }, + { 93, 16, 142, 0 }, }, + { { 132, 19, 119, 0 }, + { 48, 198, 93, 0 }, + { 119, 100, 16, 1 }, + { 93, 49, 134, 0 }, }, + { { 132, 18, 152, 0 }, + { 48, 0, 108, 1 }, + { 12, 164, 16, 1 }, + { 155, 0, 6, 0 }, }, + { { 132, 18, 155, 1 }, + { 56, 66, 108, 1 }, + { 236, 164, 16, 1 }, + { 155, 33, 14, 0 }, }, + { { 132, 18, 148, 1 }, + { 56, 0, 124, 0 }, + { 148, 164, 16, 1 }, + { 31, 0, 14, 0 }, }, + { { 132, 18, 151, 0 }, + { 48, 66, 124, 0 }, + { 116, 164, 16, 1 }, + { 31, 33, 6, 0 }, }, + { { 132, 18, 232, 0 }, + { 32, 132, 108, 1 }, + { 11, 164, 16, 1 }, + { 155, 16, 130, 0 }, }, + { { 132, 18, 235, 1 }, + { 40, 198, 108, 1 }, + { 235, 164, 16, 1 }, + { 155, 49, 138, 0 }, }, + { { 132, 18, 228, 1 }, + { 40, 132, 124, 0 }, + { 147, 164, 16, 1 }, + { 31, 16, 138, 0 }, }, + { { 132, 18, 231, 0 }, + { 32, 198, 124, 0 }, + { 115, 164, 16, 1 }, + { 31, 49, 130, 0 }, }, + { { 132, 29, 8, 0 }, + { 1, 8, 77, 1 }, + { 8, 92, 16, 1 }, + { 217, 8, 64, 0 }, }, + { { 132, 29, 11, 1 }, + { 9, 74, 77, 1 }, + { 232, 92, 16, 1 }, + { 217, 41, 72, 0 }, }, + { { 132, 29, 4, 1 }, + { 9, 8, 93, 0 }, + { 144, 92, 16, 1 }, + { 93, 8, 72, 0 }, }, + { { 132, 29, 7, 0 }, + { 1, 74, 93, 0 }, + { 112, 92, 16, 1 }, + { 93, 41, 64, 0 }, }, + { { 132, 29, 120, 0 }, + { 17, 140, 77, 1 }, + { 15, 92, 16, 1 }, + { 217, 24, 196, 0 }, }, + { { 132, 29, 123, 1 }, + { 25, 206, 77, 1 }, + { 239, 92, 16, 1 }, + { 217, 57, 204, 0 }, }, + { { 132, 29, 116, 1 }, + { 25, 140, 93, 0 }, + { 151, 92, 16, 1 }, + { 93, 24, 204, 0 }, }, + { { 132, 29, 119, 0 }, + { 17, 206, 93, 0 }, + { 119, 92, 16, 1 }, + { 93, 57, 196, 0 }, }, + { { 132, 28, 152, 0 }, + { 17, 8, 108, 1 }, + { 12, 156, 16, 1 }, + { 155, 8, 68, 0 }, }, + { { 132, 28, 155, 1 }, + { 25, 74, 108, 1 }, + { 236, 156, 16, 1 }, + { 155, 41, 76, 0 }, }, + { { 132, 28, 148, 1 }, + { 25, 8, 124, 0 }, + { 148, 156, 16, 1 }, + { 31, 8, 76, 0 }, }, + { { 132, 28, 151, 0 }, + { 17, 74, 124, 0 }, + { 116, 156, 16, 1 }, + { 31, 41, 68, 0 }, }, + { { 132, 28, 232, 0 }, + { 1, 140, 108, 1 }, + { 11, 156, 16, 1 }, + { 155, 24, 192, 0 }, }, + { { 132, 28, 235, 1 }, + { 9, 206, 108, 1 }, + { 235, 156, 16, 1 }, + { 155, 57, 200, 0 }, }, + { { 132, 28, 228, 1 }, + { 9, 140, 124, 0 }, + { 147, 156, 16, 1 }, + { 31, 24, 200, 0 }, }, + { { 132, 28, 231, 0 }, + { 1, 206, 124, 0 }, + { 115, 156, 16, 1 }, + { 31, 57, 192, 0 }, }, + { { 133, 225, 8, 0 }, + { 66, 16, 15, 1 }, + { 8, 67, 208, 1 }, + { 248, 4, 33, 0 }, }, + { { 133, 225, 11, 1 }, + { 74, 82, 15, 1 }, + { 232, 67, 208, 1 }, + { 248, 37, 41, 0 }, }, + { { 133, 225, 4, 1 }, + { 74, 16, 31, 0 }, + { 144, 67, 208, 1 }, + { 124, 4, 41, 0 }, }, + { { 133, 225, 7, 0 }, + { 66, 82, 31, 0 }, + { 112, 67, 208, 1 }, + { 124, 37, 33, 0 }, }, + { { 133, 225, 120, 0 }, + { 82, 148, 15, 1 }, + { 15, 67, 208, 1 }, + { 248, 20, 165, 0 }, }, + { { 133, 225, 123, 1 }, + { 90, 214, 15, 1 }, + { 239, 67, 208, 1 }, + { 248, 53, 173, 0 }, }, + { { 133, 225, 116, 1 }, + { 90, 148, 31, 0 }, + { 151, 67, 208, 1 }, + { 124, 20, 173, 0 }, }, + { { 133, 225, 119, 0 }, + { 82, 214, 31, 0 }, + { 119, 67, 208, 1 }, + { 124, 53, 165, 0 }, }, + { { 133, 224, 152, 0 }, + { 82, 16, 46, 1 }, + { 12, 131, 208, 1 }, + { 186, 4, 37, 0 }, }, + { { 133, 224, 155, 1 }, + { 90, 82, 46, 1 }, + { 236, 131, 208, 1 }, + { 186, 37, 45, 0 }, }, + { { 133, 224, 148, 1 }, + { 90, 16, 62, 0 }, + { 148, 131, 208, 1 }, + { 62, 4, 45, 0 }, }, + { { 133, 224, 151, 0 }, + { 82, 82, 62, 0 }, + { 116, 131, 208, 1 }, + { 62, 37, 37, 0 }, }, + { { 133, 224, 232, 0 }, + { 66, 148, 46, 1 }, + { 11, 131, 208, 1 }, + { 186, 20, 161, 0 }, }, + { { 133, 224, 235, 1 }, + { 74, 214, 46, 1 }, + { 235, 131, 208, 1 }, + { 186, 53, 169, 0 }, }, + { { 133, 224, 228, 1 }, + { 74, 148, 62, 0 }, + { 147, 131, 208, 1 }, + { 62, 20, 169, 0 }, }, + { { 133, 224, 231, 0 }, + { 66, 214, 62, 0 }, + { 115, 131, 208, 1 }, + { 62, 53, 161, 0 }, }, + { { 133, 239, 8, 0 }, + { 99, 24, 15, 1 }, + { 8, 123, 208, 1 }, + { 248, 12, 99, 0 }, }, + { { 133, 239, 11, 1 }, + { 107, 90, 15, 1 }, + { 232, 123, 208, 1 }, + { 248, 45, 107, 0 }, }, + { { 133, 239, 4, 1 }, + { 107, 24, 31, 0 }, + { 144, 123, 208, 1 }, + { 124, 12, 107, 0 }, }, + { { 133, 239, 7, 0 }, + { 99, 90, 31, 0 }, + { 112, 123, 208, 1 }, + { 124, 45, 99, 0 }, }, + { { 133, 239, 120, 0 }, + { 115, 156, 15, 1 }, + { 15, 123, 208, 1 }, + { 248, 28, 231, 0 }, }, + { { 133, 239, 123, 1 }, + { 123, 222, 15, 1 }, + { 239, 123, 208, 1 }, + { 248, 61, 239, 0 }, }, + { { 133, 239, 116, 1 }, + { 123, 156, 31, 0 }, + { 151, 123, 208, 1 }, + { 124, 28, 239, 0 }, }, + { { 133, 239, 119, 0 }, + { 115, 222, 31, 0 }, + { 119, 123, 208, 1 }, + { 124, 61, 231, 0 }, }, + { { 133, 238, 152, 0 }, + { 115, 24, 46, 1 }, + { 12, 187, 208, 1 }, + { 186, 12, 103, 0 }, }, + { { 133, 238, 155, 1 }, + { 123, 90, 46, 1 }, + { 236, 187, 208, 1 }, + { 186, 45, 111, 0 }, }, + { { 133, 238, 148, 1 }, + { 123, 24, 62, 0 }, + { 148, 187, 208, 1 }, + { 62, 12, 111, 0 }, }, + { { 133, 238, 151, 0 }, + { 115, 90, 62, 0 }, + { 116, 187, 208, 1 }, + { 62, 45, 103, 0 }, }, + { { 133, 238, 232, 0 }, + { 99, 156, 46, 1 }, + { 11, 187, 208, 1 }, + { 186, 28, 227, 0 }, }, + { { 133, 238, 235, 1 }, + { 107, 222, 46, 1 }, + { 235, 187, 208, 1 }, + { 186, 61, 235, 0 }, }, + { { 133, 238, 228, 1 }, + { 107, 156, 62, 0 }, + { 147, 187, 208, 1 }, + { 62, 28, 235, 0 }, }, + { { 133, 238, 231, 0 }, + { 99, 222, 62, 0 }, + { 115, 187, 208, 1 }, + { 62, 61, 227, 0 }, }, + { { 133, 211, 8, 0 }, + { 98, 16, 77, 1 }, + { 8, 101, 208, 1 }, + { 217, 4, 35, 0 }, }, + { { 133, 211, 11, 1 }, + { 106, 82, 77, 1 }, + { 232, 101, 208, 1 }, + { 217, 37, 43, 0 }, }, + { { 133, 211, 4, 1 }, + { 106, 16, 93, 0 }, + { 144, 101, 208, 1 }, + { 93, 4, 43, 0 }, }, + { { 133, 211, 7, 0 }, + { 98, 82, 93, 0 }, + { 112, 101, 208, 1 }, + { 93, 37, 35, 0 }, }, + { { 133, 211, 120, 0 }, + { 114, 148, 77, 1 }, + { 15, 101, 208, 1 }, + { 217, 20, 167, 0 }, }, + { { 133, 211, 123, 1 }, + { 122, 214, 77, 1 }, + { 239, 101, 208, 1 }, + { 217, 53, 175, 0 }, }, + { { 133, 211, 116, 1 }, + { 122, 148, 93, 0 }, + { 151, 101, 208, 1 }, + { 93, 20, 175, 0 }, }, + { { 133, 211, 119, 0 }, + { 114, 214, 93, 0 }, + { 119, 101, 208, 1 }, + { 93, 53, 167, 0 }, }, + { { 133, 210, 152, 0 }, + { 114, 16, 108, 1 }, + { 12, 165, 208, 1 }, + { 155, 4, 39, 0 }, }, + { { 133, 210, 155, 1 }, + { 122, 82, 108, 1 }, + { 236, 165, 208, 1 }, + { 155, 37, 47, 0 }, }, + { { 133, 210, 148, 1 }, + { 122, 16, 124, 0 }, + { 148, 165, 208, 1 }, + { 31, 4, 47, 0 }, }, + { { 133, 210, 151, 0 }, + { 114, 82, 124, 0 }, + { 116, 165, 208, 1 }, + { 31, 37, 39, 0 }, }, + { { 133, 210, 232, 0 }, + { 98, 148, 108, 1 }, + { 11, 165, 208, 1 }, + { 155, 20, 163, 0 }, }, + { { 133, 210, 235, 1 }, + { 106, 214, 108, 1 }, + { 235, 165, 208, 1 }, + { 155, 53, 171, 0 }, }, + { { 133, 210, 228, 1 }, + { 106, 148, 124, 0 }, + { 147, 165, 208, 1 }, + { 31, 20, 171, 0 }, }, + { { 133, 210, 231, 0 }, + { 98, 214, 124, 0 }, + { 115, 165, 208, 1 }, + { 31, 53, 163, 0 }, }, + { { 133, 221, 8, 0 }, + { 67, 24, 77, 1 }, + { 8, 93, 208, 1 }, + { 217, 12, 97, 0 }, }, + { { 133, 221, 11, 1 }, + { 75, 90, 77, 1 }, + { 232, 93, 208, 1 }, + { 217, 45, 105, 0 }, }, + { { 133, 221, 4, 1 }, + { 75, 24, 93, 0 }, + { 144, 93, 208, 1 }, + { 93, 12, 105, 0 }, }, + { { 133, 221, 7, 0 }, + { 67, 90, 93, 0 }, + { 112, 93, 208, 1 }, + { 93, 45, 97, 0 }, }, + { { 133, 221, 120, 0 }, + { 83, 156, 77, 1 }, + { 15, 93, 208, 1 }, + { 217, 28, 229, 0 }, }, + { { 133, 221, 123, 1 }, + { 91, 222, 77, 1 }, + { 239, 93, 208, 1 }, + { 217, 61, 237, 0 }, }, + { { 133, 221, 116, 1 }, + { 91, 156, 93, 0 }, + { 151, 93, 208, 1 }, + { 93, 28, 237, 0 }, }, + { { 133, 221, 119, 0 }, + { 83, 222, 93, 0 }, + { 119, 93, 208, 1 }, + { 93, 61, 229, 0 }, }, + { { 133, 220, 152, 0 }, + { 83, 24, 108, 1 }, + { 12, 157, 208, 1 }, + { 155, 12, 101, 0 }, }, + { { 133, 220, 155, 1 }, + { 91, 90, 108, 1 }, + { 236, 157, 208, 1 }, + { 155, 45, 109, 0 }, }, + { { 133, 220, 148, 1 }, + { 91, 24, 124, 0 }, + { 148, 157, 208, 1 }, + { 31, 12, 109, 0 }, }, + { { 133, 220, 151, 0 }, + { 83, 90, 124, 0 }, + { 116, 157, 208, 1 }, + { 31, 45, 101, 0 }, }, + { { 133, 220, 232, 0 }, + { 67, 156, 108, 1 }, + { 11, 157, 208, 1 }, + { 155, 28, 225, 0 }, }, + { { 133, 220, 235, 1 }, + { 75, 222, 108, 1 }, + { 235, 157, 208, 1 }, + { 155, 61, 233, 0 }, }, + { { 133, 220, 228, 1 }, + { 75, 156, 124, 0 }, + { 147, 157, 208, 1 }, + { 31, 28, 233, 0 }, }, + { { 133, 220, 231, 0 }, + { 67, 222, 124, 0 }, + { 115, 157, 208, 1 }, + { 31, 61, 225, 0 }, }, + { { 130, 97, 8, 0 }, + { 64, 0, 139, 1 }, + { 8, 67, 32, 1 }, + { 232, 128, 1, 0 }, }, + { { 130, 97, 11, 1 }, + { 72, 66, 139, 1 }, + { 232, 67, 32, 1 }, + { 232, 161, 9, 0 }, }, + { { 130, 97, 4, 1 }, + { 72, 0, 155, 0 }, + { 144, 67, 32, 1 }, + { 108, 128, 9, 0 }, }, + { { 130, 97, 7, 0 }, + { 64, 66, 155, 0 }, + { 112, 67, 32, 1 }, + { 108, 161, 1, 0 }, }, + { { 130, 97, 120, 0 }, + { 80, 132, 139, 1 }, + { 15, 67, 32, 1 }, + { 232, 144, 133, 0 }, }, + { { 130, 97, 123, 1 }, + { 88, 198, 139, 1 }, + { 239, 67, 32, 1 }, + { 232, 177, 141, 0 }, }, + { { 130, 97, 116, 1 }, + { 88, 132, 155, 0 }, + { 151, 67, 32, 1 }, + { 108, 144, 141, 0 }, }, + { { 130, 97, 119, 0 }, + { 80, 198, 155, 0 }, + { 119, 67, 32, 1 }, + { 108, 177, 133, 0 }, }, + { { 130, 96, 152, 0 }, + { 80, 0, 170, 1 }, + { 12, 131, 32, 1 }, + { 170, 128, 5, 0 }, }, + { { 130, 96, 155, 1 }, + { 88, 66, 170, 1 }, + { 236, 131, 32, 1 }, + { 170, 161, 13, 0 }, }, + { { 130, 96, 148, 1 }, + { 88, 0, 186, 0 }, + { 148, 131, 32, 1 }, + { 46, 128, 13, 0 }, }, + { { 130, 96, 151, 0 }, + { 80, 66, 186, 0 }, + { 116, 131, 32, 1 }, + { 46, 161, 5, 0 }, }, + { { 130, 96, 232, 0 }, + { 64, 132, 170, 1 }, + { 11, 131, 32, 1 }, + { 170, 144, 129, 0 }, }, + { { 130, 96, 235, 1 }, + { 72, 198, 170, 1 }, + { 235, 131, 32, 1 }, + { 170, 177, 137, 0 }, }, + { { 130, 96, 228, 1 }, + { 72, 132, 186, 0 }, + { 147, 131, 32, 1 }, + { 46, 144, 137, 0 }, }, + { { 130, 96, 231, 0 }, + { 64, 198, 186, 0 }, + { 115, 131, 32, 1 }, + { 46, 177, 129, 0 }, }, + { { 130, 111, 8, 0 }, + { 97, 8, 139, 1 }, + { 8, 123, 32, 1 }, + { 232, 136, 67, 0 }, }, + { { 130, 111, 11, 1 }, + { 105, 74, 139, 1 }, + { 232, 123, 32, 1 }, + { 232, 169, 75, 0 }, }, + { { 130, 111, 4, 1 }, + { 105, 8, 155, 0 }, + { 144, 123, 32, 1 }, + { 108, 136, 75, 0 }, }, + { { 130, 111, 7, 0 }, + { 97, 74, 155, 0 }, + { 112, 123, 32, 1 }, + { 108, 169, 67, 0 }, }, + { { 130, 111, 120, 0 }, + { 113, 140, 139, 1 }, + { 15, 123, 32, 1 }, + { 232, 152, 199, 0 }, }, + { { 130, 111, 123, 1 }, + { 121, 206, 139, 1 }, + { 239, 123, 32, 1 }, + { 232, 185, 207, 0 }, }, + { { 130, 111, 116, 1 }, + { 121, 140, 155, 0 }, + { 151, 123, 32, 1 }, + { 108, 152, 207, 0 }, }, + { { 130, 111, 119, 0 }, + { 113, 206, 155, 0 }, + { 119, 123, 32, 1 }, + { 108, 185, 199, 0 }, }, + { { 130, 110, 152, 0 }, + { 113, 8, 170, 1 }, + { 12, 187, 32, 1 }, + { 170, 136, 71, 0 }, }, + { { 130, 110, 155, 1 }, + { 121, 74, 170, 1 }, + { 236, 187, 32, 1 }, + { 170, 169, 79, 0 }, }, + { { 130, 110, 148, 1 }, + { 121, 8, 186, 0 }, + { 148, 187, 32, 1 }, + { 46, 136, 79, 0 }, }, + { { 130, 110, 151, 0 }, + { 113, 74, 186, 0 }, + { 116, 187, 32, 1 }, + { 46, 169, 71, 0 }, }, + { { 130, 110, 232, 0 }, + { 97, 140, 170, 1 }, + { 11, 187, 32, 1 }, + { 170, 152, 195, 0 }, }, + { { 130, 110, 235, 1 }, + { 105, 206, 170, 1 }, + { 235, 187, 32, 1 }, + { 170, 185, 203, 0 }, }, + { { 130, 110, 228, 1 }, + { 105, 140, 186, 0 }, + { 147, 187, 32, 1 }, + { 46, 152, 203, 0 }, }, + { { 130, 110, 231, 0 }, + { 97, 206, 186, 0 }, + { 115, 187, 32, 1 }, + { 46, 185, 195, 0 }, }, + { { 130, 83, 8, 0 }, + { 96, 0, 201, 1 }, + { 8, 101, 32, 1 }, + { 201, 128, 3, 0 }, }, + { { 130, 83, 11, 1 }, + { 104, 66, 201, 1 }, + { 232, 101, 32, 1 }, + { 201, 161, 11, 0 }, }, + { { 130, 83, 4, 1 }, + { 104, 0, 217, 0 }, + { 144, 101, 32, 1 }, + { 77, 128, 11, 0 }, }, + { { 130, 83, 7, 0 }, + { 96, 66, 217, 0 }, + { 112, 101, 32, 1 }, + { 77, 161, 3, 0 }, }, + { { 130, 83, 120, 0 }, + { 112, 132, 201, 1 }, + { 15, 101, 32, 1 }, + { 201, 144, 135, 0 }, }, + { { 130, 83, 123, 1 }, + { 120, 198, 201, 1 }, + { 239, 101, 32, 1 }, + { 201, 177, 143, 0 }, }, + { { 130, 83, 116, 1 }, + { 120, 132, 217, 0 }, + { 151, 101, 32, 1 }, + { 77, 144, 143, 0 }, }, + { { 130, 83, 119, 0 }, + { 112, 198, 217, 0 }, + { 119, 101, 32, 1 }, + { 77, 177, 135, 0 }, }, + { { 130, 82, 152, 0 }, + { 112, 0, 232, 1 }, + { 12, 165, 32, 1 }, + { 139, 128, 7, 0 }, }, + { { 130, 82, 155, 1 }, + { 120, 66, 232, 1 }, + { 236, 165, 32, 1 }, + { 139, 161, 15, 0 }, }, + { { 130, 82, 148, 1 }, + { 120, 0, 248, 0 }, + { 148, 165, 32, 1 }, + { 15, 128, 15, 0 }, }, + { { 130, 82, 151, 0 }, + { 112, 66, 248, 0 }, + { 116, 165, 32, 1 }, + { 15, 161, 7, 0 }, }, + { { 130, 82, 232, 0 }, + { 96, 132, 232, 1 }, + { 11, 165, 32, 1 }, + { 139, 144, 131, 0 }, }, + { { 130, 82, 235, 1 }, + { 104, 198, 232, 1 }, + { 235, 165, 32, 1 }, + { 139, 177, 139, 0 }, }, + { { 130, 82, 228, 1 }, + { 104, 132, 248, 0 }, + { 147, 165, 32, 1 }, + { 15, 144, 139, 0 }, }, + { { 130, 82, 231, 0 }, + { 96, 198, 248, 0 }, + { 115, 165, 32, 1 }, + { 15, 177, 131, 0 }, }, + { { 130, 93, 8, 0 }, + { 65, 8, 201, 1 }, + { 8, 93, 32, 1 }, + { 201, 136, 65, 0 }, }, + { { 130, 93, 11, 1 }, + { 73, 74, 201, 1 }, + { 232, 93, 32, 1 }, + { 201, 169, 73, 0 }, }, + { { 130, 93, 4, 1 }, + { 73, 8, 217, 0 }, + { 144, 93, 32, 1 }, + { 77, 136, 73, 0 }, }, + { { 130, 93, 7, 0 }, + { 65, 74, 217, 0 }, + { 112, 93, 32, 1 }, + { 77, 169, 65, 0 }, }, + { { 130, 93, 120, 0 }, + { 81, 140, 201, 1 }, + { 15, 93, 32, 1 }, + { 201, 152, 197, 0 }, }, + { { 130, 93, 123, 1 }, + { 89, 206, 201, 1 }, + { 239, 93, 32, 1 }, + { 201, 185, 205, 0 }, }, + { { 130, 93, 116, 1 }, + { 89, 140, 217, 0 }, + { 151, 93, 32, 1 }, + { 77, 152, 205, 0 }, }, + { { 130, 93, 119, 0 }, + { 81, 206, 217, 0 }, + { 119, 93, 32, 1 }, + { 77, 185, 197, 0 }, }, + { { 130, 92, 152, 0 }, + { 81, 8, 232, 1 }, + { 12, 157, 32, 1 }, + { 139, 136, 69, 0 }, }, + { { 130, 92, 155, 1 }, + { 89, 74, 232, 1 }, + { 236, 157, 32, 1 }, + { 139, 169, 77, 0 }, }, + { { 130, 92, 148, 1 }, + { 89, 8, 248, 0 }, + { 148, 157, 32, 1 }, + { 15, 136, 77, 0 }, }, + { { 130, 92, 151, 0 }, + { 81, 74, 248, 0 }, + { 116, 157, 32, 1 }, + { 15, 169, 69, 0 }, }, + { { 130, 92, 232, 0 }, + { 65, 140, 232, 1 }, + { 11, 157, 32, 1 }, + { 139, 152, 193, 0 }, }, + { { 130, 92, 235, 1 }, + { 73, 206, 232, 1 }, + { 235, 157, 32, 1 }, + { 139, 185, 201, 0 }, }, + { { 130, 92, 228, 1 }, + { 73, 140, 248, 0 }, + { 147, 157, 32, 1 }, + { 15, 152, 201, 0 }, }, + { { 130, 92, 231, 0 }, + { 65, 206, 248, 0 }, + { 115, 157, 32, 1 }, + { 15, 185, 193, 0 }, }, + { { 131, 161, 8, 0 }, + { 2, 16, 139, 1 }, + { 8, 66, 224, 1 }, + { 232, 132, 32, 0 }, }, + { { 131, 161, 11, 1 }, + { 10, 82, 139, 1 }, + { 232, 66, 224, 1 }, + { 232, 165, 40, 0 }, }, + { { 131, 161, 4, 1 }, + { 10, 16, 155, 0 }, + { 144, 66, 224, 1 }, + { 108, 132, 40, 0 }, }, + { { 131, 161, 7, 0 }, + { 2, 82, 155, 0 }, + { 112, 66, 224, 1 }, + { 108, 165, 32, 0 }, }, + { { 131, 161, 120, 0 }, + { 18, 148, 139, 1 }, + { 15, 66, 224, 1 }, + { 232, 148, 164, 0 }, }, + { { 131, 161, 123, 1 }, + { 26, 214, 139, 1 }, + { 239, 66, 224, 1 }, + { 232, 181, 172, 0 }, }, + { { 131, 161, 116, 1 }, + { 26, 148, 155, 0 }, + { 151, 66, 224, 1 }, + { 108, 148, 172, 0 }, }, + { { 131, 161, 119, 0 }, + { 18, 214, 155, 0 }, + { 119, 66, 224, 1 }, + { 108, 181, 164, 0 }, }, + { { 131, 160, 152, 0 }, + { 18, 16, 170, 1 }, + { 12, 130, 224, 1 }, + { 170, 132, 36, 0 }, }, + { { 131, 160, 155, 1 }, + { 26, 82, 170, 1 }, + { 236, 130, 224, 1 }, + { 170, 165, 44, 0 }, }, + { { 131, 160, 148, 1 }, + { 26, 16, 186, 0 }, + { 148, 130, 224, 1 }, + { 46, 132, 44, 0 }, }, + { { 131, 160, 151, 0 }, + { 18, 82, 186, 0 }, + { 116, 130, 224, 1 }, + { 46, 165, 36, 0 }, }, + { { 131, 160, 232, 0 }, + { 2, 148, 170, 1 }, + { 11, 130, 224, 1 }, + { 170, 148, 160, 0 }, }, + { { 131, 160, 235, 1 }, + { 10, 214, 170, 1 }, + { 235, 130, 224, 1 }, + { 170, 181, 168, 0 }, }, + { { 131, 160, 228, 1 }, + { 10, 148, 186, 0 }, + { 147, 130, 224, 1 }, + { 46, 148, 168, 0 }, }, + { { 131, 160, 231, 0 }, + { 2, 214, 186, 0 }, + { 115, 130, 224, 1 }, + { 46, 181, 160, 0 }, }, + { { 131, 175, 8, 0 }, + { 35, 24, 139, 1 }, + { 8, 122, 224, 1 }, + { 232, 140, 98, 0 }, }, + { { 131, 175, 11, 1 }, + { 43, 90, 139, 1 }, + { 232, 122, 224, 1 }, + { 232, 173, 106, 0 }, }, + { { 131, 175, 4, 1 }, + { 43, 24, 155, 0 }, + { 144, 122, 224, 1 }, + { 108, 140, 106, 0 }, }, + { { 131, 175, 7, 0 }, + { 35, 90, 155, 0 }, + { 112, 122, 224, 1 }, + { 108, 173, 98, 0 }, }, + { { 131, 175, 120, 0 }, + { 51, 156, 139, 1 }, + { 15, 122, 224, 1 }, + { 232, 156, 230, 0 }, }, + { { 131, 175, 123, 1 }, + { 59, 222, 139, 1 }, + { 239, 122, 224, 1 }, + { 232, 189, 238, 0 }, }, + { { 131, 175, 116, 1 }, + { 59, 156, 155, 0 }, + { 151, 122, 224, 1 }, + { 108, 156, 238, 0 }, }, + { { 131, 175, 119, 0 }, + { 51, 222, 155, 0 }, + { 119, 122, 224, 1 }, + { 108, 189, 230, 0 }, }, + { { 131, 174, 152, 0 }, + { 51, 24, 170, 1 }, + { 12, 186, 224, 1 }, + { 170, 140, 102, 0 }, }, + { { 131, 174, 155, 1 }, + { 59, 90, 170, 1 }, + { 236, 186, 224, 1 }, + { 170, 173, 110, 0 }, }, + { { 131, 174, 148, 1 }, + { 59, 24, 186, 0 }, + { 148, 186, 224, 1 }, + { 46, 140, 110, 0 }, }, + { { 131, 174, 151, 0 }, + { 51, 90, 186, 0 }, + { 116, 186, 224, 1 }, + { 46, 173, 102, 0 }, }, + { { 131, 174, 232, 0 }, + { 35, 156, 170, 1 }, + { 11, 186, 224, 1 }, + { 170, 156, 226, 0 }, }, + { { 131, 174, 235, 1 }, + { 43, 222, 170, 1 }, + { 235, 186, 224, 1 }, + { 170, 189, 234, 0 }, }, + { { 131, 174, 228, 1 }, + { 43, 156, 186, 0 }, + { 147, 186, 224, 1 }, + { 46, 156, 234, 0 }, }, + { { 131, 174, 231, 0 }, + { 35, 222, 186, 0 }, + { 115, 186, 224, 1 }, + { 46, 189, 226, 0 }, }, + { { 131, 147, 8, 0 }, + { 34, 16, 201, 1 }, + { 8, 100, 224, 1 }, + { 201, 132, 34, 0 }, }, + { { 131, 147, 11, 1 }, + { 42, 82, 201, 1 }, + { 232, 100, 224, 1 }, + { 201, 165, 42, 0 }, }, + { { 131, 147, 4, 1 }, + { 42, 16, 217, 0 }, + { 144, 100, 224, 1 }, + { 77, 132, 42, 0 }, }, + { { 131, 147, 7, 0 }, + { 34, 82, 217, 0 }, + { 112, 100, 224, 1 }, + { 77, 165, 34, 0 }, }, + { { 131, 147, 120, 0 }, + { 50, 148, 201, 1 }, + { 15, 100, 224, 1 }, + { 201, 148, 166, 0 }, }, + { { 131, 147, 123, 1 }, + { 58, 214, 201, 1 }, + { 239, 100, 224, 1 }, + { 201, 181, 174, 0 }, }, + { { 131, 147, 116, 1 }, + { 58, 148, 217, 0 }, + { 151, 100, 224, 1 }, + { 77, 148, 174, 0 }, }, + { { 131, 147, 119, 0 }, + { 50, 214, 217, 0 }, + { 119, 100, 224, 1 }, + { 77, 181, 166, 0 }, }, + { { 131, 146, 152, 0 }, + { 50, 16, 232, 1 }, + { 12, 164, 224, 1 }, + { 139, 132, 38, 0 }, }, + { { 131, 146, 155, 1 }, + { 58, 82, 232, 1 }, + { 236, 164, 224, 1 }, + { 139, 165, 46, 0 }, }, + { { 131, 146, 148, 1 }, + { 58, 16, 248, 0 }, + { 148, 164, 224, 1 }, + { 15, 132, 46, 0 }, }, + { { 131, 146, 151, 0 }, + { 50, 82, 248, 0 }, + { 116, 164, 224, 1 }, + { 15, 165, 38, 0 }, }, + { { 131, 146, 232, 0 }, + { 34, 148, 232, 1 }, + { 11, 164, 224, 1 }, + { 139, 148, 162, 0 }, }, + { { 131, 146, 235, 1 }, + { 42, 214, 232, 1 }, + { 235, 164, 224, 1 }, + { 139, 181, 170, 0 }, }, + { { 131, 146, 228, 1 }, + { 42, 148, 248, 0 }, + { 147, 164, 224, 1 }, + { 15, 148, 170, 0 }, }, + { { 131, 146, 231, 0 }, + { 34, 214, 248, 0 }, + { 115, 164, 224, 1 }, + { 15, 181, 162, 0 }, }, + { { 131, 157, 8, 0 }, + { 3, 24, 201, 1 }, + { 8, 92, 224, 1 }, + { 201, 140, 96, 0 }, }, + { { 131, 157, 11, 1 }, + { 11, 90, 201, 1 }, + { 232, 92, 224, 1 }, + { 201, 173, 104, 0 }, }, + { { 131, 157, 4, 1 }, + { 11, 24, 217, 0 }, + { 144, 92, 224, 1 }, + { 77, 140, 104, 0 }, }, + { { 131, 157, 7, 0 }, + { 3, 90, 217, 0 }, + { 112, 92, 224, 1 }, + { 77, 173, 96, 0 }, }, + { { 131, 157, 120, 0 }, + { 19, 156, 201, 1 }, + { 15, 92, 224, 1 }, + { 201, 156, 228, 0 }, }, + { { 131, 157, 123, 1 }, + { 27, 222, 201, 1 }, + { 239, 92, 224, 1 }, + { 201, 189, 236, 0 }, }, + { { 131, 157, 116, 1 }, + { 27, 156, 217, 0 }, + { 151, 92, 224, 1 }, + { 77, 156, 236, 0 }, }, + { { 131, 157, 119, 0 }, + { 19, 222, 217, 0 }, + { 119, 92, 224, 1 }, + { 77, 189, 228, 0 }, }, + { { 131, 156, 152, 0 }, + { 19, 24, 232, 1 }, + { 12, 156, 224, 1 }, + { 139, 140, 100, 0 }, }, + { { 131, 156, 155, 1 }, + { 27, 90, 232, 1 }, + { 236, 156, 224, 1 }, + { 139, 173, 108, 0 }, }, + { { 131, 156, 148, 1 }, + { 27, 24, 248, 0 }, + { 148, 156, 224, 1 }, + { 15, 140, 108, 0 }, }, + { { 131, 156, 151, 0 }, + { 19, 90, 248, 0 }, + { 116, 156, 224, 1 }, + { 15, 173, 100, 0 }, }, + { { 131, 156, 232, 0 }, + { 3, 156, 232, 1 }, + { 11, 156, 224, 1 }, + { 139, 156, 224, 0 }, }, + { { 131, 156, 235, 1 }, + { 11, 222, 232, 1 }, + { 235, 156, 224, 1 }, + { 139, 189, 232, 0 }, }, + { { 131, 156, 228, 1 }, + { 11, 156, 248, 0 }, + { 147, 156, 224, 1 }, + { 15, 156, 232, 0 }, }, + { { 131, 156, 231, 0 }, + { 3, 222, 248, 0 }, + { 115, 156, 224, 1 }, + { 15, 189, 224, 0 }, }, + { { 188, 33, 8, 0 }, + { 132, 32, 15, 1 }, + { 8, 66, 30, 1 }, + { 248, 2, 16, 1 }, }, + { { 188, 33, 11, 1 }, + { 140, 98, 15, 1 }, + { 232, 66, 30, 1 }, + { 248, 35, 24, 1 }, }, + { { 188, 33, 4, 1 }, + { 140, 32, 31, 0 }, + { 144, 66, 30, 1 }, + { 124, 2, 24, 1 }, }, + { { 188, 33, 7, 0 }, + { 132, 98, 31, 0 }, + { 112, 66, 30, 1 }, + { 124, 35, 16, 1 }, }, + { { 188, 33, 120, 0 }, + { 148, 164, 15, 1 }, + { 15, 66, 30, 1 }, + { 248, 18, 148, 1 }, }, + { { 188, 33, 123, 1 }, + { 156, 230, 15, 1 }, + { 239, 66, 30, 1 }, + { 248, 51, 156, 1 }, }, + { { 188, 33, 116, 1 }, + { 156, 164, 31, 0 }, + { 151, 66, 30, 1 }, + { 124, 18, 156, 1 }, }, + { { 188, 33, 119, 0 }, + { 148, 230, 31, 0 }, + { 119, 66, 30, 1 }, + { 124, 51, 148, 1 }, }, + { { 188, 32, 152, 0 }, + { 148, 32, 46, 1 }, + { 12, 130, 30, 1 }, + { 186, 2, 20, 1 }, }, + { { 188, 32, 155, 1 }, + { 156, 98, 46, 1 }, + { 236, 130, 30, 1 }, + { 186, 35, 28, 1 }, }, + { { 188, 32, 148, 1 }, + { 156, 32, 62, 0 }, + { 148, 130, 30, 1 }, + { 62, 2, 28, 1 }, }, + { { 188, 32, 151, 0 }, + { 148, 98, 62, 0 }, + { 116, 130, 30, 1 }, + { 62, 35, 20, 1 }, }, + { { 188, 32, 232, 0 }, + { 132, 164, 46, 1 }, + { 11, 130, 30, 1 }, + { 186, 18, 144, 1 }, }, + { { 188, 32, 235, 1 }, + { 140, 230, 46, 1 }, + { 235, 130, 30, 1 }, + { 186, 51, 152, 1 }, }, + { { 188, 32, 228, 1 }, + { 140, 164, 62, 0 }, + { 147, 130, 30, 1 }, + { 62, 18, 152, 1 }, }, + { { 188, 32, 231, 0 }, + { 132, 230, 62, 0 }, + { 115, 130, 30, 1 }, + { 62, 51, 144, 1 }, }, + { { 188, 47, 8, 0 }, + { 165, 40, 15, 1 }, + { 8, 122, 30, 1 }, + { 248, 10, 82, 1 }, }, + { { 188, 47, 11, 1 }, + { 173, 106, 15, 1 }, + { 232, 122, 30, 1 }, + { 248, 43, 90, 1 }, }, + { { 188, 47, 4, 1 }, + { 173, 40, 31, 0 }, + { 144, 122, 30, 1 }, + { 124, 10, 90, 1 }, }, + { { 188, 47, 7, 0 }, + { 165, 106, 31, 0 }, + { 112, 122, 30, 1 }, + { 124, 43, 82, 1 }, }, + { { 188, 47, 120, 0 }, + { 181, 172, 15, 1 }, + { 15, 122, 30, 1 }, + { 248, 26, 214, 1 }, }, + { { 188, 47, 123, 1 }, + { 189, 238, 15, 1 }, + { 239, 122, 30, 1 }, + { 248, 59, 222, 1 }, }, + { { 188, 47, 116, 1 }, + { 189, 172, 31, 0 }, + { 151, 122, 30, 1 }, + { 124, 26, 222, 1 }, }, + { { 188, 47, 119, 0 }, + { 181, 238, 31, 0 }, + { 119, 122, 30, 1 }, + { 124, 59, 214, 1 }, }, + { { 188, 46, 152, 0 }, + { 181, 40, 46, 1 }, + { 12, 186, 30, 1 }, + { 186, 10, 86, 1 }, }, + { { 188, 46, 155, 1 }, + { 189, 106, 46, 1 }, + { 236, 186, 30, 1 }, + { 186, 43, 94, 1 }, }, + { { 188, 46, 148, 1 }, + { 189, 40, 62, 0 }, + { 148, 186, 30, 1 }, + { 62, 10, 94, 1 }, }, + { { 188, 46, 151, 0 }, + { 181, 106, 62, 0 }, + { 116, 186, 30, 1 }, + { 62, 43, 86, 1 }, }, + { { 188, 46, 232, 0 }, + { 165, 172, 46, 1 }, + { 11, 186, 30, 1 }, + { 186, 26, 210, 1 }, }, + { { 188, 46, 235, 1 }, + { 173, 238, 46, 1 }, + { 235, 186, 30, 1 }, + { 186, 59, 218, 1 }, }, + { { 188, 46, 228, 1 }, + { 173, 172, 62, 0 }, + { 147, 186, 30, 1 }, + { 62, 26, 218, 1 }, }, + { { 188, 46, 231, 0 }, + { 165, 238, 62, 0 }, + { 115, 186, 30, 1 }, + { 62, 59, 210, 1 }, }, + { { 188, 19, 8, 0 }, + { 164, 32, 77, 1 }, + { 8, 100, 30, 1 }, + { 217, 2, 18, 1 }, }, + { { 188, 19, 11, 1 }, + { 172, 98, 77, 1 }, + { 232, 100, 30, 1 }, + { 217, 35, 26, 1 }, }, + { { 188, 19, 4, 1 }, + { 172, 32, 93, 0 }, + { 144, 100, 30, 1 }, + { 93, 2, 26, 1 }, }, + { { 188, 19, 7, 0 }, + { 164, 98, 93, 0 }, + { 112, 100, 30, 1 }, + { 93, 35, 18, 1 }, }, + { { 188, 19, 120, 0 }, + { 180, 164, 77, 1 }, + { 15, 100, 30, 1 }, + { 217, 18, 150, 1 }, }, + { { 188, 19, 123, 1 }, + { 188, 230, 77, 1 }, + { 239, 100, 30, 1 }, + { 217, 51, 158, 1 }, }, + { { 188, 19, 116, 1 }, + { 188, 164, 93, 0 }, + { 151, 100, 30, 1 }, + { 93, 18, 158, 1 }, }, + { { 188, 19, 119, 0 }, + { 180, 230, 93, 0 }, + { 119, 100, 30, 1 }, + { 93, 51, 150, 1 }, }, + { { 188, 18, 152, 0 }, + { 180, 32, 108, 1 }, + { 12, 164, 30, 1 }, + { 155, 2, 22, 1 }, }, + { { 188, 18, 155, 1 }, + { 188, 98, 108, 1 }, + { 236, 164, 30, 1 }, + { 155, 35, 30, 1 }, }, + { { 188, 18, 148, 1 }, + { 188, 32, 124, 0 }, + { 148, 164, 30, 1 }, + { 31, 2, 30, 1 }, }, + { { 188, 18, 151, 0 }, + { 180, 98, 124, 0 }, + { 116, 164, 30, 1 }, + { 31, 35, 22, 1 }, }, + { { 188, 18, 232, 0 }, + { 164, 164, 108, 1 }, + { 11, 164, 30, 1 }, + { 155, 18, 146, 1 }, }, + { { 188, 18, 235, 1 }, + { 172, 230, 108, 1 }, + { 235, 164, 30, 1 }, + { 155, 51, 154, 1 }, }, + { { 188, 18, 228, 1 }, + { 172, 164, 124, 0 }, + { 147, 164, 30, 1 }, + { 31, 18, 154, 1 }, }, + { { 188, 18, 231, 0 }, + { 164, 230, 124, 0 }, + { 115, 164, 30, 1 }, + { 31, 51, 146, 1 }, }, + { { 188, 29, 8, 0 }, + { 133, 40, 77, 1 }, + { 8, 92, 30, 1 }, + { 217, 10, 80, 1 }, }, + { { 188, 29, 11, 1 }, + { 141, 106, 77, 1 }, + { 232, 92, 30, 1 }, + { 217, 43, 88, 1 }, }, + { { 188, 29, 4, 1 }, + { 141, 40, 93, 0 }, + { 144, 92, 30, 1 }, + { 93, 10, 88, 1 }, }, + { { 188, 29, 7, 0 }, + { 133, 106, 93, 0 }, + { 112, 92, 30, 1 }, + { 93, 43, 80, 1 }, }, + { { 188, 29, 120, 0 }, + { 149, 172, 77, 1 }, + { 15, 92, 30, 1 }, + { 217, 26, 212, 1 }, }, + { { 188, 29, 123, 1 }, + { 157, 238, 77, 1 }, + { 239, 92, 30, 1 }, + { 217, 59, 220, 1 }, }, + { { 188, 29, 116, 1 }, + { 157, 172, 93, 0 }, + { 151, 92, 30, 1 }, + { 93, 26, 220, 1 }, }, + { { 188, 29, 119, 0 }, + { 149, 238, 93, 0 }, + { 119, 92, 30, 1 }, + { 93, 59, 212, 1 }, }, + { { 188, 28, 152, 0 }, + { 149, 40, 108, 1 }, + { 12, 156, 30, 1 }, + { 155, 10, 84, 1 }, }, + { { 188, 28, 155, 1 }, + { 157, 106, 108, 1 }, + { 236, 156, 30, 1 }, + { 155, 43, 92, 1 }, }, + { { 188, 28, 148, 1 }, + { 157, 40, 124, 0 }, + { 148, 156, 30, 1 }, + { 31, 10, 92, 1 }, }, + { { 188, 28, 151, 0 }, + { 149, 106, 124, 0 }, + { 116, 156, 30, 1 }, + { 31, 43, 84, 1 }, }, + { { 188, 28, 232, 0 }, + { 133, 172, 108, 1 }, + { 11, 156, 30, 1 }, + { 155, 26, 208, 1 }, }, + { { 188, 28, 235, 1 }, + { 141, 238, 108, 1 }, + { 235, 156, 30, 1 }, + { 155, 59, 216, 1 }, }, + { { 188, 28, 228, 1 }, + { 141, 172, 124, 0 }, + { 147, 156, 30, 1 }, + { 31, 26, 216, 1 }, }, + { { 188, 28, 231, 0 }, + { 133, 238, 124, 0 }, + { 115, 156, 30, 1 }, + { 31, 59, 208, 1 }, }, + { { 189, 225, 8, 0 }, + { 198, 48, 15, 1 }, + { 8, 67, 222, 1 }, + { 248, 6, 49, 1 }, }, + { { 189, 225, 11, 1 }, + { 206, 114, 15, 1 }, + { 232, 67, 222, 1 }, + { 248, 39, 57, 1 }, }, + { { 189, 225, 4, 1 }, + { 206, 48, 31, 0 }, + { 144, 67, 222, 1 }, + { 124, 6, 57, 1 }, }, + { { 189, 225, 7, 0 }, + { 198, 114, 31, 0 }, + { 112, 67, 222, 1 }, + { 124, 39, 49, 1 }, }, + { { 189, 225, 120, 0 }, + { 214, 180, 15, 1 }, + { 15, 67, 222, 1 }, + { 248, 22, 181, 1 }, }, + { { 189, 225, 123, 1 }, + { 222, 246, 15, 1 }, + { 239, 67, 222, 1 }, + { 248, 55, 189, 1 }, }, + { { 189, 225, 116, 1 }, + { 222, 180, 31, 0 }, + { 151, 67, 222, 1 }, + { 124, 22, 189, 1 }, }, + { { 189, 225, 119, 0 }, + { 214, 246, 31, 0 }, + { 119, 67, 222, 1 }, + { 124, 55, 181, 1 }, }, + { { 189, 224, 152, 0 }, + { 214, 48, 46, 1 }, + { 12, 131, 222, 1 }, + { 186, 6, 53, 1 }, }, + { { 189, 224, 155, 1 }, + { 222, 114, 46, 1 }, + { 236, 131, 222, 1 }, + { 186, 39, 61, 1 }, }, + { { 189, 224, 148, 1 }, + { 222, 48, 62, 0 }, + { 148, 131, 222, 1 }, + { 62, 6, 61, 1 }, }, + { { 189, 224, 151, 0 }, + { 214, 114, 62, 0 }, + { 116, 131, 222, 1 }, + { 62, 39, 53, 1 }, }, + { { 189, 224, 232, 0 }, + { 198, 180, 46, 1 }, + { 11, 131, 222, 1 }, + { 186, 22, 177, 1 }, }, + { { 189, 224, 235, 1 }, + { 206, 246, 46, 1 }, + { 235, 131, 222, 1 }, + { 186, 55, 185, 1 }, }, + { { 189, 224, 228, 1 }, + { 206, 180, 62, 0 }, + { 147, 131, 222, 1 }, + { 62, 22, 185, 1 }, }, + { { 189, 224, 231, 0 }, + { 198, 246, 62, 0 }, + { 115, 131, 222, 1 }, + { 62, 55, 177, 1 }, }, + { { 189, 239, 8, 0 }, + { 231, 56, 15, 1 }, + { 8, 123, 222, 1 }, + { 248, 14, 115, 1 }, }, + { { 189, 239, 11, 1 }, + { 239, 122, 15, 1 }, + { 232, 123, 222, 1 }, + { 248, 47, 123, 1 }, }, + { { 189, 239, 4, 1 }, + { 239, 56, 31, 0 }, + { 144, 123, 222, 1 }, + { 124, 14, 123, 1 }, }, + { { 189, 239, 7, 0 }, + { 231, 122, 31, 0 }, + { 112, 123, 222, 1 }, + { 124, 47, 115, 1 }, }, + { { 189, 239, 120, 0 }, + { 247, 188, 15, 1 }, + { 15, 123, 222, 1 }, + { 248, 30, 247, 1 }, }, + { { 189, 239, 123, 1 }, + { 255, 254, 15, 1 }, + { 239, 123, 222, 1 }, + { 248, 63, 255, 1 }, }, + { { 189, 239, 116, 1 }, + { 255, 188, 31, 0 }, + { 151, 123, 222, 1 }, + { 124, 30, 255, 1 }, }, + { { 189, 239, 119, 0 }, + { 247, 254, 31, 0 }, + { 119, 123, 222, 1 }, + { 124, 63, 247, 1 }, }, + { { 189, 238, 152, 0 }, + { 247, 56, 46, 1 }, + { 12, 187, 222, 1 }, + { 186, 14, 119, 1 }, }, + { { 189, 238, 155, 1 }, + { 255, 122, 46, 1 }, + { 236, 187, 222, 1 }, + { 186, 47, 127, 1 }, }, + { { 189, 238, 148, 1 }, + { 255, 56, 62, 0 }, + { 148, 187, 222, 1 }, + { 62, 14, 127, 1 }, }, + { { 189, 238, 151, 0 }, + { 247, 122, 62, 0 }, + { 116, 187, 222, 1 }, + { 62, 47, 119, 1 }, }, + { { 189, 238, 232, 0 }, + { 231, 188, 46, 1 }, + { 11, 187, 222, 1 }, + { 186, 30, 243, 1 }, }, + { { 189, 238, 235, 1 }, + { 239, 254, 46, 1 }, + { 235, 187, 222, 1 }, + { 186, 63, 251, 1 }, }, + { { 189, 238, 228, 1 }, + { 239, 188, 62, 0 }, + { 147, 187, 222, 1 }, + { 62, 30, 251, 1 }, }, + { { 189, 238, 231, 0 }, + { 231, 254, 62, 0 }, + { 115, 187, 222, 1 }, + { 62, 63, 243, 1 }, }, + { { 189, 211, 8, 0 }, + { 230, 48, 77, 1 }, + { 8, 101, 222, 1 }, + { 217, 6, 51, 1 }, }, + { { 189, 211, 11, 1 }, + { 238, 114, 77, 1 }, + { 232, 101, 222, 1 }, + { 217, 39, 59, 1 }, }, + { { 189, 211, 4, 1 }, + { 238, 48, 93, 0 }, + { 144, 101, 222, 1 }, + { 93, 6, 59, 1 }, }, + { { 189, 211, 7, 0 }, + { 230, 114, 93, 0 }, + { 112, 101, 222, 1 }, + { 93, 39, 51, 1 }, }, + { { 189, 211, 120, 0 }, + { 246, 180, 77, 1 }, + { 15, 101, 222, 1 }, + { 217, 22, 183, 1 }, }, + { { 189, 211, 123, 1 }, + { 254, 246, 77, 1 }, + { 239, 101, 222, 1 }, + { 217, 55, 191, 1 }, }, + { { 189, 211, 116, 1 }, + { 254, 180, 93, 0 }, + { 151, 101, 222, 1 }, + { 93, 22, 191, 1 }, }, + { { 189, 211, 119, 0 }, + { 246, 246, 93, 0 }, + { 119, 101, 222, 1 }, + { 93, 55, 183, 1 }, }, + { { 189, 210, 152, 0 }, + { 246, 48, 108, 1 }, + { 12, 165, 222, 1 }, + { 155, 6, 55, 1 }, }, + { { 189, 210, 155, 1 }, + { 254, 114, 108, 1 }, + { 236, 165, 222, 1 }, + { 155, 39, 63, 1 }, }, + { { 189, 210, 148, 1 }, + { 254, 48, 124, 0 }, + { 148, 165, 222, 1 }, + { 31, 6, 63, 1 }, }, + { { 189, 210, 151, 0 }, + { 246, 114, 124, 0 }, + { 116, 165, 222, 1 }, + { 31, 39, 55, 1 }, }, + { { 189, 210, 232, 0 }, + { 230, 180, 108, 1 }, + { 11, 165, 222, 1 }, + { 155, 22, 179, 1 }, }, + { { 189, 210, 235, 1 }, + { 238, 246, 108, 1 }, + { 235, 165, 222, 1 }, + { 155, 55, 187, 1 }, }, + { { 189, 210, 228, 1 }, + { 238, 180, 124, 0 }, + { 147, 165, 222, 1 }, + { 31, 22, 187, 1 }, }, + { { 189, 210, 231, 0 }, + { 230, 246, 124, 0 }, + { 115, 165, 222, 1 }, + { 31, 55, 179, 1 }, }, + { { 189, 221, 8, 0 }, + { 199, 56, 77, 1 }, + { 8, 93, 222, 1 }, + { 217, 14, 113, 1 }, }, + { { 189, 221, 11, 1 }, + { 207, 122, 77, 1 }, + { 232, 93, 222, 1 }, + { 217, 47, 121, 1 }, }, + { { 189, 221, 4, 1 }, + { 207, 56, 93, 0 }, + { 144, 93, 222, 1 }, + { 93, 14, 121, 1 }, }, + { { 189, 221, 7, 0 }, + { 199, 122, 93, 0 }, + { 112, 93, 222, 1 }, + { 93, 47, 113, 1 }, }, + { { 189, 221, 120, 0 }, + { 215, 188, 77, 1 }, + { 15, 93, 222, 1 }, + { 217, 30, 245, 1 }, }, + { { 189, 221, 123, 1 }, + { 223, 254, 77, 1 }, + { 239, 93, 222, 1 }, + { 217, 63, 253, 1 }, }, + { { 189, 221, 116, 1 }, + { 223, 188, 93, 0 }, + { 151, 93, 222, 1 }, + { 93, 30, 253, 1 }, }, + { { 189, 221, 119, 0 }, + { 215, 254, 93, 0 }, + { 119, 93, 222, 1 }, + { 93, 63, 245, 1 }, }, + { { 189, 220, 152, 0 }, + { 215, 56, 108, 1 }, + { 12, 157, 222, 1 }, + { 155, 14, 117, 1 }, }, + { { 189, 220, 155, 1 }, + { 223, 122, 108, 1 }, + { 236, 157, 222, 1 }, + { 155, 47, 125, 1 }, }, + { { 189, 220, 148, 1 }, + { 223, 56, 124, 0 }, + { 148, 157, 222, 1 }, + { 31, 14, 125, 1 }, }, + { { 189, 220, 151, 0 }, + { 215, 122, 124, 0 }, + { 116, 157, 222, 1 }, + { 31, 47, 117, 1 }, }, + { { 189, 220, 232, 0 }, + { 199, 188, 108, 1 }, + { 11, 157, 222, 1 }, + { 155, 30, 241, 1 }, }, + { { 189, 220, 235, 1 }, + { 207, 254, 108, 1 }, + { 235, 157, 222, 1 }, + { 155, 63, 249, 1 }, }, + { { 189, 220, 228, 1 }, + { 207, 188, 124, 0 }, + { 147, 157, 222, 1 }, + { 31, 30, 249, 1 }, }, + { { 189, 220, 231, 0 }, + { 199, 254, 124, 0 }, + { 115, 157, 222, 1 }, + { 31, 63, 241, 1 }, }, + { { 186, 97, 8, 0 }, + { 196, 32, 139, 1 }, + { 8, 67, 46, 1 }, + { 232, 130, 17, 1 }, }, + { { 186, 97, 11, 1 }, + { 204, 98, 139, 1 }, + { 232, 67, 46, 1 }, + { 232, 163, 25, 1 }, }, + { { 186, 97, 4, 1 }, + { 204, 32, 155, 0 }, + { 144, 67, 46, 1 }, + { 108, 130, 25, 1 }, }, + { { 186, 97, 7, 0 }, + { 196, 98, 155, 0 }, + { 112, 67, 46, 1 }, + { 108, 163, 17, 1 }, }, + { { 186, 97, 120, 0 }, + { 212, 164, 139, 1 }, + { 15, 67, 46, 1 }, + { 232, 146, 149, 1 }, }, + { { 186, 97, 123, 1 }, + { 220, 230, 139, 1 }, + { 239, 67, 46, 1 }, + { 232, 179, 157, 1 }, }, + { { 186, 97, 116, 1 }, + { 220, 164, 155, 0 }, + { 151, 67, 46, 1 }, + { 108, 146, 157, 1 }, }, + { { 186, 97, 119, 0 }, + { 212, 230, 155, 0 }, + { 119, 67, 46, 1 }, + { 108, 179, 149, 1 }, }, + { { 186, 96, 152, 0 }, + { 212, 32, 170, 1 }, + { 12, 131, 46, 1 }, + { 170, 130, 21, 1 }, }, + { { 186, 96, 155, 1 }, + { 220, 98, 170, 1 }, + { 236, 131, 46, 1 }, + { 170, 163, 29, 1 }, }, + { { 186, 96, 148, 1 }, + { 220, 32, 186, 0 }, + { 148, 131, 46, 1 }, + { 46, 130, 29, 1 }, }, + { { 186, 96, 151, 0 }, + { 212, 98, 186, 0 }, + { 116, 131, 46, 1 }, + { 46, 163, 21, 1 }, }, + { { 186, 96, 232, 0 }, + { 196, 164, 170, 1 }, + { 11, 131, 46, 1 }, + { 170, 146, 145, 1 }, }, + { { 186, 96, 235, 1 }, + { 204, 230, 170, 1 }, + { 235, 131, 46, 1 }, + { 170, 179, 153, 1 }, }, + { { 186, 96, 228, 1 }, + { 204, 164, 186, 0 }, + { 147, 131, 46, 1 }, + { 46, 146, 153, 1 }, }, + { { 186, 96, 231, 0 }, + { 196, 230, 186, 0 }, + { 115, 131, 46, 1 }, + { 46, 179, 145, 1 }, }, + { { 186, 111, 8, 0 }, + { 229, 40, 139, 1 }, + { 8, 123, 46, 1 }, + { 232, 138, 83, 1 }, }, + { { 186, 111, 11, 1 }, + { 237, 106, 139, 1 }, + { 232, 123, 46, 1 }, + { 232, 171, 91, 1 }, }, + { { 186, 111, 4, 1 }, + { 237, 40, 155, 0 }, + { 144, 123, 46, 1 }, + { 108, 138, 91, 1 }, }, + { { 186, 111, 7, 0 }, + { 229, 106, 155, 0 }, + { 112, 123, 46, 1 }, + { 108, 171, 83, 1 }, }, + { { 186, 111, 120, 0 }, + { 245, 172, 139, 1 }, + { 15, 123, 46, 1 }, + { 232, 154, 215, 1 }, }, + { { 186, 111, 123, 1 }, + { 253, 238, 139, 1 }, + { 239, 123, 46, 1 }, + { 232, 187, 223, 1 }, }, + { { 186, 111, 116, 1 }, + { 253, 172, 155, 0 }, + { 151, 123, 46, 1 }, + { 108, 154, 223, 1 }, }, + { { 186, 111, 119, 0 }, + { 245, 238, 155, 0 }, + { 119, 123, 46, 1 }, + { 108, 187, 215, 1 }, }, + { { 186, 110, 152, 0 }, + { 245, 40, 170, 1 }, + { 12, 187, 46, 1 }, + { 170, 138, 87, 1 }, }, + { { 186, 110, 155, 1 }, + { 253, 106, 170, 1 }, + { 236, 187, 46, 1 }, + { 170, 171, 95, 1 }, }, + { { 186, 110, 148, 1 }, + { 253, 40, 186, 0 }, + { 148, 187, 46, 1 }, + { 46, 138, 95, 1 }, }, + { { 186, 110, 151, 0 }, + { 245, 106, 186, 0 }, + { 116, 187, 46, 1 }, + { 46, 171, 87, 1 }, }, + { { 186, 110, 232, 0 }, + { 229, 172, 170, 1 }, + { 11, 187, 46, 1 }, + { 170, 154, 211, 1 }, }, + { { 186, 110, 235, 1 }, + { 237, 238, 170, 1 }, + { 235, 187, 46, 1 }, + { 170, 187, 219, 1 }, }, + { { 186, 110, 228, 1 }, + { 237, 172, 186, 0 }, + { 147, 187, 46, 1 }, + { 46, 154, 219, 1 }, }, + { { 186, 110, 231, 0 }, + { 229, 238, 186, 0 }, + { 115, 187, 46, 1 }, + { 46, 187, 211, 1 }, }, + { { 186, 83, 8, 0 }, + { 228, 32, 201, 1 }, + { 8, 101, 46, 1 }, + { 201, 130, 19, 1 }, }, + { { 186, 83, 11, 1 }, + { 236, 98, 201, 1 }, + { 232, 101, 46, 1 }, + { 201, 163, 27, 1 }, }, + { { 186, 83, 4, 1 }, + { 236, 32, 217, 0 }, + { 144, 101, 46, 1 }, + { 77, 130, 27, 1 }, }, + { { 186, 83, 7, 0 }, + { 228, 98, 217, 0 }, + { 112, 101, 46, 1 }, + { 77, 163, 19, 1 }, }, + { { 186, 83, 120, 0 }, + { 244, 164, 201, 1 }, + { 15, 101, 46, 1 }, + { 201, 146, 151, 1 }, }, + { { 186, 83, 123, 1 }, + { 252, 230, 201, 1 }, + { 239, 101, 46, 1 }, + { 201, 179, 159, 1 }, }, + { { 186, 83, 116, 1 }, + { 252, 164, 217, 0 }, + { 151, 101, 46, 1 }, + { 77, 146, 159, 1 }, }, + { { 186, 83, 119, 0 }, + { 244, 230, 217, 0 }, + { 119, 101, 46, 1 }, + { 77, 179, 151, 1 }, }, + { { 186, 82, 152, 0 }, + { 244, 32, 232, 1 }, + { 12, 165, 46, 1 }, + { 139, 130, 23, 1 }, }, + { { 186, 82, 155, 1 }, + { 252, 98, 232, 1 }, + { 236, 165, 46, 1 }, + { 139, 163, 31, 1 }, }, + { { 186, 82, 148, 1 }, + { 252, 32, 248, 0 }, + { 148, 165, 46, 1 }, + { 15, 130, 31, 1 }, }, + { { 186, 82, 151, 0 }, + { 244, 98, 248, 0 }, + { 116, 165, 46, 1 }, + { 15, 163, 23, 1 }, }, + { { 186, 82, 232, 0 }, + { 228, 164, 232, 1 }, + { 11, 165, 46, 1 }, + { 139, 146, 147, 1 }, }, + { { 186, 82, 235, 1 }, + { 236, 230, 232, 1 }, + { 235, 165, 46, 1 }, + { 139, 179, 155, 1 }, }, + { { 186, 82, 228, 1 }, + { 236, 164, 248, 0 }, + { 147, 165, 46, 1 }, + { 15, 146, 155, 1 }, }, + { { 186, 82, 231, 0 }, + { 228, 230, 248, 0 }, + { 115, 165, 46, 1 }, + { 15, 179, 147, 1 }, }, + { { 186, 93, 8, 0 }, + { 197, 40, 201, 1 }, + { 8, 93, 46, 1 }, + { 201, 138, 81, 1 }, }, + { { 186, 93, 11, 1 }, + { 205, 106, 201, 1 }, + { 232, 93, 46, 1 }, + { 201, 171, 89, 1 }, }, + { { 186, 93, 4, 1 }, + { 205, 40, 217, 0 }, + { 144, 93, 46, 1 }, + { 77, 138, 89, 1 }, }, + { { 186, 93, 7, 0 }, + { 197, 106, 217, 0 }, + { 112, 93, 46, 1 }, + { 77, 171, 81, 1 }, }, + { { 186, 93, 120, 0 }, + { 213, 172, 201, 1 }, + { 15, 93, 46, 1 }, + { 201, 154, 213, 1 }, }, + { { 186, 93, 123, 1 }, + { 221, 238, 201, 1 }, + { 239, 93, 46, 1 }, + { 201, 187, 221, 1 }, }, + { { 186, 93, 116, 1 }, + { 221, 172, 217, 0 }, + { 151, 93, 46, 1 }, + { 77, 154, 221, 1 }, }, + { { 186, 93, 119, 0 }, + { 213, 238, 217, 0 }, + { 119, 93, 46, 1 }, + { 77, 187, 213, 1 }, }, + { { 186, 92, 152, 0 }, + { 213, 40, 232, 1 }, + { 12, 157, 46, 1 }, + { 139, 138, 85, 1 }, }, + { { 186, 92, 155, 1 }, + { 221, 106, 232, 1 }, + { 236, 157, 46, 1 }, + { 139, 171, 93, 1 }, }, + { { 186, 92, 148, 1 }, + { 221, 40, 248, 0 }, + { 148, 157, 46, 1 }, + { 15, 138, 93, 1 }, }, + { { 186, 92, 151, 0 }, + { 213, 106, 248, 0 }, + { 116, 157, 46, 1 }, + { 15, 171, 85, 1 }, }, + { { 186, 92, 232, 0 }, + { 197, 172, 232, 1 }, + { 11, 157, 46, 1 }, + { 139, 154, 209, 1 }, }, + { { 186, 92, 235, 1 }, + { 205, 238, 232, 1 }, + { 235, 157, 46, 1 }, + { 139, 187, 217, 1 }, }, + { { 186, 92, 228, 1 }, + { 205, 172, 248, 0 }, + { 147, 157, 46, 1 }, + { 15, 154, 217, 1 }, }, + { { 186, 92, 231, 0 }, + { 197, 238, 248, 0 }, + { 115, 157, 46, 1 }, + { 15, 187, 209, 1 }, }, + { { 187, 161, 8, 0 }, + { 134, 48, 139, 1 }, + { 8, 66, 238, 1 }, + { 232, 134, 48, 1 }, }, + { { 187, 161, 11, 1 }, + { 142, 114, 139, 1 }, + { 232, 66, 238, 1 }, + { 232, 167, 56, 1 }, }, + { { 187, 161, 4, 1 }, + { 142, 48, 155, 0 }, + { 144, 66, 238, 1 }, + { 108, 134, 56, 1 }, }, + { { 187, 161, 7, 0 }, + { 134, 114, 155, 0 }, + { 112, 66, 238, 1 }, + { 108, 167, 48, 1 }, }, + { { 187, 161, 120, 0 }, + { 150, 180, 139, 1 }, + { 15, 66, 238, 1 }, + { 232, 150, 180, 1 }, }, + { { 187, 161, 123, 1 }, + { 158, 246, 139, 1 }, + { 239, 66, 238, 1 }, + { 232, 183, 188, 1 }, }, + { { 187, 161, 116, 1 }, + { 158, 180, 155, 0 }, + { 151, 66, 238, 1 }, + { 108, 150, 188, 1 }, }, + { { 187, 161, 119, 0 }, + { 150, 246, 155, 0 }, + { 119, 66, 238, 1 }, + { 108, 183, 180, 1 }, }, + { { 187, 160, 152, 0 }, + { 150, 48, 170, 1 }, + { 12, 130, 238, 1 }, + { 170, 134, 52, 1 }, }, + { { 187, 160, 155, 1 }, + { 158, 114, 170, 1 }, + { 236, 130, 238, 1 }, + { 170, 167, 60, 1 }, }, + { { 187, 160, 148, 1 }, + { 158, 48, 186, 0 }, + { 148, 130, 238, 1 }, + { 46, 134, 60, 1 }, }, + { { 187, 160, 151, 0 }, + { 150, 114, 186, 0 }, + { 116, 130, 238, 1 }, + { 46, 167, 52, 1 }, }, + { { 187, 160, 232, 0 }, + { 134, 180, 170, 1 }, + { 11, 130, 238, 1 }, + { 170, 150, 176, 1 }, }, + { { 187, 160, 235, 1 }, + { 142, 246, 170, 1 }, + { 235, 130, 238, 1 }, + { 170, 183, 184, 1 }, }, + { { 187, 160, 228, 1 }, + { 142, 180, 186, 0 }, + { 147, 130, 238, 1 }, + { 46, 150, 184, 1 }, }, + { { 187, 160, 231, 0 }, + { 134, 246, 186, 0 }, + { 115, 130, 238, 1 }, + { 46, 183, 176, 1 }, }, + { { 187, 175, 8, 0 }, + { 167, 56, 139, 1 }, + { 8, 122, 238, 1 }, + { 232, 142, 114, 1 }, }, + { { 187, 175, 11, 1 }, + { 175, 122, 139, 1 }, + { 232, 122, 238, 1 }, + { 232, 175, 122, 1 }, }, + { { 187, 175, 4, 1 }, + { 175, 56, 155, 0 }, + { 144, 122, 238, 1 }, + { 108, 142, 122, 1 }, }, + { { 187, 175, 7, 0 }, + { 167, 122, 155, 0 }, + { 112, 122, 238, 1 }, + { 108, 175, 114, 1 }, }, + { { 187, 175, 120, 0 }, + { 183, 188, 139, 1 }, + { 15, 122, 238, 1 }, + { 232, 158, 246, 1 }, }, + { { 187, 175, 123, 1 }, + { 191, 254, 139, 1 }, + { 239, 122, 238, 1 }, + { 232, 191, 254, 1 }, }, + { { 187, 175, 116, 1 }, + { 191, 188, 155, 0 }, + { 151, 122, 238, 1 }, + { 108, 158, 254, 1 }, }, + { { 187, 175, 119, 0 }, + { 183, 254, 155, 0 }, + { 119, 122, 238, 1 }, + { 108, 191, 246, 1 }, }, + { { 187, 174, 152, 0 }, + { 183, 56, 170, 1 }, + { 12, 186, 238, 1 }, + { 170, 142, 118, 1 }, }, + { { 187, 174, 155, 1 }, + { 191, 122, 170, 1 }, + { 236, 186, 238, 1 }, + { 170, 175, 126, 1 }, }, + { { 187, 174, 148, 1 }, + { 191, 56, 186, 0 }, + { 148, 186, 238, 1 }, + { 46, 142, 126, 1 }, }, + { { 187, 174, 151, 0 }, + { 183, 122, 186, 0 }, + { 116, 186, 238, 1 }, + { 46, 175, 118, 1 }, }, + { { 187, 174, 232, 0 }, + { 167, 188, 170, 1 }, + { 11, 186, 238, 1 }, + { 170, 158, 242, 1 }, }, + { { 187, 174, 235, 1 }, + { 175, 254, 170, 1 }, + { 235, 186, 238, 1 }, + { 170, 191, 250, 1 }, }, + { { 187, 174, 228, 1 }, + { 175, 188, 186, 0 }, + { 147, 186, 238, 1 }, + { 46, 158, 250, 1 }, }, + { { 187, 174, 231, 0 }, + { 167, 254, 186, 0 }, + { 115, 186, 238, 1 }, + { 46, 191, 242, 1 }, }, + { { 187, 147, 8, 0 }, + { 166, 48, 201, 1 }, + { 8, 100, 238, 1 }, + { 201, 134, 50, 1 }, }, + { { 187, 147, 11, 1 }, + { 174, 114, 201, 1 }, + { 232, 100, 238, 1 }, + { 201, 167, 58, 1 }, }, + { { 187, 147, 4, 1 }, + { 174, 48, 217, 0 }, + { 144, 100, 238, 1 }, + { 77, 134, 58, 1 }, }, + { { 187, 147, 7, 0 }, + { 166, 114, 217, 0 }, + { 112, 100, 238, 1 }, + { 77, 167, 50, 1 }, }, + { { 187, 147, 120, 0 }, + { 182, 180, 201, 1 }, + { 15, 100, 238, 1 }, + { 201, 150, 182, 1 }, }, + { { 187, 147, 123, 1 }, + { 190, 246, 201, 1 }, + { 239, 100, 238, 1 }, + { 201, 183, 190, 1 }, }, + { { 187, 147, 116, 1 }, + { 190, 180, 217, 0 }, + { 151, 100, 238, 1 }, + { 77, 150, 190, 1 }, }, + { { 187, 147, 119, 0 }, + { 182, 246, 217, 0 }, + { 119, 100, 238, 1 }, + { 77, 183, 182, 1 }, }, + { { 187, 146, 152, 0 }, + { 182, 48, 232, 1 }, + { 12, 164, 238, 1 }, + { 139, 134, 54, 1 }, }, + { { 187, 146, 155, 1 }, + { 190, 114, 232, 1 }, + { 236, 164, 238, 1 }, + { 139, 167, 62, 1 }, }, + { { 187, 146, 148, 1 }, + { 190, 48, 248, 0 }, + { 148, 164, 238, 1 }, + { 15, 134, 62, 1 }, }, + { { 187, 146, 151, 0 }, + { 182, 114, 248, 0 }, + { 116, 164, 238, 1 }, + { 15, 167, 54, 1 }, }, + { { 187, 146, 232, 0 }, + { 166, 180, 232, 1 }, + { 11, 164, 238, 1 }, + { 139, 150, 178, 1 }, }, + { { 187, 146, 235, 1 }, + { 174, 246, 232, 1 }, + { 235, 164, 238, 1 }, + { 139, 183, 186, 1 }, }, + { { 187, 146, 228, 1 }, + { 174, 180, 248, 0 }, + { 147, 164, 238, 1 }, + { 15, 150, 186, 1 }, }, + { { 187, 146, 231, 0 }, + { 166, 246, 248, 0 }, + { 115, 164, 238, 1 }, + { 15, 183, 178, 1 }, }, + { { 187, 157, 8, 0 }, + { 135, 56, 201, 1 }, + { 8, 92, 238, 1 }, + { 201, 142, 112, 1 }, }, + { { 187, 157, 11, 1 }, + { 143, 122, 201, 1 }, + { 232, 92, 238, 1 }, + { 201, 175, 120, 1 }, }, + { { 187, 157, 4, 1 }, + { 143, 56, 217, 0 }, + { 144, 92, 238, 1 }, + { 77, 142, 120, 1 }, }, + { { 187, 157, 7, 0 }, + { 135, 122, 217, 0 }, + { 112, 92, 238, 1 }, + { 77, 175, 112, 1 }, }, + { { 187, 157, 120, 0 }, + { 151, 188, 201, 1 }, + { 15, 92, 238, 1 }, + { 201, 158, 244, 1 }, }, + { { 187, 157, 123, 1 }, + { 159, 254, 201, 1 }, + { 239, 92, 238, 1 }, + { 201, 191, 252, 1 }, }, + { { 187, 157, 116, 1 }, + { 159, 188, 217, 0 }, + { 151, 92, 238, 1 }, + { 77, 158, 252, 1 }, }, + { { 187, 157, 119, 0 }, + { 151, 254, 217, 0 }, + { 119, 92, 238, 1 }, + { 77, 191, 244, 1 }, }, + { { 187, 156, 152, 0 }, + { 151, 56, 232, 1 }, + { 12, 156, 238, 1 }, + { 139, 142, 116, 1 }, }, + { { 187, 156, 155, 1 }, + { 159, 122, 232, 1 }, + { 236, 156, 238, 1 }, + { 139, 175, 124, 1 }, }, + { { 187, 156, 148, 1 }, + { 159, 56, 248, 0 }, + { 148, 156, 238, 1 }, + { 15, 142, 124, 1 }, }, + { { 187, 156, 151, 0 }, + { 151, 122, 248, 0 }, + { 116, 156, 238, 1 }, + { 15, 175, 116, 1 }, }, + { { 187, 156, 232, 0 }, + { 135, 188, 232, 1 }, + { 11, 156, 238, 1 }, + { 139, 158, 240, 1 }, }, + { { 187, 156, 235, 1 }, + { 143, 254, 232, 1 }, + { 235, 156, 238, 1 }, + { 139, 191, 248, 1 }, }, + { { 187, 156, 228, 1 }, + { 143, 188, 248, 0 }, + { 147, 156, 238, 1 }, + { 15, 158, 248, 1 }, }, + { { 187, 156, 231, 0 }, + { 135, 254, 248, 0 }, + { 115, 156, 238, 1 }, + { 15, 191, 240, 1 }, }, + { { 76, 33, 8, 0 }, + { 128, 1, 7, 1 }, + { 8, 66, 25, 0 }, + { 240, 64, 0, 1 }, }, + { { 76, 33, 11, 1 }, + { 136, 67, 7, 1 }, + { 232, 66, 25, 0 }, + { 240, 97, 8, 1 }, }, + { { 76, 33, 4, 1 }, + { 136, 1, 23, 0 }, + { 144, 66, 25, 0 }, + { 116, 64, 8, 1 }, }, + { { 76, 33, 7, 0 }, + { 128, 67, 23, 0 }, + { 112, 66, 25, 0 }, + { 116, 97, 0, 1 }, }, + { { 76, 33, 120, 0 }, + { 144, 133, 7, 1 }, + { 15, 66, 25, 0 }, + { 240, 80, 132, 1 }, }, + { { 76, 33, 123, 1 }, + { 152, 199, 7, 1 }, + { 239, 66, 25, 0 }, + { 240, 113, 140, 1 }, }, + { { 76, 33, 116, 1 }, + { 152, 133, 23, 0 }, + { 151, 66, 25, 0 }, + { 116, 80, 140, 1 }, }, + { { 76, 33, 119, 0 }, + { 144, 199, 23, 0 }, + { 119, 66, 25, 0 }, + { 116, 113, 132, 1 }, }, + { { 76, 32, 152, 0 }, + { 144, 1, 38, 1 }, + { 12, 130, 25, 0 }, + { 178, 64, 4, 1 }, }, + { { 76, 32, 155, 1 }, + { 152, 67, 38, 1 }, + { 236, 130, 25, 0 }, + { 178, 97, 12, 1 }, }, + { { 76, 32, 148, 1 }, + { 152, 1, 54, 0 }, + { 148, 130, 25, 0 }, + { 54, 64, 12, 1 }, }, + { { 76, 32, 151, 0 }, + { 144, 67, 54, 0 }, + { 116, 130, 25, 0 }, + { 54, 97, 4, 1 }, }, + { { 76, 32, 232, 0 }, + { 128, 133, 38, 1 }, + { 11, 130, 25, 0 }, + { 178, 80, 128, 1 }, }, + { { 76, 32, 235, 1 }, + { 136, 199, 38, 1 }, + { 235, 130, 25, 0 }, + { 178, 113, 136, 1 }, }, + { { 76, 32, 228, 1 }, + { 136, 133, 54, 0 }, + { 147, 130, 25, 0 }, + { 54, 80, 136, 1 }, }, + { { 76, 32, 231, 0 }, + { 128, 199, 54, 0 }, + { 115, 130, 25, 0 }, + { 54, 113, 128, 1 }, }, + { { 76, 47, 8, 0 }, + { 161, 9, 7, 1 }, + { 8, 122, 25, 0 }, + { 240, 72, 66, 1 }, }, + { { 76, 47, 11, 1 }, + { 169, 75, 7, 1 }, + { 232, 122, 25, 0 }, + { 240, 105, 74, 1 }, }, + { { 76, 47, 4, 1 }, + { 169, 9, 23, 0 }, + { 144, 122, 25, 0 }, + { 116, 72, 74, 1 }, }, + { { 76, 47, 7, 0 }, + { 161, 75, 23, 0 }, + { 112, 122, 25, 0 }, + { 116, 105, 66, 1 }, }, + { { 76, 47, 120, 0 }, + { 177, 141, 7, 1 }, + { 15, 122, 25, 0 }, + { 240, 88, 198, 1 }, }, + { { 76, 47, 123, 1 }, + { 185, 207, 7, 1 }, + { 239, 122, 25, 0 }, + { 240, 121, 206, 1 }, }, + { { 76, 47, 116, 1 }, + { 185, 141, 23, 0 }, + { 151, 122, 25, 0 }, + { 116, 88, 206, 1 }, }, + { { 76, 47, 119, 0 }, + { 177, 207, 23, 0 }, + { 119, 122, 25, 0 }, + { 116, 121, 198, 1 }, }, + { { 76, 46, 152, 0 }, + { 177, 9, 38, 1 }, + { 12, 186, 25, 0 }, + { 178, 72, 70, 1 }, }, + { { 76, 46, 155, 1 }, + { 185, 75, 38, 1 }, + { 236, 186, 25, 0 }, + { 178, 105, 78, 1 }, }, + { { 76, 46, 148, 1 }, + { 185, 9, 54, 0 }, + { 148, 186, 25, 0 }, + { 54, 72, 78, 1 }, }, + { { 76, 46, 151, 0 }, + { 177, 75, 54, 0 }, + { 116, 186, 25, 0 }, + { 54, 105, 70, 1 }, }, + { { 76, 46, 232, 0 }, + { 161, 141, 38, 1 }, + { 11, 186, 25, 0 }, + { 178, 88, 194, 1 }, }, + { { 76, 46, 235, 1 }, + { 169, 207, 38, 1 }, + { 235, 186, 25, 0 }, + { 178, 121, 202, 1 }, }, + { { 76, 46, 228, 1 }, + { 169, 141, 54, 0 }, + { 147, 186, 25, 0 }, + { 54, 88, 202, 1 }, }, + { { 76, 46, 231, 0 }, + { 161, 207, 54, 0 }, + { 115, 186, 25, 0 }, + { 54, 121, 194, 1 }, }, + { { 76, 19, 8, 0 }, + { 160, 1, 69, 1 }, + { 8, 100, 25, 0 }, + { 209, 64, 2, 1 }, }, + { { 76, 19, 11, 1 }, + { 168, 67, 69, 1 }, + { 232, 100, 25, 0 }, + { 209, 97, 10, 1 }, }, + { { 76, 19, 4, 1 }, + { 168, 1, 85, 0 }, + { 144, 100, 25, 0 }, + { 85, 64, 10, 1 }, }, + { { 76, 19, 7, 0 }, + { 160, 67, 85, 0 }, + { 112, 100, 25, 0 }, + { 85, 97, 2, 1 }, }, + { { 76, 19, 120, 0 }, + { 176, 133, 69, 1 }, + { 15, 100, 25, 0 }, + { 209, 80, 134, 1 }, }, + { { 76, 19, 123, 1 }, + { 184, 199, 69, 1 }, + { 239, 100, 25, 0 }, + { 209, 113, 142, 1 }, }, + { { 76, 19, 116, 1 }, + { 184, 133, 85, 0 }, + { 151, 100, 25, 0 }, + { 85, 80, 142, 1 }, }, + { { 76, 19, 119, 0 }, + { 176, 199, 85, 0 }, + { 119, 100, 25, 0 }, + { 85, 113, 134, 1 }, }, + { { 76, 18, 152, 0 }, + { 176, 1, 100, 1 }, + { 12, 164, 25, 0 }, + { 147, 64, 6, 1 }, }, + { { 76, 18, 155, 1 }, + { 184, 67, 100, 1 }, + { 236, 164, 25, 0 }, + { 147, 97, 14, 1 }, }, + { { 76, 18, 148, 1 }, + { 184, 1, 116, 0 }, + { 148, 164, 25, 0 }, + { 23, 64, 14, 1 }, }, + { { 76, 18, 151, 0 }, + { 176, 67, 116, 0 }, + { 116, 164, 25, 0 }, + { 23, 97, 6, 1 }, }, + { { 76, 18, 232, 0 }, + { 160, 133, 100, 1 }, + { 11, 164, 25, 0 }, + { 147, 80, 130, 1 }, }, + { { 76, 18, 235, 1 }, + { 168, 199, 100, 1 }, + { 235, 164, 25, 0 }, + { 147, 113, 138, 1 }, }, + { { 76, 18, 228, 1 }, + { 168, 133, 116, 0 }, + { 147, 164, 25, 0 }, + { 23, 80, 138, 1 }, }, + { { 76, 18, 231, 0 }, + { 160, 199, 116, 0 }, + { 115, 164, 25, 0 }, + { 23, 113, 130, 1 }, }, + { { 76, 29, 8, 0 }, + { 129, 9, 69, 1 }, + { 8, 92, 25, 0 }, + { 209, 72, 64, 1 }, }, + { { 76, 29, 11, 1 }, + { 137, 75, 69, 1 }, + { 232, 92, 25, 0 }, + { 209, 105, 72, 1 }, }, + { { 76, 29, 4, 1 }, + { 137, 9, 85, 0 }, + { 144, 92, 25, 0 }, + { 85, 72, 72, 1 }, }, + { { 76, 29, 7, 0 }, + { 129, 75, 85, 0 }, + { 112, 92, 25, 0 }, + { 85, 105, 64, 1 }, }, + { { 76, 29, 120, 0 }, + { 145, 141, 69, 1 }, + { 15, 92, 25, 0 }, + { 209, 88, 196, 1 }, }, + { { 76, 29, 123, 1 }, + { 153, 207, 69, 1 }, + { 239, 92, 25, 0 }, + { 209, 121, 204, 1 }, }, + { { 76, 29, 116, 1 }, + { 153, 141, 85, 0 }, + { 151, 92, 25, 0 }, + { 85, 88, 204, 1 }, }, + { { 76, 29, 119, 0 }, + { 145, 207, 85, 0 }, + { 119, 92, 25, 0 }, + { 85, 121, 196, 1 }, }, + { { 76, 28, 152, 0 }, + { 145, 9, 100, 1 }, + { 12, 156, 25, 0 }, + { 147, 72, 68, 1 }, }, + { { 76, 28, 155, 1 }, + { 153, 75, 100, 1 }, + { 236, 156, 25, 0 }, + { 147, 105, 76, 1 }, }, + { { 76, 28, 148, 1 }, + { 153, 9, 116, 0 }, + { 148, 156, 25, 0 }, + { 23, 72, 76, 1 }, }, + { { 76, 28, 151, 0 }, + { 145, 75, 116, 0 }, + { 116, 156, 25, 0 }, + { 23, 105, 68, 1 }, }, + { { 76, 28, 232, 0 }, + { 129, 141, 100, 1 }, + { 11, 156, 25, 0 }, + { 147, 88, 192, 1 }, }, + { { 76, 28, 235, 1 }, + { 137, 207, 100, 1 }, + { 235, 156, 25, 0 }, + { 147, 121, 200, 1 }, }, + { { 76, 28, 228, 1 }, + { 137, 141, 116, 0 }, + { 147, 156, 25, 0 }, + { 23, 88, 200, 1 }, }, + { { 76, 28, 231, 0 }, + { 129, 207, 116, 0 }, + { 115, 156, 25, 0 }, + { 23, 121, 192, 1 }, }, + { { 77, 225, 8, 0 }, + { 194, 17, 7, 1 }, + { 8, 67, 217, 0 }, + { 240, 68, 33, 1 }, }, + { { 77, 225, 11, 1 }, + { 202, 83, 7, 1 }, + { 232, 67, 217, 0 }, + { 240, 101, 41, 1 }, }, + { { 77, 225, 4, 1 }, + { 202, 17, 23, 0 }, + { 144, 67, 217, 0 }, + { 116, 68, 41, 1 }, }, + { { 77, 225, 7, 0 }, + { 194, 83, 23, 0 }, + { 112, 67, 217, 0 }, + { 116, 101, 33, 1 }, }, + { { 77, 225, 120, 0 }, + { 210, 149, 7, 1 }, + { 15, 67, 217, 0 }, + { 240, 84, 165, 1 }, }, + { { 77, 225, 123, 1 }, + { 218, 215, 7, 1 }, + { 239, 67, 217, 0 }, + { 240, 117, 173, 1 }, }, + { { 77, 225, 116, 1 }, + { 218, 149, 23, 0 }, + { 151, 67, 217, 0 }, + { 116, 84, 173, 1 }, }, + { { 77, 225, 119, 0 }, + { 210, 215, 23, 0 }, + { 119, 67, 217, 0 }, + { 116, 117, 165, 1 }, }, + { { 77, 224, 152, 0 }, + { 210, 17, 38, 1 }, + { 12, 131, 217, 0 }, + { 178, 68, 37, 1 }, }, + { { 77, 224, 155, 1 }, + { 218, 83, 38, 1 }, + { 236, 131, 217, 0 }, + { 178, 101, 45, 1 }, }, + { { 77, 224, 148, 1 }, + { 218, 17, 54, 0 }, + { 148, 131, 217, 0 }, + { 54, 68, 45, 1 }, }, + { { 77, 224, 151, 0 }, + { 210, 83, 54, 0 }, + { 116, 131, 217, 0 }, + { 54, 101, 37, 1 }, }, + { { 77, 224, 232, 0 }, + { 194, 149, 38, 1 }, + { 11, 131, 217, 0 }, + { 178, 84, 161, 1 }, }, + { { 77, 224, 235, 1 }, + { 202, 215, 38, 1 }, + { 235, 131, 217, 0 }, + { 178, 117, 169, 1 }, }, + { { 77, 224, 228, 1 }, + { 202, 149, 54, 0 }, + { 147, 131, 217, 0 }, + { 54, 84, 169, 1 }, }, + { { 77, 224, 231, 0 }, + { 194, 215, 54, 0 }, + { 115, 131, 217, 0 }, + { 54, 117, 161, 1 }, }, + { { 77, 239, 8, 0 }, + { 227, 25, 7, 1 }, + { 8, 123, 217, 0 }, + { 240, 76, 99, 1 }, }, + { { 77, 239, 11, 1 }, + { 235, 91, 7, 1 }, + { 232, 123, 217, 0 }, + { 240, 109, 107, 1 }, }, + { { 77, 239, 4, 1 }, + { 235, 25, 23, 0 }, + { 144, 123, 217, 0 }, + { 116, 76, 107, 1 }, }, + { { 77, 239, 7, 0 }, + { 227, 91, 23, 0 }, + { 112, 123, 217, 0 }, + { 116, 109, 99, 1 }, }, + { { 77, 239, 120, 0 }, + { 243, 157, 7, 1 }, + { 15, 123, 217, 0 }, + { 240, 92, 231, 1 }, }, + { { 77, 239, 123, 1 }, + { 251, 223, 7, 1 }, + { 239, 123, 217, 0 }, + { 240, 125, 239, 1 }, }, + { { 77, 239, 116, 1 }, + { 251, 157, 23, 0 }, + { 151, 123, 217, 0 }, + { 116, 92, 239, 1 }, }, + { { 77, 239, 119, 0 }, + { 243, 223, 23, 0 }, + { 119, 123, 217, 0 }, + { 116, 125, 231, 1 }, }, + { { 77, 238, 152, 0 }, + { 243, 25, 38, 1 }, + { 12, 187, 217, 0 }, + { 178, 76, 103, 1 }, }, + { { 77, 238, 155, 1 }, + { 251, 91, 38, 1 }, + { 236, 187, 217, 0 }, + { 178, 109, 111, 1 }, }, + { { 77, 238, 148, 1 }, + { 251, 25, 54, 0 }, + { 148, 187, 217, 0 }, + { 54, 76, 111, 1 }, }, + { { 77, 238, 151, 0 }, + { 243, 91, 54, 0 }, + { 116, 187, 217, 0 }, + { 54, 109, 103, 1 }, }, + { { 77, 238, 232, 0 }, + { 227, 157, 38, 1 }, + { 11, 187, 217, 0 }, + { 178, 92, 227, 1 }, }, + { { 77, 238, 235, 1 }, + { 235, 223, 38, 1 }, + { 235, 187, 217, 0 }, + { 178, 125, 235, 1 }, }, + { { 77, 238, 228, 1 }, + { 235, 157, 54, 0 }, + { 147, 187, 217, 0 }, + { 54, 92, 235, 1 }, }, + { { 77, 238, 231, 0 }, + { 227, 223, 54, 0 }, + { 115, 187, 217, 0 }, + { 54, 125, 227, 1 }, }, + { { 77, 211, 8, 0 }, + { 226, 17, 69, 1 }, + { 8, 101, 217, 0 }, + { 209, 68, 35, 1 }, }, + { { 77, 211, 11, 1 }, + { 234, 83, 69, 1 }, + { 232, 101, 217, 0 }, + { 209, 101, 43, 1 }, }, + { { 77, 211, 4, 1 }, + { 234, 17, 85, 0 }, + { 144, 101, 217, 0 }, + { 85, 68, 43, 1 }, }, + { { 77, 211, 7, 0 }, + { 226, 83, 85, 0 }, + { 112, 101, 217, 0 }, + { 85, 101, 35, 1 }, }, + { { 77, 211, 120, 0 }, + { 242, 149, 69, 1 }, + { 15, 101, 217, 0 }, + { 209, 84, 167, 1 }, }, + { { 77, 211, 123, 1 }, + { 250, 215, 69, 1 }, + { 239, 101, 217, 0 }, + { 209, 117, 175, 1 }, }, + { { 77, 211, 116, 1 }, + { 250, 149, 85, 0 }, + { 151, 101, 217, 0 }, + { 85, 84, 175, 1 }, }, + { { 77, 211, 119, 0 }, + { 242, 215, 85, 0 }, + { 119, 101, 217, 0 }, + { 85, 117, 167, 1 }, }, + { { 77, 210, 152, 0 }, + { 242, 17, 100, 1 }, + { 12, 165, 217, 0 }, + { 147, 68, 39, 1 }, }, + { { 77, 210, 155, 1 }, + { 250, 83, 100, 1 }, + { 236, 165, 217, 0 }, + { 147, 101, 47, 1 }, }, + { { 77, 210, 148, 1 }, + { 250, 17, 116, 0 }, + { 148, 165, 217, 0 }, + { 23, 68, 47, 1 }, }, + { { 77, 210, 151, 0 }, + { 242, 83, 116, 0 }, + { 116, 165, 217, 0 }, + { 23, 101, 39, 1 }, }, + { { 77, 210, 232, 0 }, + { 226, 149, 100, 1 }, + { 11, 165, 217, 0 }, + { 147, 84, 163, 1 }, }, + { { 77, 210, 235, 1 }, + { 234, 215, 100, 1 }, + { 235, 165, 217, 0 }, + { 147, 117, 171, 1 }, }, + { { 77, 210, 228, 1 }, + { 234, 149, 116, 0 }, + { 147, 165, 217, 0 }, + { 23, 84, 171, 1 }, }, + { { 77, 210, 231, 0 }, + { 226, 215, 116, 0 }, + { 115, 165, 217, 0 }, + { 23, 117, 163, 1 }, }, + { { 77, 221, 8, 0 }, + { 195, 25, 69, 1 }, + { 8, 93, 217, 0 }, + { 209, 76, 97, 1 }, }, + { { 77, 221, 11, 1 }, + { 203, 91, 69, 1 }, + { 232, 93, 217, 0 }, + { 209, 109, 105, 1 }, }, + { { 77, 221, 4, 1 }, + { 203, 25, 85, 0 }, + { 144, 93, 217, 0 }, + { 85, 76, 105, 1 }, }, + { { 77, 221, 7, 0 }, + { 195, 91, 85, 0 }, + { 112, 93, 217, 0 }, + { 85, 109, 97, 1 }, }, + { { 77, 221, 120, 0 }, + { 211, 157, 69, 1 }, + { 15, 93, 217, 0 }, + { 209, 92, 229, 1 }, }, + { { 77, 221, 123, 1 }, + { 219, 223, 69, 1 }, + { 239, 93, 217, 0 }, + { 209, 125, 237, 1 }, }, + { { 77, 221, 116, 1 }, + { 219, 157, 85, 0 }, + { 151, 93, 217, 0 }, + { 85, 92, 237, 1 }, }, + { { 77, 221, 119, 0 }, + { 211, 223, 85, 0 }, + { 119, 93, 217, 0 }, + { 85, 125, 229, 1 }, }, + { { 77, 220, 152, 0 }, + { 211, 25, 100, 1 }, + { 12, 157, 217, 0 }, + { 147, 76, 101, 1 }, }, + { { 77, 220, 155, 1 }, + { 219, 91, 100, 1 }, + { 236, 157, 217, 0 }, + { 147, 109, 109, 1 }, }, + { { 77, 220, 148, 1 }, + { 219, 25, 116, 0 }, + { 148, 157, 217, 0 }, + { 23, 76, 109, 1 }, }, + { { 77, 220, 151, 0 }, + { 211, 91, 116, 0 }, + { 116, 157, 217, 0 }, + { 23, 109, 101, 1 }, }, + { { 77, 220, 232, 0 }, + { 195, 157, 100, 1 }, + { 11, 157, 217, 0 }, + { 147, 92, 225, 1 }, }, + { { 77, 220, 235, 1 }, + { 203, 223, 100, 1 }, + { 235, 157, 217, 0 }, + { 147, 125, 233, 1 }, }, + { { 77, 220, 228, 1 }, + { 203, 157, 116, 0 }, + { 147, 157, 217, 0 }, + { 23, 92, 233, 1 }, }, + { { 77, 220, 231, 0 }, + { 195, 223, 116, 0 }, + { 115, 157, 217, 0 }, + { 23, 125, 225, 1 }, }, + { { 74, 97, 8, 0 }, + { 192, 1, 131, 1 }, + { 8, 67, 41, 0 }, + { 224, 192, 1, 1 }, }, + { { 74, 97, 11, 1 }, + { 200, 67, 131, 1 }, + { 232, 67, 41, 0 }, + { 224, 225, 9, 1 }, }, + { { 74, 97, 4, 1 }, + { 200, 1, 147, 0 }, + { 144, 67, 41, 0 }, + { 100, 192, 9, 1 }, }, + { { 74, 97, 7, 0 }, + { 192, 67, 147, 0 }, + { 112, 67, 41, 0 }, + { 100, 225, 1, 1 }, }, + { { 74, 97, 120, 0 }, + { 208, 133, 131, 1 }, + { 15, 67, 41, 0 }, + { 224, 208, 133, 1 }, }, + { { 74, 97, 123, 1 }, + { 216, 199, 131, 1 }, + { 239, 67, 41, 0 }, + { 224, 241, 141, 1 }, }, + { { 74, 97, 116, 1 }, + { 216, 133, 147, 0 }, + { 151, 67, 41, 0 }, + { 100, 208, 141, 1 }, }, + { { 74, 97, 119, 0 }, + { 208, 199, 147, 0 }, + { 119, 67, 41, 0 }, + { 100, 241, 133, 1 }, }, + { { 74, 96, 152, 0 }, + { 208, 1, 162, 1 }, + { 12, 131, 41, 0 }, + { 162, 192, 5, 1 }, }, + { { 74, 96, 155, 1 }, + { 216, 67, 162, 1 }, + { 236, 131, 41, 0 }, + { 162, 225, 13, 1 }, }, + { { 74, 96, 148, 1 }, + { 216, 1, 178, 0 }, + { 148, 131, 41, 0 }, + { 38, 192, 13, 1 }, }, + { { 74, 96, 151, 0 }, + { 208, 67, 178, 0 }, + { 116, 131, 41, 0 }, + { 38, 225, 5, 1 }, }, + { { 74, 96, 232, 0 }, + { 192, 133, 162, 1 }, + { 11, 131, 41, 0 }, + { 162, 208, 129, 1 }, }, + { { 74, 96, 235, 1 }, + { 200, 199, 162, 1 }, + { 235, 131, 41, 0 }, + { 162, 241, 137, 1 }, }, + { { 74, 96, 228, 1 }, + { 200, 133, 178, 0 }, + { 147, 131, 41, 0 }, + { 38, 208, 137, 1 }, }, + { { 74, 96, 231, 0 }, + { 192, 199, 178, 0 }, + { 115, 131, 41, 0 }, + { 38, 241, 129, 1 }, }, + { { 74, 111, 8, 0 }, + { 225, 9, 131, 1 }, + { 8, 123, 41, 0 }, + { 224, 200, 67, 1 }, }, + { { 74, 111, 11, 1 }, + { 233, 75, 131, 1 }, + { 232, 123, 41, 0 }, + { 224, 233, 75, 1 }, }, + { { 74, 111, 4, 1 }, + { 233, 9, 147, 0 }, + { 144, 123, 41, 0 }, + { 100, 200, 75, 1 }, }, + { { 74, 111, 7, 0 }, + { 225, 75, 147, 0 }, + { 112, 123, 41, 0 }, + { 100, 233, 67, 1 }, }, + { { 74, 111, 120, 0 }, + { 241, 141, 131, 1 }, + { 15, 123, 41, 0 }, + { 224, 216, 199, 1 }, }, + { { 74, 111, 123, 1 }, + { 249, 207, 131, 1 }, + { 239, 123, 41, 0 }, + { 224, 249, 207, 1 }, }, + { { 74, 111, 116, 1 }, + { 249, 141, 147, 0 }, + { 151, 123, 41, 0 }, + { 100, 216, 207, 1 }, }, + { { 74, 111, 119, 0 }, + { 241, 207, 147, 0 }, + { 119, 123, 41, 0 }, + { 100, 249, 199, 1 }, }, + { { 74, 110, 152, 0 }, + { 241, 9, 162, 1 }, + { 12, 187, 41, 0 }, + { 162, 200, 71, 1 }, }, + { { 74, 110, 155, 1 }, + { 249, 75, 162, 1 }, + { 236, 187, 41, 0 }, + { 162, 233, 79, 1 }, }, + { { 74, 110, 148, 1 }, + { 249, 9, 178, 0 }, + { 148, 187, 41, 0 }, + { 38, 200, 79, 1 }, }, + { { 74, 110, 151, 0 }, + { 241, 75, 178, 0 }, + { 116, 187, 41, 0 }, + { 38, 233, 71, 1 }, }, + { { 74, 110, 232, 0 }, + { 225, 141, 162, 1 }, + { 11, 187, 41, 0 }, + { 162, 216, 195, 1 }, }, + { { 74, 110, 235, 1 }, + { 233, 207, 162, 1 }, + { 235, 187, 41, 0 }, + { 162, 249, 203, 1 }, }, + { { 74, 110, 228, 1 }, + { 233, 141, 178, 0 }, + { 147, 187, 41, 0 }, + { 38, 216, 203, 1 }, }, + { { 74, 110, 231, 0 }, + { 225, 207, 178, 0 }, + { 115, 187, 41, 0 }, + { 38, 249, 195, 1 }, }, + { { 74, 83, 8, 0 }, + { 224, 1, 193, 1 }, + { 8, 101, 41, 0 }, + { 193, 192, 3, 1 }, }, + { { 74, 83, 11, 1 }, + { 232, 67, 193, 1 }, + { 232, 101, 41, 0 }, + { 193, 225, 11, 1 }, }, + { { 74, 83, 4, 1 }, + { 232, 1, 209, 0 }, + { 144, 101, 41, 0 }, + { 69, 192, 11, 1 }, }, + { { 74, 83, 7, 0 }, + { 224, 67, 209, 0 }, + { 112, 101, 41, 0 }, + { 69, 225, 3, 1 }, }, + { { 74, 83, 120, 0 }, + { 240, 133, 193, 1 }, + { 15, 101, 41, 0 }, + { 193, 208, 135, 1 }, }, + { { 74, 83, 123, 1 }, + { 248, 199, 193, 1 }, + { 239, 101, 41, 0 }, + { 193, 241, 143, 1 }, }, + { { 74, 83, 116, 1 }, + { 248, 133, 209, 0 }, + { 151, 101, 41, 0 }, + { 69, 208, 143, 1 }, }, + { { 74, 83, 119, 0 }, + { 240, 199, 209, 0 }, + { 119, 101, 41, 0 }, + { 69, 241, 135, 1 }, }, + { { 74, 82, 152, 0 }, + { 240, 1, 224, 1 }, + { 12, 165, 41, 0 }, + { 131, 192, 7, 1 }, }, + { { 74, 82, 155, 1 }, + { 248, 67, 224, 1 }, + { 236, 165, 41, 0 }, + { 131, 225, 15, 1 }, }, + { { 74, 82, 148, 1 }, + { 248, 1, 240, 0 }, + { 148, 165, 41, 0 }, + { 7, 192, 15, 1 }, }, + { { 74, 82, 151, 0 }, + { 240, 67, 240, 0 }, + { 116, 165, 41, 0 }, + { 7, 225, 7, 1 }, }, + { { 74, 82, 232, 0 }, + { 224, 133, 224, 1 }, + { 11, 165, 41, 0 }, + { 131, 208, 131, 1 }, }, + { { 74, 82, 235, 1 }, + { 232, 199, 224, 1 }, + { 235, 165, 41, 0 }, + { 131, 241, 139, 1 }, }, + { { 74, 82, 228, 1 }, + { 232, 133, 240, 0 }, + { 147, 165, 41, 0 }, + { 7, 208, 139, 1 }, }, + { { 74, 82, 231, 0 }, + { 224, 199, 240, 0 }, + { 115, 165, 41, 0 }, + { 7, 241, 131, 1 }, }, + { { 74, 93, 8, 0 }, + { 193, 9, 193, 1 }, + { 8, 93, 41, 0 }, + { 193, 200, 65, 1 }, }, + { { 74, 93, 11, 1 }, + { 201, 75, 193, 1 }, + { 232, 93, 41, 0 }, + { 193, 233, 73, 1 }, }, + { { 74, 93, 4, 1 }, + { 201, 9, 209, 0 }, + { 144, 93, 41, 0 }, + { 69, 200, 73, 1 }, }, + { { 74, 93, 7, 0 }, + { 193, 75, 209, 0 }, + { 112, 93, 41, 0 }, + { 69, 233, 65, 1 }, }, + { { 74, 93, 120, 0 }, + { 209, 141, 193, 1 }, + { 15, 93, 41, 0 }, + { 193, 216, 197, 1 }, }, + { { 74, 93, 123, 1 }, + { 217, 207, 193, 1 }, + { 239, 93, 41, 0 }, + { 193, 249, 205, 1 }, }, + { { 74, 93, 116, 1 }, + { 217, 141, 209, 0 }, + { 151, 93, 41, 0 }, + { 69, 216, 205, 1 }, }, + { { 74, 93, 119, 0 }, + { 209, 207, 209, 0 }, + { 119, 93, 41, 0 }, + { 69, 249, 197, 1 }, }, + { { 74, 92, 152, 0 }, + { 209, 9, 224, 1 }, + { 12, 157, 41, 0 }, + { 131, 200, 69, 1 }, }, + { { 74, 92, 155, 1 }, + { 217, 75, 224, 1 }, + { 236, 157, 41, 0 }, + { 131, 233, 77, 1 }, }, + { { 74, 92, 148, 1 }, + { 217, 9, 240, 0 }, + { 148, 157, 41, 0 }, + { 7, 200, 77, 1 }, }, + { { 74, 92, 151, 0 }, + { 209, 75, 240, 0 }, + { 116, 157, 41, 0 }, + { 7, 233, 69, 1 }, }, + { { 74, 92, 232, 0 }, + { 193, 141, 224, 1 }, + { 11, 157, 41, 0 }, + { 131, 216, 193, 1 }, }, + { { 74, 92, 235, 1 }, + { 201, 207, 224, 1 }, + { 235, 157, 41, 0 }, + { 131, 249, 201, 1 }, }, + { { 74, 92, 228, 1 }, + { 201, 141, 240, 0 }, + { 147, 157, 41, 0 }, + { 7, 216, 201, 1 }, }, + { { 74, 92, 231, 0 }, + { 193, 207, 240, 0 }, + { 115, 157, 41, 0 }, + { 7, 249, 193, 1 }, }, + { { 75, 161, 8, 0 }, + { 130, 17, 131, 1 }, + { 8, 66, 233, 0 }, + { 224, 196, 32, 1 }, }, + { { 75, 161, 11, 1 }, + { 138, 83, 131, 1 }, + { 232, 66, 233, 0 }, + { 224, 229, 40, 1 }, }, + { { 75, 161, 4, 1 }, + { 138, 17, 147, 0 }, + { 144, 66, 233, 0 }, + { 100, 196, 40, 1 }, }, + { { 75, 161, 7, 0 }, + { 130, 83, 147, 0 }, + { 112, 66, 233, 0 }, + { 100, 229, 32, 1 }, }, + { { 75, 161, 120, 0 }, + { 146, 149, 131, 1 }, + { 15, 66, 233, 0 }, + { 224, 212, 164, 1 }, }, + { { 75, 161, 123, 1 }, + { 154, 215, 131, 1 }, + { 239, 66, 233, 0 }, + { 224, 245, 172, 1 }, }, + { { 75, 161, 116, 1 }, + { 154, 149, 147, 0 }, + { 151, 66, 233, 0 }, + { 100, 212, 172, 1 }, }, + { { 75, 161, 119, 0 }, + { 146, 215, 147, 0 }, + { 119, 66, 233, 0 }, + { 100, 245, 164, 1 }, }, + { { 75, 160, 152, 0 }, + { 146, 17, 162, 1 }, + { 12, 130, 233, 0 }, + { 162, 196, 36, 1 }, }, + { { 75, 160, 155, 1 }, + { 154, 83, 162, 1 }, + { 236, 130, 233, 0 }, + { 162, 229, 44, 1 }, }, + { { 75, 160, 148, 1 }, + { 154, 17, 178, 0 }, + { 148, 130, 233, 0 }, + { 38, 196, 44, 1 }, }, + { { 75, 160, 151, 0 }, + { 146, 83, 178, 0 }, + { 116, 130, 233, 0 }, + { 38, 229, 36, 1 }, }, + { { 75, 160, 232, 0 }, + { 130, 149, 162, 1 }, + { 11, 130, 233, 0 }, + { 162, 212, 160, 1 }, }, + { { 75, 160, 235, 1 }, + { 138, 215, 162, 1 }, + { 235, 130, 233, 0 }, + { 162, 245, 168, 1 }, }, + { { 75, 160, 228, 1 }, + { 138, 149, 178, 0 }, + { 147, 130, 233, 0 }, + { 38, 212, 168, 1 }, }, + { { 75, 160, 231, 0 }, + { 130, 215, 178, 0 }, + { 115, 130, 233, 0 }, + { 38, 245, 160, 1 }, }, + { { 75, 175, 8, 0 }, + { 163, 25, 131, 1 }, + { 8, 122, 233, 0 }, + { 224, 204, 98, 1 }, }, + { { 75, 175, 11, 1 }, + { 171, 91, 131, 1 }, + { 232, 122, 233, 0 }, + { 224, 237, 106, 1 }, }, + { { 75, 175, 4, 1 }, + { 171, 25, 147, 0 }, + { 144, 122, 233, 0 }, + { 100, 204, 106, 1 }, }, + { { 75, 175, 7, 0 }, + { 163, 91, 147, 0 }, + { 112, 122, 233, 0 }, + { 100, 237, 98, 1 }, }, + { { 75, 175, 120, 0 }, + { 179, 157, 131, 1 }, + { 15, 122, 233, 0 }, + { 224, 220, 230, 1 }, }, + { { 75, 175, 123, 1 }, + { 187, 223, 131, 1 }, + { 239, 122, 233, 0 }, + { 224, 253, 238, 1 }, }, + { { 75, 175, 116, 1 }, + { 187, 157, 147, 0 }, + { 151, 122, 233, 0 }, + { 100, 220, 238, 1 }, }, + { { 75, 175, 119, 0 }, + { 179, 223, 147, 0 }, + { 119, 122, 233, 0 }, + { 100, 253, 230, 1 }, }, + { { 75, 174, 152, 0 }, + { 179, 25, 162, 1 }, + { 12, 186, 233, 0 }, + { 162, 204, 102, 1 }, }, + { { 75, 174, 155, 1 }, + { 187, 91, 162, 1 }, + { 236, 186, 233, 0 }, + { 162, 237, 110, 1 }, }, + { { 75, 174, 148, 1 }, + { 187, 25, 178, 0 }, + { 148, 186, 233, 0 }, + { 38, 204, 110, 1 }, }, + { { 75, 174, 151, 0 }, + { 179, 91, 178, 0 }, + { 116, 186, 233, 0 }, + { 38, 237, 102, 1 }, }, + { { 75, 174, 232, 0 }, + { 163, 157, 162, 1 }, + { 11, 186, 233, 0 }, + { 162, 220, 226, 1 }, }, + { { 75, 174, 235, 1 }, + { 171, 223, 162, 1 }, + { 235, 186, 233, 0 }, + { 162, 253, 234, 1 }, }, + { { 75, 174, 228, 1 }, + { 171, 157, 178, 0 }, + { 147, 186, 233, 0 }, + { 38, 220, 234, 1 }, }, + { { 75, 174, 231, 0 }, + { 163, 223, 178, 0 }, + { 115, 186, 233, 0 }, + { 38, 253, 226, 1 }, }, + { { 75, 147, 8, 0 }, + { 162, 17, 193, 1 }, + { 8, 100, 233, 0 }, + { 193, 196, 34, 1 }, }, + { { 75, 147, 11, 1 }, + { 170, 83, 193, 1 }, + { 232, 100, 233, 0 }, + { 193, 229, 42, 1 }, }, + { { 75, 147, 4, 1 }, + { 170, 17, 209, 0 }, + { 144, 100, 233, 0 }, + { 69, 196, 42, 1 }, }, + { { 75, 147, 7, 0 }, + { 162, 83, 209, 0 }, + { 112, 100, 233, 0 }, + { 69, 229, 34, 1 }, }, + { { 75, 147, 120, 0 }, + { 178, 149, 193, 1 }, + { 15, 100, 233, 0 }, + { 193, 212, 166, 1 }, }, + { { 75, 147, 123, 1 }, + { 186, 215, 193, 1 }, + { 239, 100, 233, 0 }, + { 193, 245, 174, 1 }, }, + { { 75, 147, 116, 1 }, + { 186, 149, 209, 0 }, + { 151, 100, 233, 0 }, + { 69, 212, 174, 1 }, }, + { { 75, 147, 119, 0 }, + { 178, 215, 209, 0 }, + { 119, 100, 233, 0 }, + { 69, 245, 166, 1 }, }, + { { 75, 146, 152, 0 }, + { 178, 17, 224, 1 }, + { 12, 164, 233, 0 }, + { 131, 196, 38, 1 }, }, + { { 75, 146, 155, 1 }, + { 186, 83, 224, 1 }, + { 236, 164, 233, 0 }, + { 131, 229, 46, 1 }, }, + { { 75, 146, 148, 1 }, + { 186, 17, 240, 0 }, + { 148, 164, 233, 0 }, + { 7, 196, 46, 1 }, }, + { { 75, 146, 151, 0 }, + { 178, 83, 240, 0 }, + { 116, 164, 233, 0 }, + { 7, 229, 38, 1 }, }, + { { 75, 146, 232, 0 }, + { 162, 149, 224, 1 }, + { 11, 164, 233, 0 }, + { 131, 212, 162, 1 }, }, + { { 75, 146, 235, 1 }, + { 170, 215, 224, 1 }, + { 235, 164, 233, 0 }, + { 131, 245, 170, 1 }, }, + { { 75, 146, 228, 1 }, + { 170, 149, 240, 0 }, + { 147, 164, 233, 0 }, + { 7, 212, 170, 1 }, }, + { { 75, 146, 231, 0 }, + { 162, 215, 240, 0 }, + { 115, 164, 233, 0 }, + { 7, 245, 162, 1 }, }, + { { 75, 157, 8, 0 }, + { 131, 25, 193, 1 }, + { 8, 92, 233, 0 }, + { 193, 204, 96, 1 }, }, + { { 75, 157, 11, 1 }, + { 139, 91, 193, 1 }, + { 232, 92, 233, 0 }, + { 193, 237, 104, 1 }, }, + { { 75, 157, 4, 1 }, + { 139, 25, 209, 0 }, + { 144, 92, 233, 0 }, + { 69, 204, 104, 1 }, }, + { { 75, 157, 7, 0 }, + { 131, 91, 209, 0 }, + { 112, 92, 233, 0 }, + { 69, 237, 96, 1 }, }, + { { 75, 157, 120, 0 }, + { 147, 157, 193, 1 }, + { 15, 92, 233, 0 }, + { 193, 220, 228, 1 }, }, + { { 75, 157, 123, 1 }, + { 155, 223, 193, 1 }, + { 239, 92, 233, 0 }, + { 193, 253, 236, 1 }, }, + { { 75, 157, 116, 1 }, + { 155, 157, 209, 0 }, + { 151, 92, 233, 0 }, + { 69, 220, 236, 1 }, }, + { { 75, 157, 119, 0 }, + { 147, 223, 209, 0 }, + { 119, 92, 233, 0 }, + { 69, 253, 228, 1 }, }, + { { 75, 156, 152, 0 }, + { 147, 25, 224, 1 }, + { 12, 156, 233, 0 }, + { 131, 204, 100, 1 }, }, + { { 75, 156, 155, 1 }, + { 155, 91, 224, 1 }, + { 236, 156, 233, 0 }, + { 131, 237, 108, 1 }, }, + { { 75, 156, 148, 1 }, + { 155, 25, 240, 0 }, + { 148, 156, 233, 0 }, + { 7, 204, 108, 1 }, }, + { { 75, 156, 151, 0 }, + { 147, 91, 240, 0 }, + { 116, 156, 233, 0 }, + { 7, 237, 100, 1 }, }, + { { 75, 156, 232, 0 }, + { 131, 157, 224, 1 }, + { 11, 156, 233, 0 }, + { 131, 220, 224, 1 }, }, + { { 75, 156, 235, 1 }, + { 139, 223, 224, 1 }, + { 235, 156, 233, 0 }, + { 131, 253, 232, 1 }, }, + { { 75, 156, 228, 1 }, + { 139, 157, 240, 0 }, + { 147, 156, 233, 0 }, + { 7, 220, 232, 1 }, }, + { { 75, 156, 231, 0 }, + { 131, 223, 240, 0 }, + { 115, 156, 233, 0 }, + { 7, 253, 224, 1 }, }, + { { 116, 33, 8, 0 }, + { 4, 33, 7, 1 }, + { 8, 66, 23, 0 }, + { 240, 66, 16, 0 }, }, + { { 116, 33, 11, 1 }, + { 12, 99, 7, 1 }, + { 232, 66, 23, 0 }, + { 240, 99, 24, 0 }, }, + { { 116, 33, 4, 1 }, + { 12, 33, 23, 0 }, + { 144, 66, 23, 0 }, + { 116, 66, 24, 0 }, }, + { { 116, 33, 7, 0 }, + { 4, 99, 23, 0 }, + { 112, 66, 23, 0 }, + { 116, 99, 16, 0 }, }, + { { 116, 33, 120, 0 }, + { 20, 165, 7, 1 }, + { 15, 66, 23, 0 }, + { 240, 82, 148, 0 }, }, + { { 116, 33, 123, 1 }, + { 28, 231, 7, 1 }, + { 239, 66, 23, 0 }, + { 240, 115, 156, 0 }, }, + { { 116, 33, 116, 1 }, + { 28, 165, 23, 0 }, + { 151, 66, 23, 0 }, + { 116, 82, 156, 0 }, }, + { { 116, 33, 119, 0 }, + { 20, 231, 23, 0 }, + { 119, 66, 23, 0 }, + { 116, 115, 148, 0 }, }, + { { 116, 32, 152, 0 }, + { 20, 33, 38, 1 }, + { 12, 130, 23, 0 }, + { 178, 66, 20, 0 }, }, + { { 116, 32, 155, 1 }, + { 28, 99, 38, 1 }, + { 236, 130, 23, 0 }, + { 178, 99, 28, 0 }, }, + { { 116, 32, 148, 1 }, + { 28, 33, 54, 0 }, + { 148, 130, 23, 0 }, + { 54, 66, 28, 0 }, }, + { { 116, 32, 151, 0 }, + { 20, 99, 54, 0 }, + { 116, 130, 23, 0 }, + { 54, 99, 20, 0 }, }, + { { 116, 32, 232, 0 }, + { 4, 165, 38, 1 }, + { 11, 130, 23, 0 }, + { 178, 82, 144, 0 }, }, + { { 116, 32, 235, 1 }, + { 12, 231, 38, 1 }, + { 235, 130, 23, 0 }, + { 178, 115, 152, 0 }, }, + { { 116, 32, 228, 1 }, + { 12, 165, 54, 0 }, + { 147, 130, 23, 0 }, + { 54, 82, 152, 0 }, }, + { { 116, 32, 231, 0 }, + { 4, 231, 54, 0 }, + { 115, 130, 23, 0 }, + { 54, 115, 144, 0 }, }, + { { 116, 47, 8, 0 }, + { 37, 41, 7, 1 }, + { 8, 122, 23, 0 }, + { 240, 74, 82, 0 }, }, + { { 116, 47, 11, 1 }, + { 45, 107, 7, 1 }, + { 232, 122, 23, 0 }, + { 240, 107, 90, 0 }, }, + { { 116, 47, 4, 1 }, + { 45, 41, 23, 0 }, + { 144, 122, 23, 0 }, + { 116, 74, 90, 0 }, }, + { { 116, 47, 7, 0 }, + { 37, 107, 23, 0 }, + { 112, 122, 23, 0 }, + { 116, 107, 82, 0 }, }, + { { 116, 47, 120, 0 }, + { 53, 173, 7, 1 }, + { 15, 122, 23, 0 }, + { 240, 90, 214, 0 }, }, + { { 116, 47, 123, 1 }, + { 61, 239, 7, 1 }, + { 239, 122, 23, 0 }, + { 240, 123, 222, 0 }, }, + { { 116, 47, 116, 1 }, + { 61, 173, 23, 0 }, + { 151, 122, 23, 0 }, + { 116, 90, 222, 0 }, }, + { { 116, 47, 119, 0 }, + { 53, 239, 23, 0 }, + { 119, 122, 23, 0 }, + { 116, 123, 214, 0 }, }, + { { 116, 46, 152, 0 }, + { 53, 41, 38, 1 }, + { 12, 186, 23, 0 }, + { 178, 74, 86, 0 }, }, + { { 116, 46, 155, 1 }, + { 61, 107, 38, 1 }, + { 236, 186, 23, 0 }, + { 178, 107, 94, 0 }, }, + { { 116, 46, 148, 1 }, + { 61, 41, 54, 0 }, + { 148, 186, 23, 0 }, + { 54, 74, 94, 0 }, }, + { { 116, 46, 151, 0 }, + { 53, 107, 54, 0 }, + { 116, 186, 23, 0 }, + { 54, 107, 86, 0 }, }, + { { 116, 46, 232, 0 }, + { 37, 173, 38, 1 }, + { 11, 186, 23, 0 }, + { 178, 90, 210, 0 }, }, + { { 116, 46, 235, 1 }, + { 45, 239, 38, 1 }, + { 235, 186, 23, 0 }, + { 178, 123, 218, 0 }, }, + { { 116, 46, 228, 1 }, + { 45, 173, 54, 0 }, + { 147, 186, 23, 0 }, + { 54, 90, 218, 0 }, }, + { { 116, 46, 231, 0 }, + { 37, 239, 54, 0 }, + { 115, 186, 23, 0 }, + { 54, 123, 210, 0 }, }, + { { 116, 19, 8, 0 }, + { 36, 33, 69, 1 }, + { 8, 100, 23, 0 }, + { 209, 66, 18, 0 }, }, + { { 116, 19, 11, 1 }, + { 44, 99, 69, 1 }, + { 232, 100, 23, 0 }, + { 209, 99, 26, 0 }, }, + { { 116, 19, 4, 1 }, + { 44, 33, 85, 0 }, + { 144, 100, 23, 0 }, + { 85, 66, 26, 0 }, }, + { { 116, 19, 7, 0 }, + { 36, 99, 85, 0 }, + { 112, 100, 23, 0 }, + { 85, 99, 18, 0 }, }, + { { 116, 19, 120, 0 }, + { 52, 165, 69, 1 }, + { 15, 100, 23, 0 }, + { 209, 82, 150, 0 }, }, + { { 116, 19, 123, 1 }, + { 60, 231, 69, 1 }, + { 239, 100, 23, 0 }, + { 209, 115, 158, 0 }, }, + { { 116, 19, 116, 1 }, + { 60, 165, 85, 0 }, + { 151, 100, 23, 0 }, + { 85, 82, 158, 0 }, }, + { { 116, 19, 119, 0 }, + { 52, 231, 85, 0 }, + { 119, 100, 23, 0 }, + { 85, 115, 150, 0 }, }, + { { 116, 18, 152, 0 }, + { 52, 33, 100, 1 }, + { 12, 164, 23, 0 }, + { 147, 66, 22, 0 }, }, + { { 116, 18, 155, 1 }, + { 60, 99, 100, 1 }, + { 236, 164, 23, 0 }, + { 147, 99, 30, 0 }, }, + { { 116, 18, 148, 1 }, + { 60, 33, 116, 0 }, + { 148, 164, 23, 0 }, + { 23, 66, 30, 0 }, }, + { { 116, 18, 151, 0 }, + { 52, 99, 116, 0 }, + { 116, 164, 23, 0 }, + { 23, 99, 22, 0 }, }, + { { 116, 18, 232, 0 }, + { 36, 165, 100, 1 }, + { 11, 164, 23, 0 }, + { 147, 82, 146, 0 }, }, + { { 116, 18, 235, 1 }, + { 44, 231, 100, 1 }, + { 235, 164, 23, 0 }, + { 147, 115, 154, 0 }, }, + { { 116, 18, 228, 1 }, + { 44, 165, 116, 0 }, + { 147, 164, 23, 0 }, + { 23, 82, 154, 0 }, }, + { { 116, 18, 231, 0 }, + { 36, 231, 116, 0 }, + { 115, 164, 23, 0 }, + { 23, 115, 146, 0 }, }, + { { 116, 29, 8, 0 }, + { 5, 41, 69, 1 }, + { 8, 92, 23, 0 }, + { 209, 74, 80, 0 }, }, + { { 116, 29, 11, 1 }, + { 13, 107, 69, 1 }, + { 232, 92, 23, 0 }, + { 209, 107, 88, 0 }, }, + { { 116, 29, 4, 1 }, + { 13, 41, 85, 0 }, + { 144, 92, 23, 0 }, + { 85, 74, 88, 0 }, }, + { { 116, 29, 7, 0 }, + { 5, 107, 85, 0 }, + { 112, 92, 23, 0 }, + { 85, 107, 80, 0 }, }, + { { 116, 29, 120, 0 }, + { 21, 173, 69, 1 }, + { 15, 92, 23, 0 }, + { 209, 90, 212, 0 }, }, + { { 116, 29, 123, 1 }, + { 29, 239, 69, 1 }, + { 239, 92, 23, 0 }, + { 209, 123, 220, 0 }, }, + { { 116, 29, 116, 1 }, + { 29, 173, 85, 0 }, + { 151, 92, 23, 0 }, + { 85, 90, 220, 0 }, }, + { { 116, 29, 119, 0 }, + { 21, 239, 85, 0 }, + { 119, 92, 23, 0 }, + { 85, 123, 212, 0 }, }, + { { 116, 28, 152, 0 }, + { 21, 41, 100, 1 }, + { 12, 156, 23, 0 }, + { 147, 74, 84, 0 }, }, + { { 116, 28, 155, 1 }, + { 29, 107, 100, 1 }, + { 236, 156, 23, 0 }, + { 147, 107, 92, 0 }, }, + { { 116, 28, 148, 1 }, + { 29, 41, 116, 0 }, + { 148, 156, 23, 0 }, + { 23, 74, 92, 0 }, }, + { { 116, 28, 151, 0 }, + { 21, 107, 116, 0 }, + { 116, 156, 23, 0 }, + { 23, 107, 84, 0 }, }, + { { 116, 28, 232, 0 }, + { 5, 173, 100, 1 }, + { 11, 156, 23, 0 }, + { 147, 90, 208, 0 }, }, + { { 116, 28, 235, 1 }, + { 13, 239, 100, 1 }, + { 235, 156, 23, 0 }, + { 147, 123, 216, 0 }, }, + { { 116, 28, 228, 1 }, + { 13, 173, 116, 0 }, + { 147, 156, 23, 0 }, + { 23, 90, 216, 0 }, }, + { { 116, 28, 231, 0 }, + { 5, 239, 116, 0 }, + { 115, 156, 23, 0 }, + { 23, 123, 208, 0 }, }, + { { 117, 225, 8, 0 }, + { 70, 49, 7, 1 }, + { 8, 67, 215, 0 }, + { 240, 70, 49, 0 }, }, + { { 117, 225, 11, 1 }, + { 78, 115, 7, 1 }, + { 232, 67, 215, 0 }, + { 240, 103, 57, 0 }, }, + { { 117, 225, 4, 1 }, + { 78, 49, 23, 0 }, + { 144, 67, 215, 0 }, + { 116, 70, 57, 0 }, }, + { { 117, 225, 7, 0 }, + { 70, 115, 23, 0 }, + { 112, 67, 215, 0 }, + { 116, 103, 49, 0 }, }, + { { 117, 225, 120, 0 }, + { 86, 181, 7, 1 }, + { 15, 67, 215, 0 }, + { 240, 86, 181, 0 }, }, + { { 117, 225, 123, 1 }, + { 94, 247, 7, 1 }, + { 239, 67, 215, 0 }, + { 240, 119, 189, 0 }, }, + { { 117, 225, 116, 1 }, + { 94, 181, 23, 0 }, + { 151, 67, 215, 0 }, + { 116, 86, 189, 0 }, }, + { { 117, 225, 119, 0 }, + { 86, 247, 23, 0 }, + { 119, 67, 215, 0 }, + { 116, 119, 181, 0 }, }, + { { 117, 224, 152, 0 }, + { 86, 49, 38, 1 }, + { 12, 131, 215, 0 }, + { 178, 70, 53, 0 }, }, + { { 117, 224, 155, 1 }, + { 94, 115, 38, 1 }, + { 236, 131, 215, 0 }, + { 178, 103, 61, 0 }, }, + { { 117, 224, 148, 1 }, + { 94, 49, 54, 0 }, + { 148, 131, 215, 0 }, + { 54, 70, 61, 0 }, }, + { { 117, 224, 151, 0 }, + { 86, 115, 54, 0 }, + { 116, 131, 215, 0 }, + { 54, 103, 53, 0 }, }, + { { 117, 224, 232, 0 }, + { 70, 181, 38, 1 }, + { 11, 131, 215, 0 }, + { 178, 86, 177, 0 }, }, + { { 117, 224, 235, 1 }, + { 78, 247, 38, 1 }, + { 235, 131, 215, 0 }, + { 178, 119, 185, 0 }, }, + { { 117, 224, 228, 1 }, + { 78, 181, 54, 0 }, + { 147, 131, 215, 0 }, + { 54, 86, 185, 0 }, }, + { { 117, 224, 231, 0 }, + { 70, 247, 54, 0 }, + { 115, 131, 215, 0 }, + { 54, 119, 177, 0 }, }, + { { 117, 239, 8, 0 }, + { 103, 57, 7, 1 }, + { 8, 123, 215, 0 }, + { 240, 78, 115, 0 }, }, + { { 117, 239, 11, 1 }, + { 111, 123, 7, 1 }, + { 232, 123, 215, 0 }, + { 240, 111, 123, 0 }, }, + { { 117, 239, 4, 1 }, + { 111, 57, 23, 0 }, + { 144, 123, 215, 0 }, + { 116, 78, 123, 0 }, }, + { { 117, 239, 7, 0 }, + { 103, 123, 23, 0 }, + { 112, 123, 215, 0 }, + { 116, 111, 115, 0 }, }, + { { 117, 239, 120, 0 }, + { 119, 189, 7, 1 }, + { 15, 123, 215, 0 }, + { 240, 94, 247, 0 }, }, + { { 117, 239, 123, 1 }, + { 127, 255, 7, 1 }, + { 239, 123, 215, 0 }, + { 240, 127, 255, 0 }, }, + { { 117, 239, 116, 1 }, + { 127, 189, 23, 0 }, + { 151, 123, 215, 0 }, + { 116, 94, 255, 0 }, }, + { { 117, 239, 119, 0 }, + { 119, 255, 23, 0 }, + { 119, 123, 215, 0 }, + { 116, 127, 247, 0 }, }, + { { 117, 238, 152, 0 }, + { 119, 57, 38, 1 }, + { 12, 187, 215, 0 }, + { 178, 78, 119, 0 }, }, + { { 117, 238, 155, 1 }, + { 127, 123, 38, 1 }, + { 236, 187, 215, 0 }, + { 178, 111, 127, 0 }, }, + { { 117, 238, 148, 1 }, + { 127, 57, 54, 0 }, + { 148, 187, 215, 0 }, + { 54, 78, 127, 0 }, }, + { { 117, 238, 151, 0 }, + { 119, 123, 54, 0 }, + { 116, 187, 215, 0 }, + { 54, 111, 119, 0 }, }, + { { 117, 238, 232, 0 }, + { 103, 189, 38, 1 }, + { 11, 187, 215, 0 }, + { 178, 94, 243, 0 }, }, + { { 117, 238, 235, 1 }, + { 111, 255, 38, 1 }, + { 235, 187, 215, 0 }, + { 178, 127, 251, 0 }, }, + { { 117, 238, 228, 1 }, + { 111, 189, 54, 0 }, + { 147, 187, 215, 0 }, + { 54, 94, 251, 0 }, }, + { { 117, 238, 231, 0 }, + { 103, 255, 54, 0 }, + { 115, 187, 215, 0 }, + { 54, 127, 243, 0 }, }, + { { 117, 211, 8, 0 }, + { 102, 49, 69, 1 }, + { 8, 101, 215, 0 }, + { 209, 70, 51, 0 }, }, + { { 117, 211, 11, 1 }, + { 110, 115, 69, 1 }, + { 232, 101, 215, 0 }, + { 209, 103, 59, 0 }, }, + { { 117, 211, 4, 1 }, + { 110, 49, 85, 0 }, + { 144, 101, 215, 0 }, + { 85, 70, 59, 0 }, }, + { { 117, 211, 7, 0 }, + { 102, 115, 85, 0 }, + { 112, 101, 215, 0 }, + { 85, 103, 51, 0 }, }, + { { 117, 211, 120, 0 }, + { 118, 181, 69, 1 }, + { 15, 101, 215, 0 }, + { 209, 86, 183, 0 }, }, + { { 117, 211, 123, 1 }, + { 126, 247, 69, 1 }, + { 239, 101, 215, 0 }, + { 209, 119, 191, 0 }, }, + { { 117, 211, 116, 1 }, + { 126, 181, 85, 0 }, + { 151, 101, 215, 0 }, + { 85, 86, 191, 0 }, }, + { { 117, 211, 119, 0 }, + { 118, 247, 85, 0 }, + { 119, 101, 215, 0 }, + { 85, 119, 183, 0 }, }, + { { 117, 210, 152, 0 }, + { 118, 49, 100, 1 }, + { 12, 165, 215, 0 }, + { 147, 70, 55, 0 }, }, + { { 117, 210, 155, 1 }, + { 126, 115, 100, 1 }, + { 236, 165, 215, 0 }, + { 147, 103, 63, 0 }, }, + { { 117, 210, 148, 1 }, + { 126, 49, 116, 0 }, + { 148, 165, 215, 0 }, + { 23, 70, 63, 0 }, }, + { { 117, 210, 151, 0 }, + { 118, 115, 116, 0 }, + { 116, 165, 215, 0 }, + { 23, 103, 55, 0 }, }, + { { 117, 210, 232, 0 }, + { 102, 181, 100, 1 }, + { 11, 165, 215, 0 }, + { 147, 86, 179, 0 }, }, + { { 117, 210, 235, 1 }, + { 110, 247, 100, 1 }, + { 235, 165, 215, 0 }, + { 147, 119, 187, 0 }, }, + { { 117, 210, 228, 1 }, + { 110, 181, 116, 0 }, + { 147, 165, 215, 0 }, + { 23, 86, 187, 0 }, }, + { { 117, 210, 231, 0 }, + { 102, 247, 116, 0 }, + { 115, 165, 215, 0 }, + { 23, 119, 179, 0 }, }, + { { 117, 221, 8, 0 }, + { 71, 57, 69, 1 }, + { 8, 93, 215, 0 }, + { 209, 78, 113, 0 }, }, + { { 117, 221, 11, 1 }, + { 79, 123, 69, 1 }, + { 232, 93, 215, 0 }, + { 209, 111, 121, 0 }, }, + { { 117, 221, 4, 1 }, + { 79, 57, 85, 0 }, + { 144, 93, 215, 0 }, + { 85, 78, 121, 0 }, }, + { { 117, 221, 7, 0 }, + { 71, 123, 85, 0 }, + { 112, 93, 215, 0 }, + { 85, 111, 113, 0 }, }, + { { 117, 221, 120, 0 }, + { 87, 189, 69, 1 }, + { 15, 93, 215, 0 }, + { 209, 94, 245, 0 }, }, + { { 117, 221, 123, 1 }, + { 95, 255, 69, 1 }, + { 239, 93, 215, 0 }, + { 209, 127, 253, 0 }, }, + { { 117, 221, 116, 1 }, + { 95, 189, 85, 0 }, + { 151, 93, 215, 0 }, + { 85, 94, 253, 0 }, }, + { { 117, 221, 119, 0 }, + { 87, 255, 85, 0 }, + { 119, 93, 215, 0 }, + { 85, 127, 245, 0 }, }, + { { 117, 220, 152, 0 }, + { 87, 57, 100, 1 }, + { 12, 157, 215, 0 }, + { 147, 78, 117, 0 }, }, + { { 117, 220, 155, 1 }, + { 95, 123, 100, 1 }, + { 236, 157, 215, 0 }, + { 147, 111, 125, 0 }, }, + { { 117, 220, 148, 1 }, + { 95, 57, 116, 0 }, + { 148, 157, 215, 0 }, + { 23, 78, 125, 0 }, }, + { { 117, 220, 151, 0 }, + { 87, 123, 116, 0 }, + { 116, 157, 215, 0 }, + { 23, 111, 117, 0 }, }, + { { 117, 220, 232, 0 }, + { 71, 189, 100, 1 }, + { 11, 157, 215, 0 }, + { 147, 94, 241, 0 }, }, + { { 117, 220, 235, 1 }, + { 79, 255, 100, 1 }, + { 235, 157, 215, 0 }, + { 147, 127, 249, 0 }, }, + { { 117, 220, 228, 1 }, + { 79, 189, 116, 0 }, + { 147, 157, 215, 0 }, + { 23, 94, 249, 0 }, }, + { { 117, 220, 231, 0 }, + { 71, 255, 116, 0 }, + { 115, 157, 215, 0 }, + { 23, 127, 241, 0 }, }, + { { 114, 97, 8, 0 }, + { 68, 33, 131, 1 }, + { 8, 67, 39, 0 }, + { 224, 194, 17, 0 }, }, + { { 114, 97, 11, 1 }, + { 76, 99, 131, 1 }, + { 232, 67, 39, 0 }, + { 224, 227, 25, 0 }, }, + { { 114, 97, 4, 1 }, + { 76, 33, 147, 0 }, + { 144, 67, 39, 0 }, + { 100, 194, 25, 0 }, }, + { { 114, 97, 7, 0 }, + { 68, 99, 147, 0 }, + { 112, 67, 39, 0 }, + { 100, 227, 17, 0 }, }, + { { 114, 97, 120, 0 }, + { 84, 165, 131, 1 }, + { 15, 67, 39, 0 }, + { 224, 210, 149, 0 }, }, + { { 114, 97, 123, 1 }, + { 92, 231, 131, 1 }, + { 239, 67, 39, 0 }, + { 224, 243, 157, 0 }, }, + { { 114, 97, 116, 1 }, + { 92, 165, 147, 0 }, + { 151, 67, 39, 0 }, + { 100, 210, 157, 0 }, }, + { { 114, 97, 119, 0 }, + { 84, 231, 147, 0 }, + { 119, 67, 39, 0 }, + { 100, 243, 149, 0 }, }, + { { 114, 96, 152, 0 }, + { 84, 33, 162, 1 }, + { 12, 131, 39, 0 }, + { 162, 194, 21, 0 }, }, + { { 114, 96, 155, 1 }, + { 92, 99, 162, 1 }, + { 236, 131, 39, 0 }, + { 162, 227, 29, 0 }, }, + { { 114, 96, 148, 1 }, + { 92, 33, 178, 0 }, + { 148, 131, 39, 0 }, + { 38, 194, 29, 0 }, }, + { { 114, 96, 151, 0 }, + { 84, 99, 178, 0 }, + { 116, 131, 39, 0 }, + { 38, 227, 21, 0 }, }, + { { 114, 96, 232, 0 }, + { 68, 165, 162, 1 }, + { 11, 131, 39, 0 }, + { 162, 210, 145, 0 }, }, + { { 114, 96, 235, 1 }, + { 76, 231, 162, 1 }, + { 235, 131, 39, 0 }, + { 162, 243, 153, 0 }, }, + { { 114, 96, 228, 1 }, + { 76, 165, 178, 0 }, + { 147, 131, 39, 0 }, + { 38, 210, 153, 0 }, }, + { { 114, 96, 231, 0 }, + { 68, 231, 178, 0 }, + { 115, 131, 39, 0 }, + { 38, 243, 145, 0 }, }, + { { 114, 111, 8, 0 }, + { 101, 41, 131, 1 }, + { 8, 123, 39, 0 }, + { 224, 202, 83, 0 }, }, + { { 114, 111, 11, 1 }, + { 109, 107, 131, 1 }, + { 232, 123, 39, 0 }, + { 224, 235, 91, 0 }, }, + { { 114, 111, 4, 1 }, + { 109, 41, 147, 0 }, + { 144, 123, 39, 0 }, + { 100, 202, 91, 0 }, }, + { { 114, 111, 7, 0 }, + { 101, 107, 147, 0 }, + { 112, 123, 39, 0 }, + { 100, 235, 83, 0 }, }, + { { 114, 111, 120, 0 }, + { 117, 173, 131, 1 }, + { 15, 123, 39, 0 }, + { 224, 218, 215, 0 }, }, + { { 114, 111, 123, 1 }, + { 125, 239, 131, 1 }, + { 239, 123, 39, 0 }, + { 224, 251, 223, 0 }, }, + { { 114, 111, 116, 1 }, + { 125, 173, 147, 0 }, + { 151, 123, 39, 0 }, + { 100, 218, 223, 0 }, }, + { { 114, 111, 119, 0 }, + { 117, 239, 147, 0 }, + { 119, 123, 39, 0 }, + { 100, 251, 215, 0 }, }, + { { 114, 110, 152, 0 }, + { 117, 41, 162, 1 }, + { 12, 187, 39, 0 }, + { 162, 202, 87, 0 }, }, + { { 114, 110, 155, 1 }, + { 125, 107, 162, 1 }, + { 236, 187, 39, 0 }, + { 162, 235, 95, 0 }, }, + { { 114, 110, 148, 1 }, + { 125, 41, 178, 0 }, + { 148, 187, 39, 0 }, + { 38, 202, 95, 0 }, }, + { { 114, 110, 151, 0 }, + { 117, 107, 178, 0 }, + { 116, 187, 39, 0 }, + { 38, 235, 87, 0 }, }, + { { 114, 110, 232, 0 }, + { 101, 173, 162, 1 }, + { 11, 187, 39, 0 }, + { 162, 218, 211, 0 }, }, + { { 114, 110, 235, 1 }, + { 109, 239, 162, 1 }, + { 235, 187, 39, 0 }, + { 162, 251, 219, 0 }, }, + { { 114, 110, 228, 1 }, + { 109, 173, 178, 0 }, + { 147, 187, 39, 0 }, + { 38, 218, 219, 0 }, }, + { { 114, 110, 231, 0 }, + { 101, 239, 178, 0 }, + { 115, 187, 39, 0 }, + { 38, 251, 211, 0 }, }, + { { 114, 83, 8, 0 }, + { 100, 33, 193, 1 }, + { 8, 101, 39, 0 }, + { 193, 194, 19, 0 }, }, + { { 114, 83, 11, 1 }, + { 108, 99, 193, 1 }, + { 232, 101, 39, 0 }, + { 193, 227, 27, 0 }, }, + { { 114, 83, 4, 1 }, + { 108, 33, 209, 0 }, + { 144, 101, 39, 0 }, + { 69, 194, 27, 0 }, }, + { { 114, 83, 7, 0 }, + { 100, 99, 209, 0 }, + { 112, 101, 39, 0 }, + { 69, 227, 19, 0 }, }, + { { 114, 83, 120, 0 }, + { 116, 165, 193, 1 }, + { 15, 101, 39, 0 }, + { 193, 210, 151, 0 }, }, + { { 114, 83, 123, 1 }, + { 124, 231, 193, 1 }, + { 239, 101, 39, 0 }, + { 193, 243, 159, 0 }, }, + { { 114, 83, 116, 1 }, + { 124, 165, 209, 0 }, + { 151, 101, 39, 0 }, + { 69, 210, 159, 0 }, }, + { { 114, 83, 119, 0 }, + { 116, 231, 209, 0 }, + { 119, 101, 39, 0 }, + { 69, 243, 151, 0 }, }, + { { 114, 82, 152, 0 }, + { 116, 33, 224, 1 }, + { 12, 165, 39, 0 }, + { 131, 194, 23, 0 }, }, + { { 114, 82, 155, 1 }, + { 124, 99, 224, 1 }, + { 236, 165, 39, 0 }, + { 131, 227, 31, 0 }, }, + { { 114, 82, 148, 1 }, + { 124, 33, 240, 0 }, + { 148, 165, 39, 0 }, + { 7, 194, 31, 0 }, }, + { { 114, 82, 151, 0 }, + { 116, 99, 240, 0 }, + { 116, 165, 39, 0 }, + { 7, 227, 23, 0 }, }, + { { 114, 82, 232, 0 }, + { 100, 165, 224, 1 }, + { 11, 165, 39, 0 }, + { 131, 210, 147, 0 }, }, + { { 114, 82, 235, 1 }, + { 108, 231, 224, 1 }, + { 235, 165, 39, 0 }, + { 131, 243, 155, 0 }, }, + { { 114, 82, 228, 1 }, + { 108, 165, 240, 0 }, + { 147, 165, 39, 0 }, + { 7, 210, 155, 0 }, }, + { { 114, 82, 231, 0 }, + { 100, 231, 240, 0 }, + { 115, 165, 39, 0 }, + { 7, 243, 147, 0 }, }, + { { 114, 93, 8, 0 }, + { 69, 41, 193, 1 }, + { 8, 93, 39, 0 }, + { 193, 202, 81, 0 }, }, + { { 114, 93, 11, 1 }, + { 77, 107, 193, 1 }, + { 232, 93, 39, 0 }, + { 193, 235, 89, 0 }, }, + { { 114, 93, 4, 1 }, + { 77, 41, 209, 0 }, + { 144, 93, 39, 0 }, + { 69, 202, 89, 0 }, }, + { { 114, 93, 7, 0 }, + { 69, 107, 209, 0 }, + { 112, 93, 39, 0 }, + { 69, 235, 81, 0 }, }, + { { 114, 93, 120, 0 }, + { 85, 173, 193, 1 }, + { 15, 93, 39, 0 }, + { 193, 218, 213, 0 }, }, + { { 114, 93, 123, 1 }, + { 93, 239, 193, 1 }, + { 239, 93, 39, 0 }, + { 193, 251, 221, 0 }, }, + { { 114, 93, 116, 1 }, + { 93, 173, 209, 0 }, + { 151, 93, 39, 0 }, + { 69, 218, 221, 0 }, }, + { { 114, 93, 119, 0 }, + { 85, 239, 209, 0 }, + { 119, 93, 39, 0 }, + { 69, 251, 213, 0 }, }, + { { 114, 92, 152, 0 }, + { 85, 41, 224, 1 }, + { 12, 157, 39, 0 }, + { 131, 202, 85, 0 }, }, + { { 114, 92, 155, 1 }, + { 93, 107, 224, 1 }, + { 236, 157, 39, 0 }, + { 131, 235, 93, 0 }, }, + { { 114, 92, 148, 1 }, + { 93, 41, 240, 0 }, + { 148, 157, 39, 0 }, + { 7, 202, 93, 0 }, }, + { { 114, 92, 151, 0 }, + { 85, 107, 240, 0 }, + { 116, 157, 39, 0 }, + { 7, 235, 85, 0 }, }, + { { 114, 92, 232, 0 }, + { 69, 173, 224, 1 }, + { 11, 157, 39, 0 }, + { 131, 218, 209, 0 }, }, + { { 114, 92, 235, 1 }, + { 77, 239, 224, 1 }, + { 235, 157, 39, 0 }, + { 131, 251, 217, 0 }, }, + { { 114, 92, 228, 1 }, + { 77, 173, 240, 0 }, + { 147, 157, 39, 0 }, + { 7, 218, 217, 0 }, }, + { { 114, 92, 231, 0 }, + { 69, 239, 240, 0 }, + { 115, 157, 39, 0 }, + { 7, 251, 209, 0 }, }, + { { 115, 161, 8, 0 }, + { 6, 49, 131, 1 }, + { 8, 66, 231, 0 }, + { 224, 198, 48, 0 }, }, + { { 115, 161, 11, 1 }, + { 14, 115, 131, 1 }, + { 232, 66, 231, 0 }, + { 224, 231, 56, 0 }, }, + { { 115, 161, 4, 1 }, + { 14, 49, 147, 0 }, + { 144, 66, 231, 0 }, + { 100, 198, 56, 0 }, }, + { { 115, 161, 7, 0 }, + { 6, 115, 147, 0 }, + { 112, 66, 231, 0 }, + { 100, 231, 48, 0 }, }, + { { 115, 161, 120, 0 }, + { 22, 181, 131, 1 }, + { 15, 66, 231, 0 }, + { 224, 214, 180, 0 }, }, + { { 115, 161, 123, 1 }, + { 30, 247, 131, 1 }, + { 239, 66, 231, 0 }, + { 224, 247, 188, 0 }, }, + { { 115, 161, 116, 1 }, + { 30, 181, 147, 0 }, + { 151, 66, 231, 0 }, + { 100, 214, 188, 0 }, }, + { { 115, 161, 119, 0 }, + { 22, 247, 147, 0 }, + { 119, 66, 231, 0 }, + { 100, 247, 180, 0 }, }, + { { 115, 160, 152, 0 }, + { 22, 49, 162, 1 }, + { 12, 130, 231, 0 }, + { 162, 198, 52, 0 }, }, + { { 115, 160, 155, 1 }, + { 30, 115, 162, 1 }, + { 236, 130, 231, 0 }, + { 162, 231, 60, 0 }, }, + { { 115, 160, 148, 1 }, + { 30, 49, 178, 0 }, + { 148, 130, 231, 0 }, + { 38, 198, 60, 0 }, }, + { { 115, 160, 151, 0 }, + { 22, 115, 178, 0 }, + { 116, 130, 231, 0 }, + { 38, 231, 52, 0 }, }, + { { 115, 160, 232, 0 }, + { 6, 181, 162, 1 }, + { 11, 130, 231, 0 }, + { 162, 214, 176, 0 }, }, + { { 115, 160, 235, 1 }, + { 14, 247, 162, 1 }, + { 235, 130, 231, 0 }, + { 162, 247, 184, 0 }, }, + { { 115, 160, 228, 1 }, + { 14, 181, 178, 0 }, + { 147, 130, 231, 0 }, + { 38, 214, 184, 0 }, }, + { { 115, 160, 231, 0 }, + { 6, 247, 178, 0 }, + { 115, 130, 231, 0 }, + { 38, 247, 176, 0 }, }, + { { 115, 175, 8, 0 }, + { 39, 57, 131, 1 }, + { 8, 122, 231, 0 }, + { 224, 206, 114, 0 }, }, + { { 115, 175, 11, 1 }, + { 47, 123, 131, 1 }, + { 232, 122, 231, 0 }, + { 224, 239, 122, 0 }, }, + { { 115, 175, 4, 1 }, + { 47, 57, 147, 0 }, + { 144, 122, 231, 0 }, + { 100, 206, 122, 0 }, }, + { { 115, 175, 7, 0 }, + { 39, 123, 147, 0 }, + { 112, 122, 231, 0 }, + { 100, 239, 114, 0 }, }, + { { 115, 175, 120, 0 }, + { 55, 189, 131, 1 }, + { 15, 122, 231, 0 }, + { 224, 222, 246, 0 }, }, + { { 115, 175, 123, 1 }, + { 63, 255, 131, 1 }, + { 239, 122, 231, 0 }, + { 224, 255, 254, 0 }, }, + { { 115, 175, 116, 1 }, + { 63, 189, 147, 0 }, + { 151, 122, 231, 0 }, + { 100, 222, 254, 0 }, }, + { { 115, 175, 119, 0 }, + { 55, 255, 147, 0 }, + { 119, 122, 231, 0 }, + { 100, 255, 246, 0 }, }, + { { 115, 174, 152, 0 }, + { 55, 57, 162, 1 }, + { 12, 186, 231, 0 }, + { 162, 206, 118, 0 }, }, + { { 115, 174, 155, 1 }, + { 63, 123, 162, 1 }, + { 236, 186, 231, 0 }, + { 162, 239, 126, 0 }, }, + { { 115, 174, 148, 1 }, + { 63, 57, 178, 0 }, + { 148, 186, 231, 0 }, + { 38, 206, 126, 0 }, }, + { { 115, 174, 151, 0 }, + { 55, 123, 178, 0 }, + { 116, 186, 231, 0 }, + { 38, 239, 118, 0 }, }, + { { 115, 174, 232, 0 }, + { 39, 189, 162, 1 }, + { 11, 186, 231, 0 }, + { 162, 222, 242, 0 }, }, + { { 115, 174, 235, 1 }, + { 47, 255, 162, 1 }, + { 235, 186, 231, 0 }, + { 162, 255, 250, 0 }, }, + { { 115, 174, 228, 1 }, + { 47, 189, 178, 0 }, + { 147, 186, 231, 0 }, + { 38, 222, 250, 0 }, }, + { { 115, 174, 231, 0 }, + { 39, 255, 178, 0 }, + { 115, 186, 231, 0 }, + { 38, 255, 242, 0 }, }, + { { 115, 147, 8, 0 }, + { 38, 49, 193, 1 }, + { 8, 100, 231, 0 }, + { 193, 198, 50, 0 }, }, + { { 115, 147, 11, 1 }, + { 46, 115, 193, 1 }, + { 232, 100, 231, 0 }, + { 193, 231, 58, 0 }, }, + { { 115, 147, 4, 1 }, + { 46, 49, 209, 0 }, + { 144, 100, 231, 0 }, + { 69, 198, 58, 0 }, }, + { { 115, 147, 7, 0 }, + { 38, 115, 209, 0 }, + { 112, 100, 231, 0 }, + { 69, 231, 50, 0 }, }, + { { 115, 147, 120, 0 }, + { 54, 181, 193, 1 }, + { 15, 100, 231, 0 }, + { 193, 214, 182, 0 }, }, + { { 115, 147, 123, 1 }, + { 62, 247, 193, 1 }, + { 239, 100, 231, 0 }, + { 193, 247, 190, 0 }, }, + { { 115, 147, 116, 1 }, + { 62, 181, 209, 0 }, + { 151, 100, 231, 0 }, + { 69, 214, 190, 0 }, }, + { { 115, 147, 119, 0 }, + { 54, 247, 209, 0 }, + { 119, 100, 231, 0 }, + { 69, 247, 182, 0 }, }, + { { 115, 146, 152, 0 }, + { 54, 49, 224, 1 }, + { 12, 164, 231, 0 }, + { 131, 198, 54, 0 }, }, + { { 115, 146, 155, 1 }, + { 62, 115, 224, 1 }, + { 236, 164, 231, 0 }, + { 131, 231, 62, 0 }, }, + { { 115, 146, 148, 1 }, + { 62, 49, 240, 0 }, + { 148, 164, 231, 0 }, + { 7, 198, 62, 0 }, }, + { { 115, 146, 151, 0 }, + { 54, 115, 240, 0 }, + { 116, 164, 231, 0 }, + { 7, 231, 54, 0 }, }, + { { 115, 146, 232, 0 }, + { 38, 181, 224, 1 }, + { 11, 164, 231, 0 }, + { 131, 214, 178, 0 }, }, + { { 115, 146, 235, 1 }, + { 46, 247, 224, 1 }, + { 235, 164, 231, 0 }, + { 131, 247, 186, 0 }, }, + { { 115, 146, 228, 1 }, + { 46, 181, 240, 0 }, + { 147, 164, 231, 0 }, + { 7, 214, 186, 0 }, }, + { { 115, 146, 231, 0 }, + { 38, 247, 240, 0 }, + { 115, 164, 231, 0 }, + { 7, 247, 178, 0 }, }, + { { 115, 157, 8, 0 }, + { 7, 57, 193, 1 }, + { 8, 92, 231, 0 }, + { 193, 206, 112, 0 }, }, + { { 115, 157, 11, 1 }, + { 15, 123, 193, 1 }, + { 232, 92, 231, 0 }, + { 193, 239, 120, 0 }, }, + { { 115, 157, 4, 1 }, + { 15, 57, 209, 0 }, + { 144, 92, 231, 0 }, + { 69, 206, 120, 0 }, }, + { { 115, 157, 7, 0 }, + { 7, 123, 209, 0 }, + { 112, 92, 231, 0 }, + { 69, 239, 112, 0 }, }, + { { 115, 157, 120, 0 }, + { 23, 189, 193, 1 }, + { 15, 92, 231, 0 }, + { 193, 222, 244, 0 }, }, + { { 115, 157, 123, 1 }, + { 31, 255, 193, 1 }, + { 239, 92, 231, 0 }, + { 193, 255, 252, 0 }, }, + { { 115, 157, 116, 1 }, + { 31, 189, 209, 0 }, + { 151, 92, 231, 0 }, + { 69, 222, 252, 0 }, }, + { { 115, 157, 119, 0 }, + { 23, 255, 209, 0 }, + { 119, 92, 231, 0 }, + { 69, 255, 244, 0 }, }, + { { 115, 156, 152, 0 }, + { 23, 57, 224, 1 }, + { 12, 156, 231, 0 }, + { 131, 206, 116, 0 }, }, + { { 115, 156, 155, 1 }, + { 31, 123, 224, 1 }, + { 236, 156, 231, 0 }, + { 131, 239, 124, 0 }, }, + { { 115, 156, 148, 1 }, + { 31, 57, 240, 0 }, + { 148, 156, 231, 0 }, + { 7, 206, 124, 0 }, }, + { { 115, 156, 151, 0 }, + { 23, 123, 240, 0 }, + { 116, 156, 231, 0 }, + { 7, 239, 116, 0 }, }, + { { 115, 156, 232, 0 }, + { 7, 189, 224, 1 }, + { 11, 156, 231, 0 }, + { 131, 222, 240, 0 }, }, + { { 115, 156, 235, 1 }, + { 15, 255, 224, 1 }, + { 235, 156, 231, 0 }, + { 131, 255, 248, 0 }, }, + { { 115, 156, 228, 1 }, + { 15, 189, 240, 0 }, + { 147, 156, 231, 0 }, + { 7, 222, 248, 0 }, }, + { { 115, 156, 231, 0 }, + { 7, 255, 240, 0 }, + { 115, 156, 231, 0 }, + { 7, 255, 240, 0 }, }, +}; + +static unsigned char DICT_4X4_1000_BYTES[][4][2] = + { { { 181, 50 }, + { 235, 72 }, + { 76, 173 }, + { 18, 215 }, }, + { { 15, 154 }, + { 101, 71 }, + { 89, 240 }, + { 226, 166 }, }, + { { 51, 45 }, + { 222, 17 }, + { 180, 204 }, + { 136, 123 }, }, + { { 153, 70 }, + { 193, 60 }, + { 98, 153 }, + { 60, 131 }, }, + { { 84, 158 }, + { 161, 211 }, + { 121, 42 }, + { 203, 133 }, }, + { { 121, 205 }, + { 216, 183 }, + { 179, 158 }, + { 237, 27 }, }, + { { 158, 46 }, + { 135, 93 }, + { 116, 121 }, + { 186, 225 }, }, + { { 196, 242 }, + { 35, 234 }, + { 79, 35 }, + { 87, 196 }, }, + { { 254, 218 }, + { 173, 239 }, + { 91, 127 }, + { 247, 181 }, }, + { { 207, 86 }, + { 101, 252 }, + { 106, 243 }, + { 63, 166 }, }, + { { 249, 145 }, + { 248, 142 }, + { 137, 159 }, + { 113, 31 }, }, + { { 17, 167 }, + { 211, 18 }, + { 229, 136 }, + { 72, 203 }, }, + { { 14, 183 }, + { 55, 86 }, + { 237, 112 }, + { 106, 236 }, }, + { { 42, 15 }, + { 29, 21 }, + { 240, 84 }, + { 168, 184 }, }, + { { 36, 177 }, + { 58, 66 }, + { 141, 36 }, + { 66, 92 }, }, + { { 38, 62 }, + { 47, 81 }, + { 124, 100 }, + { 138, 244 }, }, + { { 70, 101 }, + { 22, 240 }, + { 166, 98 }, + { 15, 104 }, }, + { { 102, 0 }, + { 12, 192 }, + { 0, 102 }, + { 3, 48 }, }, + { { 108, 94 }, + { 41, 245 }, + { 122, 54 }, + { 175, 148 }, }, + { { 118, 175 }, + { 159, 211 }, + { 245, 110 }, + { 203, 249 }, }, + { { 134, 139 }, + { 21, 75 }, + { 209, 97 }, + { 210, 168 }, }, + { { 176, 43 }, + { 155, 9 }, + { 212, 13 }, + { 144, 217 }, }, + { { 204, 213 }, + { 48, 254 }, + { 171, 51 }, + { 127, 12 }, }, + { { 221, 130 }, + { 193, 206 }, + { 65, 187 }, + { 115, 131 }, }, + { { 254, 71 }, + { 157, 252 }, + { 226, 127 }, + { 63, 185 }, }, + { { 148, 113 }, + { 178, 104 }, + { 142, 41 }, + { 22, 77 }, }, + { { 172, 228 }, + { 10, 126 }, + { 39, 53 }, + { 126, 80 }, }, + { { 165, 84 }, + { 104, 120 }, + { 42, 165 }, + { 30, 22 }, }, + { { 33, 35 }, + { 91, 0 }, + { 196, 132 }, + { 0, 218 }, }, + { { 52, 111 }, + { 155, 113 }, + { 246, 44 }, + { 142, 217 }, }, + { { 68, 21 }, + { 48, 208 }, + { 168, 34 }, + { 11, 12 }, }, + { { 87, 178 }, + { 231, 194 }, + { 77, 234 }, + { 67, 231 }, }, + { { 158, 207 }, + { 149, 127 }, + { 243, 121 }, + { 254, 169 }, }, + { { 240, 203 }, + { 153, 171 }, + { 211, 15 }, + { 213, 153 }, }, + { { 8, 174 }, + { 3, 23 }, + { 117, 16 }, + { 232, 192 }, }, + { { 9, 41 }, + { 82, 5 }, + { 148, 144 }, + { 160, 74 }, }, + { { 24, 117 }, + { 178, 52 }, + { 174, 24 }, + { 44, 77 }, }, + { { 4, 255 }, + { 51, 115 }, + { 255, 32 }, + { 206, 204 }, }, + { { 13, 246 }, + { 99, 118 }, + { 111, 176 }, + { 110, 198 }, }, + { { 28, 90 }, + { 161, 101 }, + { 90, 56 }, + { 166, 133 }, }, + { { 23, 24 }, + { 228, 65 }, + { 24, 232 }, + { 130, 39 }, }, + { { 42, 40 }, + { 14, 5 }, + { 20, 84 }, + { 160, 112 }, }, + { { 50, 140 }, + { 140, 19 }, + { 49, 76 }, + { 200, 49 }, }, + { { 56, 178 }, + { 171, 6 }, + { 77, 28 }, + { 96, 213 }, }, + { { 36, 232 }, + { 10, 99 }, + { 23, 36 }, + { 198, 80 }, }, + { { 46, 235 }, + { 31, 103 }, + { 215, 116 }, + { 230, 248 }, }, + { { 45, 63 }, + { 123, 85 }, + { 252, 180 }, + { 170, 222 }, }, + { { 75, 100 }, + { 70, 180 }, + { 38, 210 }, + { 45, 98 }, }, + { { 80, 46 }, + { 131, 145 }, + { 116, 10 }, + { 137, 193 }, }, + { { 80, 19 }, + { 177, 128 }, + { 200, 10 }, + { 1, 141 }, }, + { { 81, 148 }, + { 224, 146 }, + { 41, 138 }, + { 73, 7 }, }, + { { 85, 104 }, + { 194, 225 }, + { 22, 170 }, + { 135, 67 }, }, + { { 93, 65 }, + { 208, 228 }, + { 130, 186 }, + { 39, 11 }, }, + { { 95, 151 }, + { 245, 214 }, + { 233, 250 }, + { 107, 175 }, }, + { { 104, 1 }, + { 24, 132 }, + { 128, 22 }, + { 33, 24 }, }, + { { 104, 103 }, + { 27, 180 }, + { 230, 22 }, + { 45, 216 }, }, + { { 97, 36 }, + { 74, 144 }, + { 36, 134 }, + { 9, 82 }, }, + { { 97, 233 }, + { 90, 163 }, + { 151, 134 }, + { 197, 90 }, }, + { { 107, 18 }, + { 109, 132 }, + { 72, 214 }, + { 33, 182 }, }, + { { 111, 229 }, + { 94, 246 }, + { 167, 246 }, + { 111, 122 }, }, + { { 103, 223 }, + { 125, 243 }, + { 251, 230 }, + { 207, 190 }, }, + { { 126, 27 }, + { 189, 197 }, + { 216, 126 }, + { 163, 189 }, }, + { { 128, 160 }, + { 2, 10 }, + { 5, 1 }, + { 80, 64 }, }, + { { 131, 68 }, + { 68, 56 }, + { 34, 193 }, + { 28, 34 }, }, + { { 139, 162 }, + { 71, 14 }, + { 69, 209 }, + { 112, 226 }, }, + { { 147, 122 }, + { 231, 41 }, + { 94, 201 }, + { 148, 231 }, }, + { { 132, 108 }, + { 2, 121 }, + { 54, 33 }, + { 158, 64 }, }, + { { 133, 42 }, + { 67, 73 }, + { 84, 161 }, + { 146, 194 }, }, + { { 133, 156 }, + { 96, 91 }, + { 57, 161 }, + { 218, 6 }, }, + { { 156, 137 }, + { 144, 79 }, + { 145, 57 }, + { 242, 9 }, }, + { { 159, 161 }, + { 214, 78 }, + { 133, 249 }, + { 114, 107 }, }, + { { 187, 124 }, + { 238, 61 }, + { 62, 221 }, + { 188, 119 }, }, + { { 188, 4 }, + { 136, 92 }, + { 32, 61 }, + { 58, 17 }, }, + { { 182, 91 }, + { 189, 105 }, + { 218, 109 }, + { 150, 189 }, }, + { { 191, 200 }, + { 204, 111 }, + { 19, 253 }, + { 246, 51 }, }, + { { 183, 171 }, + { 223, 75 }, + { 213, 237 }, + { 210, 251 }, }, + { { 202, 31 }, + { 53, 157 }, + { 248, 83 }, + { 185, 172 }, }, + { { 201, 98 }, + { 67, 172 }, + { 70, 147 }, + { 53, 194 }, }, + { { 217, 88 }, + { 224, 173 }, + { 26, 155 }, + { 181, 7 }, }, + { { 211, 213 }, + { 244, 186 }, + { 171, 203 }, + { 93, 47 }, }, + { { 204, 152 }, + { 32, 207 }, + { 25, 51 }, + { 243, 4 }, }, + { { 199, 160 }, + { 70, 202 }, + { 5, 227 }, + { 83, 98 }, }, + { { 197, 55 }, + { 115, 216 }, + { 236, 163 }, + { 27, 206 }, }, + { { 233, 93 }, + { 120, 189 }, + { 186, 151 }, + { 189, 30 }, }, + { { 249, 37 }, + { 218, 156 }, + { 164, 159 }, + { 57, 91 }, }, + { { 251, 187 }, + { 255, 143 }, + { 221, 223 }, + { 241, 255 }, }, + { { 238, 42 }, + { 15, 205 }, + { 84, 119 }, + { 179, 240 }, }, + { { 247, 77 }, + { 220, 249 }, + { 178, 239 }, + { 159, 59 }, }, + { { 53, 117 }, + { 250, 112 }, + { 174, 172 }, + { 14, 95 }, }, + { { 138, 173 }, + { 22, 31 }, + { 181, 81 }, + { 248, 104 }, }, + { { 118, 23 }, + { 189, 208 }, + { 232, 110 }, + { 11, 189 }, }, + { { 10, 207 }, + { 21, 55 }, + { 243, 80 }, + { 236, 168 }, }, + { { 6, 75 }, + { 21, 97 }, + { 210, 96 }, + { 134, 168 }, }, + { { 45, 193 }, + { 88, 102 }, + { 131, 180 }, + { 102, 26 }, }, + { { 73, 216 }, + { 96, 167 }, + { 27, 146 }, + { 229, 6 }, }, + { { 67, 244 }, + { 102, 178 }, + { 47, 194 }, + { 77, 102 }, }, + { { 79, 54 }, + { 103, 212 }, + { 108, 242 }, + { 43, 230 }, }, + { { 79, 211 }, + { 117, 230 }, + { 203, 242 }, + { 103, 174 }, }, + { { 105, 228 }, + { 74, 182 }, + { 39, 150 }, + { 109, 82 }, }, + { { 112, 199 }, + { 153, 178 }, + { 227, 14 }, + { 77, 153 }, }, + { { 122, 110 }, + { 143, 181 }, + { 118, 94 }, + { 173, 241 }, }, + { { 180, 234 }, + { 139, 107 }, + { 87, 45 }, + { 214, 209 }, }, + { { 237, 79 }, + { 89, 253 }, + { 242, 183 }, + { 191, 154 }, }, + { { 252, 231 }, + { 155, 254 }, + { 231, 63 }, + { 127, 217 }, }, + { { 254, 166 }, + { 143, 222 }, + { 101, 127 }, + { 123, 241 }, }, + { { 0, 37 }, + { 18, 16 }, + { 164, 0 }, + { 8, 72 }, }, + { { 0, 67 }, + { 17, 32 }, + { 194, 0 }, + { 4, 136 }, }, + { { 10, 136 }, + { 4, 7 }, + { 17, 80 }, + { 224, 32 }, }, + { { 10, 134 }, + { 5, 22 }, + { 97, 80 }, + { 104, 160 }, }, + { { 2, 111 }, + { 23, 49 }, + { 246, 64 }, + { 140, 232 }, }, + { { 0, 28 }, + { 32, 17 }, + { 56, 0 }, + { 136, 4 }, }, + { { 0, 151 }, + { 49, 18 }, + { 233, 0 }, + { 72, 140 }, }, + { { 8, 55 }, + { 51, 20 }, + { 236, 16 }, + { 40, 204 }, }, + { { 10, 49 }, + { 54, 4 }, + { 140, 80 }, + { 32, 108 }, }, + { { 9, 198 }, + { 65, 54 }, + { 99, 144 }, + { 108, 130 }, }, + { { 11, 1 }, + { 84, 4 }, + { 128, 208 }, + { 32, 42 }, }, + { { 9, 251 }, + { 115, 39 }, + { 223, 144 }, + { 228, 206 }, }, + { { 11, 88 }, + { 100, 37 }, + { 26, 208 }, + { 164, 38 }, }, + { { 16, 130 }, + { 129, 2 }, + { 65, 8 }, + { 64, 129 }, }, + { { 24, 45 }, + { 146, 21 }, + { 180, 24 }, + { 168, 73 }, }, + { { 16, 120 }, + { 162, 33 }, + { 30, 8 }, + { 132, 69 }, }, + { { 16, 115 }, + { 179, 32 }, + { 206, 8 }, + { 4, 205 }, }, + { { 18, 116 }, + { 166, 48 }, + { 46, 72 }, + { 12, 101 }, }, + { { 18, 177 }, + { 182, 2 }, + { 141, 72 }, + { 64, 109 }, }, + { { 26, 249 }, + { 182, 39 }, + { 159, 88 }, + { 228, 109 }, }, + { { 19, 6 }, + { 197, 16 }, + { 96, 200 }, + { 8, 163 }, }, + { { 12, 14 }, + { 1, 85 }, + { 112, 48 }, + { 170, 128 }, }, + { { 12, 241 }, + { 50, 102 }, + { 143, 48 }, + { 102, 76 }, }, + { { 4, 51 }, + { 51, 64 }, + { 204, 32 }, + { 2, 204 }, }, + { { 12, 159 }, + { 49, 87 }, + { 249, 48 }, + { 234, 140 }, }, + { { 14, 242 }, + { 39, 102 }, + { 79, 112 }, + { 102, 228 }, }, + { { 14, 253 }, + { 54, 119 }, + { 191, 112 }, + { 238, 108 }, }, + { { 7, 76 }, + { 68, 113 }, + { 50, 224 }, + { 142, 34 }, }, + { { 15, 164 }, + { 70, 86 }, + { 37, 240 }, + { 106, 98 }, }, + { { 7, 47 }, + { 87, 81 }, + { 244, 224 }, + { 138, 234 }, }, + { { 5, 181 }, + { 114, 82 }, + { 173, 160 }, + { 74, 78 }, }, + { { 15, 145 }, + { 116, 70 }, + { 137, 240 }, + { 98, 46 }, }, + { { 7, 219 }, + { 117, 99 }, + { 219, 224 }, + { 198, 174 }, }, + { { 30, 228 }, + { 134, 118 }, + { 39, 120 }, + { 110, 97 }, }, + { { 20, 57 }, + { 178, 65 }, + { 156, 40 }, + { 130, 77 }, }, + { { 29, 128 }, + { 192, 70 }, + { 1, 184 }, + { 98, 3 }, }, + { { 21, 200 }, + { 192, 99 }, + { 19, 168 }, + { 198, 3 }, }, + { { 31, 139 }, + { 213, 71 }, + { 209, 248 }, + { 226, 171 }, }, + { { 21, 186 }, + { 227, 67 }, + { 93, 168 }, + { 194, 199 }, }, + { { 29, 177 }, + { 242, 70 }, + { 141, 184 }, + { 98, 79 }, }, + { { 32, 128 }, + { 8, 2 }, + { 1, 4 }, + { 64, 16 }, }, + { { 40, 233 }, + { 26, 39 }, + { 151, 20 }, + { 228, 88 }, }, + { { 34, 162 }, + { 15, 2 }, + { 69, 68 }, + { 64, 240 }, }, + { { 40, 83 }, + { 57, 36 }, + { 202, 20 }, + { 36, 156 }, }, + { { 42, 240 }, + { 46, 38 }, + { 15, 84 }, + { 100, 116 }, }, + { { 34, 247 }, + { 63, 50 }, + { 239, 68 }, + { 76, 252 }, }, + { { 41, 64 }, + { 72, 36 }, + { 2, 148 }, + { 36, 18 }, }, + { { 33, 70 }, + { 73, 48 }, + { 98, 132 }, + { 12, 146 }, }, + { { 41, 185 }, + { 122, 7 }, + { 157, 148 }, + { 224, 94 }, }, + { { 43, 156 }, + { 108, 23 }, + { 57, 212 }, + { 232, 54 }, }, + { { 43, 178 }, + { 111, 6 }, + { 77, 212 }, + { 96, 246 }, }, + { { 56, 202 }, + { 137, 39 }, + { 83, 28 }, + { 228, 145 }, }, + { { 56, 46 }, + { 139, 21 }, + { 116, 28 }, + { 168, 209 }, }, + { { 48, 7 }, + { 153, 16 }, + { 224, 12 }, + { 8, 153 }, }, + { { 56, 231 }, + { 155, 54 }, + { 231, 28 }, + { 108, 217 }, }, + { { 58, 73 }, + { 156, 37 }, + { 146, 92 }, + { 164, 57 }, }, + { { 58, 101 }, + { 158, 52 }, + { 166, 92 }, + { 44, 121 }, }, + { { 50, 93 }, + { 188, 49 }, + { 186, 76 }, + { 140, 61 }, }, + { { 59, 136 }, + { 204, 7 }, + { 17, 220 }, + { 224, 51 }, }, + { { 57, 29 }, + { 248, 21 }, + { 184, 156 }, + { 168, 31 }, }, + { { 59, 211 }, + { 253, 38 }, + { 203, 220 }, + { 100, 191 }, }, + { { 38, 71 }, + { 29, 112 }, + { 226, 100 }, + { 14, 184 }, }, + { { 39, 128 }, + { 76, 66 }, + { 1, 228 }, + { 66, 50 }, }, + { { 47, 170 }, + { 79, 71 }, + { 85, 244 }, + { 226, 242 }, }, + { { 45, 20 }, + { 104, 84 }, + { 40, 180 }, + { 42, 22 }, }, + { { 37, 222 }, + { 105, 115 }, + { 123, 164 }, + { 206, 150 }, }, + { { 37, 83 }, + { 121, 96 }, + { 202, 164 }, + { 6, 158 }, }, + { { 47, 119 }, + { 127, 116 }, + { 238, 244 }, + { 46, 254 }, }, + { { 52, 72 }, + { 136, 97 }, + { 18, 44 }, + { 134, 17 }, }, + { { 60, 168 }, + { 138, 71 }, + { 21, 60 }, + { 226, 81 }, }, + { { 60, 65 }, + { 152, 100 }, + { 130, 60 }, + { 38, 25 }, }, + { { 52, 13 }, + { 152, 81 }, + { 176, 44 }, + { 138, 25 }, }, + { { 52, 251 }, + { 187, 99 }, + { 223, 44 }, + { 198, 221 }, }, + { { 54, 154 }, + { 173, 67 }, + { 89, 108 }, + { 194, 181 }, }, + { { 61, 224 }, + { 202, 102 }, + { 7, 188 }, + { 102, 83 }, }, + { { 53, 106 }, + { 203, 97 }, + { 86, 172 }, + { 134, 211 }, }, + { { 61, 9 }, + { 216, 69 }, + { 144, 188 }, + { 162, 27 }, }, + { { 61, 237 }, + { 218, 119 }, + { 183, 188 }, + { 238, 91 }, }, + { { 63, 196 }, + { 204, 118 }, + { 35, 252 }, + { 110, 51 }, }, + { { 63, 108 }, + { 206, 117 }, + { 54, 252 }, + { 174, 115 }, }, + { { 55, 206 }, + { 205, 115 }, + { 115, 236 }, + { 206, 179 }, }, + { { 61, 92 }, + { 232, 117 }, + { 58, 188 }, + { 174, 23 }, }, + { { 61, 118 }, + { 235, 116 }, + { 110, 188 }, + { 46, 215 }, }, + { { 55, 176 }, + { 238, 66 }, + { 13, 236 }, + { 66, 119 }, }, + { { 63, 23 }, + { 253, 84 }, + { 232, 252 }, + { 42, 191 }, }, + { { 63, 255 }, + { 255, 119 }, + { 255, 252 }, + { 238, 255 }, }, + { { 72, 229 }, + { 18, 182 }, + { 167, 18 }, + { 109, 72 }, }, + { { 66, 104 }, + { 6, 161 }, + { 22, 66 }, + { 133, 96 }, }, + { { 74, 45 }, + { 22, 149 }, + { 180, 82 }, + { 169, 104 }, }, + { { 65, 96 }, + { 66, 160 }, + { 6, 130 }, + { 5, 66 }, }, + { { 73, 81 }, + { 112, 164 }, + { 138, 146 }, + { 37, 14 }, }, + { { 65, 221 }, + { 112, 179 }, + { 187, 130 }, + { 205, 14 }, }, + { { 75, 223 }, + { 117, 183 }, + { 251, 210 }, + { 237, 174 }, }, + { { 88, 79 }, + { 145, 181 }, + { 242, 26 }, + { 173, 137 }, }, + { { 90, 72 }, + { 132, 165 }, + { 18, 90 }, + { 165, 33 }, }, + { { 88, 22 }, + { 161, 148 }, + { 104, 26 }, + { 41, 133 }, }, + { { 80, 93 }, + { 176, 177 }, + { 186, 10 }, + { 141, 13 }, }, + { { 90, 250 }, + { 167, 167 }, + { 95, 90 }, + { 229, 229 }, }, + { { 90, 181 }, + { 182, 150 }, + { 173, 90 }, + { 105, 109 }, }, + { { 81, 35 }, + { 211, 128 }, + { 196, 138 }, + { 1, 203 }, }, + { { 91, 138 }, + { 197, 135 }, + { 81, 218 }, + { 225, 163 }, }, + { { 89, 25 }, + { 240, 133 }, + { 152, 154 }, + { 161, 15 }, }, + { { 81, 53 }, + { 242, 144 }, + { 172, 138 }, + { 9, 79 }, }, + { { 76, 105 }, + { 18, 229 }, + { 150, 50 }, + { 167, 72 }, }, + { { 70, 193 }, + { 20, 226 }, + { 131, 98 }, + { 71, 40 }, }, + { { 78, 11 }, + { 21, 197 }, + { 208, 114 }, + { 163, 168 }, }, + { { 68, 95 }, + { 49, 241 }, + { 250, 34 }, + { 143, 140 }, }, + { { 78, 89 }, + { 52, 229 }, + { 154, 114 }, + { 167, 44 }, }, + { { 77, 131 }, + { 81, 198 }, + { 193, 178 }, + { 99, 138 }, }, + { { 77, 125 }, + { 114, 245 }, + { 190, 178 }, + { 175, 78 }, }, + { { 71, 216 }, + { 100, 227 }, + { 27, 226 }, + { 199, 38 }, }, + { { 71, 115 }, + { 119, 224 }, + { 206, 226 }, + { 7, 238 }, }, + { { 92, 133 }, + { 144, 214 }, + { 161, 58 }, + { 107, 9 }, }, + { { 94, 68 }, + { 132, 244 }, + { 34, 122 }, + { 47, 33 }, }, + { { 86, 43 }, + { 151, 193 }, + { 212, 106 }, + { 131, 233 }, }, + { { 92, 187 }, + { 179, 199 }, + { 221, 58 }, + { 227, 205 }, }, + { { 85, 195 }, + { 209, 226 }, + { 195, 170 }, + { 71, 139 }, }, + { { 95, 110 }, + { 199, 245 }, + { 118, 250 }, + { 175, 227 }, }, + { { 95, 235 }, + { 215, 231 }, + { 215, 250 }, + { 231, 235 }, }, + { { 93, 18 }, + { 225, 196 }, + { 72, 186 }, + { 35, 135 }, }, + { { 85, 94 }, + { 225, 241 }, + { 122, 170 }, + { 143, 135 }, }, + { { 98, 112 }, + { 46, 160 }, + { 14, 70 }, + { 5, 116 }, }, + { { 98, 21 }, + { 60, 144 }, + { 168, 70 }, + { 9, 60 }, }, + { { 97, 194 }, + { 73, 162 }, + { 67, 134 }, + { 69, 146 }, }, + { { 107, 32 }, + { 78, 132 }, + { 4, 214 }, + { 33, 114 }, }, + { { 99, 69 }, + { 92, 176 }, + { 162, 198 }, + { 13, 58 }, }, + { { 107, 92 }, + { 108, 181 }, + { 58, 214 }, + { 173, 54 }, }, + { { 107, 91 }, + { 125, 165 }, + { 218, 214 }, + { 165, 190 }, }, + { { 120, 12 }, + { 136, 149 }, + { 48, 30 }, + { 169, 17 }, }, + { { 122, 207 }, + { 157, 183 }, + { 243, 94 }, + { 237, 185 }, }, + { { 120, 127 }, + { 187, 181 }, + { 254, 30 }, + { 173, 221 }, }, + { { 121, 128 }, + { 200, 134 }, + { 1, 158 }, + { 97, 19 }, }, + { { 113, 229 }, + { 218, 178 }, + { 167, 142 }, + { 77, 91 }, }, + { { 113, 116 }, + { 234, 176 }, + { 46, 142 }, + { 13, 87 }, }, + { { 121, 182 }, + { 235, 150 }, + { 109, 158 }, + { 105, 215 }, }, + { { 113, 211 }, + { 249, 162 }, + { 203, 142 }, + { 69, 159 }, }, + { { 123, 51 }, + { 255, 132 }, + { 204, 222 }, + { 33, 255 }, }, + { { 100, 106 }, + { 11, 225 }, + { 86, 38 }, + { 135, 208 }, }, + { { 102, 168 }, + { 14, 195 }, + { 21, 102 }, + { 195, 112 }, }, + { { 110, 167 }, + { 31, 214 }, + { 229, 118 }, + { 107, 248 }, }, + { { 110, 145 }, + { 60, 198 }, + { 137, 118 }, + { 99, 60 }, }, + { { 101, 34 }, + { 75, 192 }, + { 68, 166 }, + { 3, 210 }, }, + { { 109, 203 }, + { 89, 231 }, + { 211, 182 }, + { 231, 154 }, }, + { { 103, 141 }, + { 92, 211 }, + { 177, 230 }, + { 203, 58 }, }, + { { 109, 49 }, + { 122, 196 }, + { 140, 182 }, + { 35, 94 }, }, + { { 126, 128 }, + { 140, 198 }, + { 1, 126 }, + { 99, 49 }, }, + { { 126, 226 }, + { 143, 230 }, + { 71, 126 }, + { 103, 241 }, }, + { { 126, 141 }, + { 156, 215 }, + { 177, 126 }, + { 235, 57 }, }, + { { 116, 210 }, + { 169, 226 }, + { 75, 46 }, + { 71, 149 }, }, + { { 124, 50 }, + { 171, 196 }, + { 76, 62 }, + { 35, 213 }, }, + { { 126, 53 }, + { 190, 212 }, + { 172, 126 }, + { 43, 125 }, }, + { { 117, 171 }, + { 219, 195 }, + { 213, 174 }, + { 195, 219 }, }, + { { 119, 5 }, + { 220, 208 }, + { 160, 238 }, + { 11, 59 }, }, + { { 127, 43 }, + { 223, 197 }, + { 212, 254 }, + { 163, 251 }, }, + { { 125, 218 }, + { 233, 231 }, + { 91, 190 }, + { 231, 151 }, }, + { { 127, 146 }, + { 237, 198 }, + { 73, 254 }, + { 99, 183 }, }, + { { 128, 117 }, + { 50, 56 }, + { 174, 1 }, + { 28, 76 }, }, + { { 128, 243 }, + { 51, 42 }, + { 207, 1 }, + { 84, 204 }, }, + { { 129, 166 }, + { 67, 26 }, + { 101, 129 }, + { 88, 194 }, }, + { { 137, 237 }, + { 82, 63 }, + { 183, 145 }, + { 252, 74 }, }, + { { 129, 252 }, + { 98, 59 }, + { 63, 129 }, + { 220, 70 }, }, + { { 152, 166 }, + { 131, 30 }, + { 101, 25 }, + { 120, 193 }, }, + { { 154, 32 }, + { 134, 12 }, + { 4, 89 }, + { 48, 97 }, }, + { { 145, 67 }, + { 209, 40 }, + { 194, 137 }, + { 20, 139 }, }, + { { 153, 249 }, + { 242, 47 }, + { 159, 153 }, + { 244, 79 }, }, + { { 145, 147 }, + { 241, 10 }, + { 201, 137 }, + { 80, 143 }, }, + { { 155, 212 }, + { 228, 62 }, + { 43, 217 }, + { 124, 39 }, }, + { { 132, 9 }, + { 16, 73 }, + { 144, 33 }, + { 146, 8 }, }, + { { 132, 107 }, + { 19, 105 }, + { 214, 33 }, + { 150, 200 }, }, + { { 134, 196 }, + { 4, 122 }, + { 35, 97 }, + { 94, 32 }, }, + { { 142, 100 }, + { 6, 124 }, + { 38, 113 }, + { 62, 96 }, }, + { { 134, 26 }, + { 37, 73 }, + { 88, 97 }, + { 146, 164 }, }, + { { 133, 78 }, + { 65, 121 }, + { 114, 161 }, + { 158, 130 }, }, + { { 141, 203 }, + { 81, 111 }, + { 211, 177 }, + { 246, 138 }, }, + { { 133, 103 }, + { 83, 120 }, + { 230, 161 }, + { 30, 202 }, }, + { { 133, 175 }, + { 83, 91 }, + { 245, 161 }, + { 218, 202 }, }, + { { 133, 215 }, + { 113, 122 }, + { 235, 161 }, + { 94, 142 }, }, + { { 135, 179 }, + { 119, 74 }, + { 205, 225 }, + { 82, 238 }, }, + { { 156, 225 }, + { 146, 110 }, + { 135, 57 }, + { 118, 73 }, }, + { { 156, 242 }, + { 163, 110 }, + { 79, 57 }, + { 118, 197 }, }, + { { 148, 23 }, + { 177, 88 }, + { 232, 41 }, + { 26, 141 }, }, + { { 149, 0 }, + { 192, 72 }, + { 0, 169 }, + { 18, 3 }, }, + { { 149, 162 }, + { 195, 74 }, + { 69, 169 }, + { 82, 195 }, }, + { { 157, 35 }, + { 211, 76 }, + { 196, 185 }, + { 50, 203 }, }, + { { 159, 98 }, + { 199, 108 }, + { 70, 249 }, + { 54, 227 }, }, + { { 157, 82 }, + { 225, 108 }, + { 74, 185 }, + { 54, 135 }, }, + { { 149, 218 }, + { 225, 107 }, + { 91, 169 }, + { 214, 135 }, }, + { { 160, 197 }, + { 24, 58 }, + { 163, 5 }, + { 92, 24 }, }, + { { 170, 205 }, + { 28, 63 }, + { 179, 85 }, + { 252, 56 }, }, + { { 162, 216 }, + { 44, 43 }, + { 27, 69 }, + { 212, 52 }, }, + { { 162, 87 }, + { 61, 56 }, + { 234, 69 }, + { 28, 188 }, }, + { { 169, 61 }, + { 122, 29 }, + { 188, 149 }, + { 184, 94 }, }, + { { 169, 87 }, + { 121, 60 }, + { 234, 149 }, + { 60, 158 }, }, + { { 171, 82 }, + { 109, 44 }, + { 74, 213 }, + { 52, 182 }, }, + { { 163, 54 }, + { 111, 24 }, + { 108, 197 }, + { 24, 246 }, }, + { { 163, 89 }, + { 124, 41 }, + { 154, 197 }, + { 148, 62 }, }, + { { 176, 244 }, + { 170, 58 }, + { 47, 13 }, + { 92, 85 }, }, + { { 184, 18 }, + { 169, 12 }, + { 72, 29 }, + { 48, 149 }, }, + { { 176, 191 }, + { 187, 27 }, + { 253, 13 }, + { 216, 221 }, }, + { { 178, 157 }, + { 188, 27 }, + { 185, 77 }, + { 216, 61 }, }, + { { 187, 237 }, + { 222, 63 }, + { 183, 221 }, + { 252, 123 }, }, + { { 185, 114 }, + { 235, 44 }, + { 78, 157 }, + { 52, 215 }, }, + { { 185, 150 }, + { 233, 30 }, + { 105, 157 }, + { 120, 151 }, }, + { { 164, 195 }, + { 25, 106 }, + { 195, 37 }, + { 86, 152 }, }, + { { 172, 210 }, + { 41, 110 }, + { 75, 53 }, + { 118, 148 }, }, + { { 174, 177 }, + { 62, 78 }, + { 141, 117 }, + { 114, 124 }, }, + { { 165, 130 }, + { 73, 74 }, + { 65, 165 }, + { 82, 146 }, }, + { { 175, 101 }, + { 94, 124 }, + { 166, 245 }, + { 62, 122 }, }, + { { 165, 123 }, + { 123, 105 }, + { 222, 165 }, + { 150, 222 }, }, + { { 175, 250 }, + { 111, 111 }, + { 95, 245 }, + { 246, 246 }, }, + { { 180, 100 }, + { 138, 120 }, + { 38, 45 }, + { 30, 81 }, }, + { { 188, 98 }, + { 139, 108 }, + { 70, 61 }, + { 54, 209 }, }, + { { 180, 129 }, + { 152, 74 }, + { 129, 45 }, + { 82, 25 }, }, + { { 182, 160 }, + { 142, 74 }, + { 5, 109 }, + { 82, 113 }, }, + { { 190, 238 }, + { 143, 127 }, + { 119, 125 }, + { 254, 241 }, }, + { { 190, 13 }, + { 156, 93 }, + { 176, 125 }, + { 186, 57 }, }, + { { 188, 217 }, + { 184, 111 }, + { 155, 61 }, + { 246, 29 }, }, + { { 190, 248 }, + { 174, 111 }, + { 31, 125 }, + { 246, 117 }, }, + { { 181, 40 }, + { 202, 73 }, + { 20, 173 }, + { 146, 83 }, }, + { { 183, 9 }, + { 220, 73 }, + { 144, 237 }, + { 146, 59 }, }, + { { 183, 210 }, + { 237, 106 }, + { 75, 237 }, + { 86, 183 }, }, + { { 192, 234 }, + { 3, 171 }, + { 87, 3 }, + { 213, 192 }, }, + { { 192, 25 }, + { 48, 137 }, + { 152, 3 }, + { 145, 12 }, }, + { { 192, 253 }, + { 50, 187 }, + { 191, 3 }, + { 221, 76 }, }, + { { 200, 211 }, + { 49, 174 }, + { 203, 19 }, + { 117, 140 }, }, + { { 202, 90 }, + { 37, 173 }, + { 90, 83 }, + { 181, 164 }, }, + { { 193, 77 }, + { 80, 185 }, + { 178, 131 }, + { 157, 10 }, }, + { { 201, 180 }, + { 98, 158 }, + { 45, 147 }, + { 121, 70 }, }, + { { 193, 87 }, + { 113, 184 }, + { 234, 131 }, + { 29, 142 }, }, + { { 195, 152 }, + { 100, 139 }, + { 25, 195 }, + { 209, 38 }, }, + { { 195, 29 }, + { 116, 153 }, + { 184, 195 }, + { 153, 46 }, }, + { { 216, 128 }, + { 128, 142 }, + { 1, 27 }, + { 113, 1 }, }, + { { 216, 239 }, + { 147, 191 }, + { 247, 27 }, + { 253, 201 }, }, + { { 218, 43 }, + { 151, 141 }, + { 212, 91 }, + { 177, 233 }, }, + { { 208, 30 }, + { 161, 153 }, + { 120, 11 }, + { 153, 133 }, }, + { { 209, 5 }, + { 208, 152 }, + { 160, 139 }, + { 25, 11 }, }, + { { 211, 173 }, + { 214, 155 }, + { 181, 203 }, + { 217, 107 }, }, + { { 219, 167 }, + { 215, 158 }, + { 229, 219 }, + { 121, 235 }, }, + { { 196, 201 }, + { 16, 235 }, + { 147, 35 }, + { 215, 8 }, }, + { { 204, 120 }, + { 34, 237 }, + { 30, 51 }, + { 183, 68 }, }, + { { 205, 69 }, + { 80, 252 }, + { 162, 179 }, + { 63, 10 }, }, + { { 197, 11 }, + { 81, 201 }, + { 208, 163 }, + { 147, 138 }, }, + { { 207, 207 }, + { 85, 255 }, + { 243, 243 }, + { 255, 170 }, }, + { { 220, 172 }, + { 130, 223 }, + { 53, 59 }, + { 251, 65 }, }, + { { 212, 2 }, + { 129, 200 }, + { 64, 43 }, + { 19, 129 }, }, + { { 220, 99 }, + { 147, 236 }, + { 198, 59 }, + { 55, 201 }, }, + { { 212, 39 }, + { 147, 216 }, + { 228, 43 }, + { 27, 201 }, }, + { { 212, 245 }, + { 178, 250 }, + { 175, 43 }, + { 95, 77 }, }, + { { 214, 120 }, + { 166, 233 }, + { 30, 107 }, + { 151, 101 }, }, + { { 222, 184 }, + { 166, 207 }, + { 29, 123 }, + { 243, 101 }, }, + { { 221, 230 }, + { 195, 254 }, + { 103, 187 }, + { 127, 195 }, }, + { { 213, 93 }, + { 240, 249 }, + { 186, 171 }, + { 159, 15 }, }, + { { 221, 189 }, + { 242, 223 }, + { 189, 187 }, + { 251, 79 }, }, + { { 223, 29 }, + { 244, 221 }, + { 184, 251 }, + { 187, 47 }, }, + { { 226, 202 }, + { 13, 171 }, + { 83, 71 }, + { 213, 176 }, }, + { { 234, 107 }, + { 31, 173 }, + { 214, 87 }, + { 181, 248 }, }, + { { 224, 180 }, + { 42, 154 }, + { 45, 7 }, + { 89, 84 }, }, + { { 226, 56 }, + { 46, 137 }, + { 28, 71 }, + { 145, 116 }, }, + { { 226, 212 }, + { 44, 186 }, + { 43, 71 }, + { 93, 52 }, }, + { { 227, 34 }, + { 79, 136 }, + { 68, 199 }, + { 17, 242 }, }, + { { 225, 216 }, + { 104, 171 }, + { 27, 135 }, + { 213, 22 }, }, + { { 240, 3 }, + { 153, 136 }, + { 192, 15 }, + { 17, 153 }, }, + { { 242, 204 }, + { 140, 187 }, + { 51, 79 }, + { 221, 49 }, }, + { { 248, 246 }, + { 171, 190 }, + { 111, 31 }, + { 125, 213 }, }, + { { 241, 73 }, + { 216, 169 }, + { 146, 143 }, + { 149, 27 }, }, + { { 243, 234 }, + { 207, 171 }, + { 87, 207 }, + { 213, 243 }, }, + { { 241, 156 }, + { 232, 155 }, + { 57, 143 }, + { 217, 23 }, }, + { { 249, 245 }, + { 250, 190 }, + { 175, 159 }, + { 125, 95 }, }, + { { 241, 59 }, + { 251, 137 }, + { 220, 143 }, + { 145, 223 }, }, + { { 236, 141 }, + { 24, 223 }, + { 177, 55 }, + { 251, 24 }, }, + { { 238, 201 }, + { 28, 239 }, + { 147, 119 }, + { 247, 56 }, }, + { { 230, 15 }, + { 29, 217 }, + { 240, 103 }, + { 155, 184 }, }, + { { 228, 247 }, + { 59, 250 }, + { 239, 39 }, + { 95, 220 }, }, + { { 231, 96 }, + { 78, 232 }, + { 6, 231 }, + { 23, 114 }, }, + { { 239, 232 }, + { 78, 239 }, + { 23, 247 }, + { 247, 114 }, }, + { { 237, 178 }, + { 107, 206 }, + { 77, 183 }, + { 115, 214 }, }, + { { 229, 21 }, + { 120, 216 }, + { 168, 167 }, + { 27, 30 }, }, + { { 239, 209 }, + { 124, 238 }, + { 139, 247 }, + { 119, 62 }, }, + { { 244, 134 }, + { 137, 218 }, + { 97, 47 }, + { 91, 145 }, }, + { { 252, 1 }, + { 152, 204 }, + { 128, 63 }, + { 51, 25 }, }, + { { 246, 195 }, + { 157, 234 }, + { 195, 111 }, + { 87, 185 }, }, + { { 244, 124 }, + { 170, 249 }, + { 62, 47 }, + { 159, 85 }, }, + { { 252, 147 }, + { 185, 206 }, + { 201, 63 }, + { 115, 157 }, }, + { { 245, 66 }, + { 201, 232 }, + { 66, 175 }, + { 23, 147 }, }, + { { 253, 152 }, + { 232, 207 }, + { 25, 191 }, + { 243, 23 }, }, + { { 245, 61 }, + { 250, 217 }, + { 188, 175 }, + { 155, 95 }, }, + { { 2, 189 }, + { 54, 19 }, + { 189, 64 }, + { 200, 108 }, }, + { { 0, 225 }, + { 18, 34 }, + { 135, 0 }, + { 68, 72 }, }, + { { 2, 226 }, + { 7, 34 }, + { 71, 64 }, + { 68, 224 }, }, + { { 2, 174 }, + { 7, 19 }, + { 117, 64 }, + { 200, 224 }, }, + { { 8, 120 }, + { 34, 37 }, + { 30, 16 }, + { 164, 68 }, }, + { { 0, 116 }, + { 34, 48 }, + { 46, 0 }, + { 12, 68 }, }, + { { 8, 158 }, + { 33, 23 }, + { 121, 16 }, + { 232, 132 }, }, + { { 8, 209 }, + { 48, 38 }, + { 139, 16 }, + { 100, 12 }, }, + { { 8, 125 }, + { 50, 53 }, + { 190, 16 }, + { 172, 76 }, }, + { { 10, 50 }, + { 39, 4 }, + { 76, 80 }, + { 32, 228 }, }, + { { 10, 222 }, + { 37, 55 }, + { 123, 80 }, + { 236, 164 }, }, + { { 2, 81 }, + { 52, 32 }, + { 138, 64 }, + { 4, 44 }, }, + { { 1, 162 }, + { 67, 2 }, + { 69, 128 }, + { 64, 194 }, }, + { { 3, 128 }, + { 68, 2 }, + { 1, 192 }, + { 64, 34 }, }, + { { 11, 131 }, + { 85, 6 }, + { 193, 208 }, + { 96, 170 }, }, + { { 11, 75 }, + { 85, 37 }, + { 210, 208 }, + { 164, 170 }, }, + { { 11, 39 }, + { 87, 20 }, + { 228, 208 }, + { 40, 234 }, }, + { { 11, 239 }, + { 87, 55 }, + { 247, 208 }, + { 236, 234 }, }, + { { 9, 182 }, + { 99, 22 }, + { 109, 144 }, + { 104, 198 }, }, + { { 9, 89 }, + { 112, 37 }, + { 154, 144 }, + { 164, 14 }, }, + { { 9, 147 }, + { 113, 6 }, + { 201, 144 }, + { 96, 142 }, }, + { { 11, 248 }, + { 102, 39 }, + { 31, 208 }, + { 228, 102 }, }, + { { 3, 217 }, + { 116, 35 }, + { 155, 192 }, + { 196, 46 }, }, + { { 3, 241 }, + { 118, 34 }, + { 143, 192 }, + { 68, 110 }, }, + { { 16, 196 }, + { 128, 50 }, + { 35, 8 }, + { 76, 1 }, }, + { { 24, 171 }, + { 147, 7 }, + { 213, 24 }, + { 224, 201 }, }, + { { 26, 160 }, + { 134, 6 }, + { 5, 88 }, + { 96, 97 }, }, + { { 26, 4 }, + { 132, 20 }, + { 32, 88 }, + { 40, 33 }, }, + { { 26, 108 }, + { 134, 53 }, + { 54, 88 }, + { 172, 97 }, }, + { { 26, 174 }, + { 135, 23 }, + { 117, 88 }, + { 232, 225 }, }, + { { 18, 137 }, + { 148, 3 }, + { 145, 72 }, + { 192, 41 }, }, + { { 16, 23 }, + { 177, 16 }, + { 232, 8 }, + { 8, 141 }, }, + { { 26, 243 }, + { 183, 38 }, + { 207, 88 }, + { 100, 237 }, }, + { { 25, 64 }, + { 192, 36 }, + { 2, 152 }, + { 36, 3 }, }, + { { 17, 2 }, + { 193, 0 }, + { 64, 136 }, + { 0, 131 }, }, + { { 17, 43 }, + { 211, 1 }, + { 212, 136 }, + { 128, 203 }, }, + { { 17, 207 }, + { 209, 51 }, + { 243, 136 }, + { 204, 139 }, }, + { { 27, 34 }, + { 199, 4 }, + { 68, 216 }, + { 32, 227 }, }, + { { 19, 46 }, + { 199, 17 }, + { 116, 200 }, + { 136, 227 }, }, + { { 17, 21 }, + { 240, 16 }, + { 168, 136 }, + { 8, 15 }, }, + { { 19, 187 }, + { 247, 3 }, + { 221, 200 }, + { 192, 239 }, }, + { { 12, 32 }, + { 2, 68 }, + { 4, 48 }, + { 34, 64 }, }, + { { 12, 201 }, + { 16, 103 }, + { 147, 48 }, + { 230, 8 }, }, + { { 12, 220 }, + { 32, 119 }, + { 59, 48 }, + { 238, 4 }, }, + { { 12, 54 }, + { 35, 84 }, + { 108, 48 }, + { 42, 196 }, }, + { { 6, 20 }, + { 36, 80 }, + { 40, 96 }, + { 10, 36 }, }, + { { 6, 114 }, + { 39, 96 }, + { 78, 96 }, + { 6, 228 }, }, + { { 13, 97 }, + { 82, 100 }, + { 134, 176 }, + { 38, 74 }, }, + { { 5, 13 }, + { 80, 81 }, + { 176, 160 }, + { 138, 10 }, }, + { { 13, 143 }, + { 81, 87 }, + { 241, 176 }, + { 234, 138 }, }, + { { 15, 224 }, + { 70, 102 }, + { 7, 240 }, + { 102, 98 }, }, + { { 15, 73 }, + { 84, 101 }, + { 146, 240 }, + { 166, 42 }, }, + { { 7, 133 }, + { 84, 82 }, + { 161, 224 }, + { 74, 42 }, }, + { { 5, 144 }, + { 96, 66 }, + { 9, 160 }, + { 66, 6 }, }, + { { 13, 51 }, + { 115, 68 }, + { 204, 176 }, + { 34, 206 }, }, + { { 15, 150 }, + { 101, 86 }, + { 105, 240 }, + { 106, 166 }, }, + { { 15, 118 }, + { 103, 116 }, + { 110, 240 }, + { 46, 230 }, }, + { { 20, 96 }, + { 130, 96 }, + { 6, 40 }, + { 6, 65 }, }, + { { 28, 141 }, + { 144, 87 }, + { 177, 56 }, + { 234, 9 }, }, + { { 20, 218 }, + { 161, 99 }, + { 91, 40 }, + { 198, 133 }, }, + { { 28, 115 }, + { 179, 100 }, + { 206, 56 }, + { 38, 205 }, }, + { { 30, 148 }, + { 164, 86 }, + { 41, 120 }, + { 106, 37 }, }, + { { 30, 186 }, + { 167, 71 }, + { 93, 120 }, + { 226, 229 }, }, + { { 22, 217 }, + { 180, 99 }, + { 155, 104 }, + { 198, 45 }, }, + { { 30, 61 }, + { 182, 85 }, + { 188, 120 }, + { 170, 109 }, }, + { { 22, 251 }, + { 183, 99 }, + { 223, 104 }, + { 198, 237 }, }, + { { 29, 233 }, + { 210, 103 }, + { 151, 184 }, + { 230, 75 }, }, + { { 29, 254 }, + { 227, 119 }, + { 127, 184 }, + { 238, 199 }, }, + { { 31, 159 }, + { 245, 87 }, + { 249, 248 }, + { 234, 175 }, }, + { { 40, 139 }, + { 25, 7 }, + { 209, 20 }, + { 224, 152 }, }, + { { 32, 175 }, + { 27, 19 }, + { 245, 4 }, + { 200, 216 }, }, + { { 34, 14 }, + { 13, 17 }, + { 112, 68 }, + { 136, 176 }, }, + { { 34, 169 }, + { 30, 3 }, + { 149, 68 }, + { 192, 120 }, }, + { { 42, 141 }, + { 28, 23 }, + { 177, 84 }, + { 232, 56 }, }, + { { 42, 163 }, + { 31, 6 }, + { 197, 84 }, + { 96, 248 }, }, + { { 42, 239 }, + { 31, 55 }, + { 247, 84 }, + { 236, 248 }, }, + { { 40, 144 }, + { 40, 6 }, + { 9, 20 }, + { 96, 20 }, }, + { { 40, 59 }, + { 59, 5 }, + { 220, 20 }, + { 160, 220 }, }, + { { 42, 88 }, + { 44, 37 }, + { 26, 84 }, + { 164, 52 }, }, + { { 34, 51 }, + { 63, 0 }, + { 204, 68 }, + { 0, 252 }, }, + { { 33, 160 }, + { 74, 2 }, + { 5, 132 }, + { 64, 82 }, }, + { { 33, 2 }, + { 73, 0 }, + { 64, 132 }, + { 0, 146 }, }, + { { 33, 165 }, + { 90, 18 }, + { 165, 132 }, + { 72, 90 }, }, + { { 33, 199 }, + { 89, 50 }, + { 227, 132 }, + { 76, 154 }, }, + { { 43, 3 }, + { 93, 4 }, + { 192, 212 }, + { 32, 186 }, }, + { { 35, 103 }, + { 95, 48 }, + { 230, 196 }, + { 12, 250 }, }, + { { 41, 48 }, + { 106, 4 }, + { 12, 148 }, + { 32, 86 }, }, + { { 41, 210 }, + { 105, 38 }, + { 75, 148 }, + { 100, 150 }, }, + { { 43, 25 }, + { 124, 5 }, + { 152, 212 }, + { 160, 62 }, }, + { { 43, 155 }, + { 125, 7 }, + { 217, 212 }, + { 224, 190 }, }, + { { 43, 151 }, + { 125, 22 }, + { 233, 212 }, + { 104, 190 }, }, + { { 56, 40 }, + { 138, 5 }, + { 20, 28 }, + { 160, 81 }, }, + { { 56, 165 }, + { 154, 22 }, + { 165, 28 }, + { 104, 89 }, }, + { { 58, 134 }, + { 141, 22 }, + { 97, 92 }, + { 104, 177 }, }, + { { 50, 1 }, + { 156, 0 }, + { 128, 76 }, + { 0, 57 }, }, + { { 56, 159 }, + { 185, 23 }, + { 249, 28 }, + { 232, 157 }, }, + { { 50, 210 }, + { 173, 34 }, + { 75, 76 }, + { 68, 181 }, }, + { { 58, 153 }, + { 188, 7 }, + { 153, 92 }, + { 224, 61 }, }, + { { 58, 213 }, + { 188, 54 }, + { 171, 92 }, + { 108, 61 }, }, + { { 57, 232 }, + { 202, 39 }, + { 23, 156 }, + { 228, 83 }, }, + { { 59, 193 }, + { 220, 38 }, + { 131, 220 }, + { 100, 59 }, }, + { { 51, 67 }, + { 221, 32 }, + { 194, 204 }, + { 4, 187 }, }, + { { 59, 231 }, + { 223, 54 }, + { 231, 220 }, + { 108, 251 }, }, + { { 49, 154 }, + { 233, 3 }, + { 89, 140 }, + { 192, 151 }, }, + { { 51, 144 }, + { 236, 2 }, + { 9, 204 }, + { 64, 55 }, }, + { { 59, 158 }, + { 237, 23 }, + { 121, 220 }, + { 232, 183 }, }, + { { 36, 196 }, + { 8, 114 }, + { 35, 36 }, + { 78, 16 }, }, + { { 44, 74 }, + { 9, 101 }, + { 82, 52 }, + { 166, 144 }, }, + { { 44, 173 }, + { 26, 87 }, + { 181, 52 }, + { 234, 88 }, }, + { { 44, 207 }, + { 25, 119 }, + { 243, 52 }, + { 238, 152 }, }, + { { 44, 103 }, + { 27, 116 }, + { 230, 52 }, + { 46, 216 }, }, + { { 38, 234 }, + { 15, 99 }, + { 87, 100 }, + { 198, 240 }, }, + { { 46, 229 }, + { 30, 118 }, + { 167, 116 }, + { 110, 120 }, }, + { { 44, 112 }, + { 42, 100 }, + { 14, 52 }, + { 38, 84 }, }, + { { 46, 18 }, + { 45, 68 }, + { 72, 116 }, + { 34, 180 }, }, + { { 46, 209 }, + { 60, 102 }, + { 139, 116 }, + { 102, 60 }, }, + { { 46, 57 }, + { 62, 69 }, + { 156, 116 }, + { 162, 124 }, }, + { { 37, 100 }, + { 74, 112 }, + { 38, 164 }, + { 14, 82 }, }, + { { 37, 231 }, + { 91, 114 }, + { 231, 164 }, + { 78, 218 }, }, + { { 47, 204 }, + { 76, 119 }, + { 51, 244 }, + { 238, 50 }, }, + { { 45, 188 }, + { 106, 87 }, + { 61, 180 }, + { 234, 86 }, }, + { { 45, 113 }, + { 122, 100 }, + { 142, 180 }, + { 38, 94 }, }, + { { 37, 213 }, + { 120, 114 }, + { 171, 164 }, + { 78, 30 }, }, + { { 37, 155 }, + { 121, 67 }, + { 217, 164 }, + { 194, 158 }, }, + { { 39, 16 }, + { 108, 64 }, + { 8, 228 }, + { 2, 54 }, }, + { { 47, 124 }, + { 110, 117 }, + { 62, 244 }, + { 174, 118 }, }, + { { 39, 242 }, + { 111, 98 }, + { 79, 228 }, + { 70, 246 }, }, + { { 39, 58 }, + { 111, 65 }, + { 92, 228 }, + { 130, 246 }, }, + { { 47, 182 }, + { 111, 86 }, + { 109, 244 }, + { 106, 246 }, }, + { { 39, 211 }, + { 125, 98 }, + { 203, 228 }, + { 70, 190 }, }, + { { 47, 179 }, + { 127, 70 }, + { 205, 244 }, + { 98, 254 }, }, + { { 39, 31 }, + { 125, 81 }, + { 248, 228 }, + { 138, 190 }, }, + { { 60, 75 }, + { 153, 101 }, + { 210, 60 }, + { 166, 153 }, }, + { { 54, 192 }, + { 140, 98 }, + { 3, 108 }, + { 70, 49 }, }, + { { 54, 238 }, + { 143, 115 }, + { 119, 108 }, + { 206, 241 }, }, + { { 62, 233 }, + { 158, 103 }, + { 151, 124 }, + { 230, 121 }, }, + { { 52, 184 }, + { 170, 67 }, + { 29, 44 }, + { 194, 85 }, }, + { { 60, 20 }, + { 168, 84 }, + { 40, 60 }, + { 42, 21 }, }, + { { 60, 82 }, + { 169, 100 }, + { 74, 60 }, + { 38, 149 }, }, + { { 52, 114 }, + { 171, 96 }, + { 78, 44 }, + { 6, 213 }, }, + { { 52, 126 }, + { 171, 113 }, + { 126, 44 }, + { 142, 213 }, }, + { { 52, 191 }, + { 187, 83 }, + { 253, 44 }, + { 202, 221 }, }, + { { 62, 113 }, + { 190, 100 }, + { 142, 124 }, + { 38, 125 }, }, + { { 62, 83 }, + { 189, 100 }, + { 202, 124 }, + { 38, 189 }, }, + { { 61, 140 }, + { 200, 87 }, + { 49, 188 }, + { 234, 19 }, }, + { { 53, 162 }, + { 203, 66 }, + { 69, 172 }, + { 66, 211 }, }, + { { 53, 46 }, + { 203, 81 }, + { 116, 172 }, + { 138, 211 }, }, + { { 53, 45 }, + { 218, 81 }, + { 180, 172 }, + { 138, 91 }, }, + { { 55, 172 }, + { 206, 83 }, + { 53, 236 }, + { 202, 115 }, }, + { { 53, 112 }, + { 234, 96 }, + { 14, 172 }, + { 6, 87 }, }, + { { 55, 250 }, + { 239, 99 }, + { 95, 236 }, + { 198, 247 }, }, + { { 63, 241 }, + { 254, 102 }, + { 143, 252 }, + { 102, 127 }, }, + { { 63, 219 }, + { 253, 103 }, + { 219, 252 }, + { 230, 191 }, }, + { { 72, 196 }, + { 0, 182 }, + { 35, 18 }, + { 109, 0 }, }, + { { 72, 233 }, + { 18, 167 }, + { 151, 18 }, + { 229, 72 }, }, + { { 74, 194 }, + { 5, 166 }, + { 67, 82 }, + { 101, 160 }, }, + { { 74, 65 }, + { 20, 164 }, + { 130, 82 }, + { 37, 40 }, }, + { { 66, 235 }, + { 23, 163 }, + { 215, 66 }, + { 197, 232 }, }, + { { 72, 19 }, + { 49, 132 }, + { 200, 18 }, + { 33, 140 }, }, + { { 74, 216 }, + { 36, 167 }, + { 27, 82 }, + { 229, 36 }, }, + { { 66, 253 }, + { 54, 179 }, + { 191, 66 }, + { 205, 108 }, }, + { { 74, 23 }, + { 53, 148 }, + { 232, 82 }, + { 41, 172 }, }, + { { 73, 99 }, + { 83, 164 }, + { 198, 146 }, + { 37, 202 }, }, + { { 67, 110 }, + { 71, 177 }, + { 118, 194 }, + { 141, 226 }, }, + { { 65, 58 }, + { 99, 129 }, + { 92, 130 }, + { 129, 198 }, }, + { { 73, 177 }, + { 114, 134 }, + { 141, 146 }, + { 97, 78 }, }, + { { 65, 61 }, + { 114, 145 }, + { 188, 130 }, + { 137, 78 }, }, + { { 75, 146 }, + { 101, 134 }, + { 73, 210 }, + { 97, 166 }, }, + { { 75, 155 }, + { 117, 135 }, + { 217, 210 }, + { 225, 174 }, }, + { { 67, 63 }, + { 119, 145 }, + { 252, 194 }, + { 137, 238 }, }, + { { 88, 34 }, + { 131, 132 }, + { 68, 26 }, + { 33, 193 }, }, + { { 80, 170 }, + { 131, 131 }, + { 85, 10 }, + { 193, 193 }, }, + { { 88, 39 }, + { 147, 148 }, + { 228, 26 }, + { 41, 201 }, }, + { { 82, 200 }, + { 132, 163 }, + { 19, 74 }, + { 197, 33 }, }, + { { 82, 132 }, + { 132, 146 }, + { 33, 74 }, + { 73, 33 }, }, + { { 82, 10 }, + { 133, 129 }, + { 80, 74 }, + { 129, 161 }, }, + { { 90, 15 }, + { 149, 149 }, + { 240, 90 }, + { 169, 169 }, }, + { { 88, 152 }, + { 160, 135 }, + { 25, 26 }, + { 225, 5 }, }, + { { 88, 92 }, + { 160, 181 }, + { 58, 26 }, + { 173, 5 }, }, + { { 80, 219 }, + { 177, 163 }, + { 219, 10 }, + { 197, 141 }, }, + { { 80, 247 }, + { 179, 178 }, + { 239, 10 }, + { 77, 205 }, }, + { { 90, 244 }, + { 166, 182 }, + { 47, 90 }, + { 109, 101 }, }, + { { 81, 236 }, + { 194, 179 }, + { 55, 138 }, + { 205, 67 }, }, + { { 81, 66 }, + { 193, 160 }, + { 66, 138 }, + { 5, 131 }, }, + { { 81, 13 }, + { 208, 145 }, + { 176, 138 }, + { 137, 11 }, }, + { { 91, 3 }, + { 213, 132 }, + { 192, 218 }, + { 33, 171 }, }, + { { 83, 235 }, + { 215, 163 }, + { 215, 202 }, + { 197, 235 }, }, + { { 81, 118 }, + { 227, 176 }, + { 110, 138 }, + { 13, 199 }, }, + { { 89, 113 }, + { 242, 164 }, + { 142, 154 }, + { 37, 79 }, }, + { { 81, 147 }, + { 241, 130 }, + { 201, 138 }, + { 65, 143 }, }, + { { 83, 249 }, + { 246, 163 }, + { 159, 202 }, + { 197, 111 }, }, + { { 91, 179 }, + { 247, 134 }, + { 205, 218 }, + { 97, 239 }, }, + { { 83, 151 }, + { 245, 146 }, + { 233, 202 }, + { 73, 175 }, }, + { { 76, 76 }, + { 0, 245 }, + { 50, 50 }, + { 175, 0 }, }, + { { 68, 75 }, + { 17, 225 }, + { 210, 34 }, + { 135, 136 }, }, + { { 76, 35 }, + { 19, 196 }, + { 196, 50 }, + { 35, 200 }, }, + { { 70, 140 }, + { 4, 211 }, + { 49, 98 }, + { 203, 32 }, }, + { { 78, 39 }, + { 23, 212 }, + { 228, 114 }, + { 43, 232 }, }, + { { 70, 144 }, + { 36, 194 }, + { 9, 98 }, + { 67, 36 }, }, + { { 78, 212 }, + { 36, 246 }, + { 43, 114 }, + { 111, 36 }, }, + { { 69, 206 }, + { 65, 243 }, + { 115, 162 }, + { 207, 130 }, }, + { { 69, 229 }, + { 82, 242 }, + { 167, 162 }, + { 79, 74 }, }, + { { 69, 39 }, + { 83, 208 }, + { 228, 162 }, + { 11, 202 }, }, + { { 79, 193 }, + { 84, 230 }, + { 131, 242 }, + { 103, 42 }, }, + { { 71, 5 }, + { 84, 208 }, + { 160, 226 }, + { 11, 42 }, }, + { { 69, 52 }, + { 98, 208 }, + { 44, 162 }, + { 11, 70 }, }, + { { 69, 114 }, + { 99, 224 }, + { 78, 162 }, + { 7, 198 }, }, + { { 92, 200 }, + { 128, 231 }, + { 19, 58 }, + { 231, 1 }, }, + { { 92, 14 }, + { 129, 213 }, + { 112, 58 }, + { 171, 129 }, }, + { { 84, 235 }, + { 147, 227 }, + { 215, 42 }, + { 199, 201 }, }, + { { 86, 137 }, + { 148, 195 }, + { 145, 106 }, + { 195, 41 }, }, + { { 86, 67 }, + { 149, 224 }, + { 194, 106 }, + { 7, 169 }, }, + { { 94, 231 }, + { 151, 246 }, + { 231, 122 }, + { 111, 233 }, }, + { { 92, 112 }, + { 162, 228 }, + { 14, 58 }, + { 39, 69 }, }, + { { 84, 178 }, + { 163, 194 }, + { 77, 42 }, + { 67, 197 }, }, + { { 94, 121 }, + { 182, 229 }, + { 158, 122 }, + { 167, 109 }, }, + { { 86, 243 }, + { 183, 226 }, + { 207, 106 }, + { 71, 237 }, }, + { { 93, 163 }, + { 211, 198 }, + { 197, 186 }, + { 99, 203 }, }, + { { 93, 242 }, + { 227, 230 }, + { 79, 186 }, + { 103, 199 }, }, + { { 85, 29 }, + { 240, 209 }, + { 184, 170 }, + { 139, 15 }, }, + { { 93, 157 }, + { 240, 215 }, + { 185, 186 }, + { 235, 15 }, }, + { { 87, 252 }, + { 230, 243 }, + { 63, 234 }, + { 207, 103 }, }, + { { 87, 210 }, + { 229, 226 }, + { 75, 234 }, + { 71, 167 }, }, + { { 95, 115 }, + { 247, 228 }, + { 206, 250 }, + { 39, 239 }, }, + { { 104, 45 }, + { 26, 149 }, + { 180, 22 }, + { 169, 88 }, }, + { { 104, 195 }, + { 25, 166 }, + { 195, 22 }, + { 101, 152 }, }, + { { 104, 135 }, + { 25, 150 }, + { 225, 22 }, + { 105, 152 }, }, + { { 106, 74 }, + { 13, 165 }, + { 82, 86 }, + { 165, 176 }, }, + { { 98, 105 }, + { 30, 161 }, + { 150, 70 }, + { 133, 120 }, }, + { { 96, 185 }, + { 58, 131 }, + { 157, 6 }, + { 193, 92 }, }, + { { 104, 255 }, + { 59, 183 }, + { 255, 22 }, + { 237, 220 }, }, + { { 106, 220 }, + { 44, 183 }, + { 59, 86 }, + { 237, 52 }, }, + { { 106, 218 }, + { 45, 167 }, + { 91, 86 }, + { 229, 180 }, }, + { { 106, 62 }, + { 47, 149 }, + { 124, 86 }, + { 169, 244 }, }, + { { 106, 81 }, + { 60, 164 }, + { 138, 86 }, + { 37, 60 }, }, + { { 106, 49 }, + { 62, 132 }, + { 140, 86 }, + { 33, 124 }, }, + { { 98, 215 }, + { 61, 178 }, + { 235, 70 }, + { 77, 188 }, }, + { { 97, 204 }, + { 72, 179 }, + { 51, 134 }, + { 205, 18 }, }, + { { 107, 130 }, + { 77, 134 }, + { 65, 214 }, + { 97, 178 }, }, + { { 107, 227 }, + { 95, 166 }, + { 199, 214 }, + { 101, 250 }, }, + { { 105, 58 }, + { 107, 133 }, + { 92, 150 }, + { 161, 214 }, }, + { { 97, 158 }, + { 105, 147 }, + { 121, 134 }, + { 201, 150 }, }, + { { 97, 149 }, + { 120, 146 }, + { 169, 134 }, + { 73, 30 }, }, + { { 97, 117 }, + { 122, 176 }, + { 174, 134 }, + { 13, 94 }, }, + { { 105, 95 }, + { 121, 181 }, + { 250, 150 }, + { 173, 158 }, }, + { { 105, 55 }, + { 123, 148 }, + { 236, 150 }, + { 41, 222 }, }, + { { 99, 218 }, + { 109, 163 }, + { 91, 198 }, + { 197, 182 }, }, + { { 112, 2 }, + { 137, 128 }, + { 64, 14 }, + { 1, 145 }, }, + { { 120, 99 }, + { 155, 164 }, + { 198, 30 }, + { 37, 217 }, }, + { { 112, 79 }, + { 153, 177 }, + { 242, 14 }, + { 141, 153 }, }, + { { 114, 202 }, + { 141, 163 }, + { 83, 78 }, + { 197, 177 }, }, + { { 122, 173 }, + { 158, 151 }, + { 181, 94 }, + { 233, 121 }, }, + { { 112, 123 }, + { 187, 161 }, + { 222, 14 }, + { 133, 221 }, }, + { { 122, 20 }, + { 172, 148 }, + { 40, 94 }, + { 41, 53 }, }, + { { 122, 249 }, + { 190, 167 }, + { 159, 94 }, + { 229, 125 }, }, + { { 122, 211 }, + { 189, 166 }, + { 203, 94 }, + { 101, 189 }, }, + { { 122, 187 }, + { 191, 135 }, + { 221, 94 }, + { 225, 253 }, }, + { { 121, 226 }, + { 203, 166 }, + { 71, 158 }, + { 101, 211 }, }, + { { 113, 41 }, + { 218, 129 }, + { 148, 142 }, + { 129, 91 }, }, + { { 123, 103 }, + { 223, 180 }, + { 230, 222 }, + { 45, 251 }, }, + { { 113, 208 }, + { 232, 162 }, + { 11, 142 }, + { 69, 23 }, }, + { { 121, 57 }, + { 250, 133 }, + { 156, 158 }, + { 161, 95 }, }, + { { 115, 48 }, + { 238, 128 }, + { 12, 206 }, + { 1, 119 }, }, + { { 115, 185 }, + { 254, 131 }, + { 157, 206 }, + { 193, 127 }, }, + { { 115, 83 }, + { 253, 160 }, + { 202, 206 }, + { 5, 191 }, }, + { { 115, 255 }, + { 255, 179 }, + { 255, 206 }, + { 205, 255 }, }, + { { 108, 136 }, + { 8, 199 }, + { 17, 54 }, + { 227, 16 }, }, + { { 100, 9 }, + { 24, 193 }, + { 144, 38 }, + { 131, 24 }, }, + { { 108, 67 }, + { 25, 228 }, + { 194, 54 }, + { 39, 152 }, }, + { { 102, 6 }, + { 13, 208 }, + { 96, 102 }, + { 11, 176 }, }, + { { 102, 131 }, + { 29, 194 }, + { 193, 102 }, + { 67, 184 }, }, + { { 100, 176 }, + { 42, 194 }, + { 13, 38 }, + { 67, 84 }, }, + { { 100, 218 }, + { 41, 227 }, + { 91, 38 }, + { 199, 148 }, }, + { { 110, 159 }, + { 61, 215 }, + { 249, 118 }, + { 235, 188 }, }, + { { 103, 200 }, + { 76, 227 }, + { 19, 230 }, + { 199, 50 }, }, + { { 111, 238 }, + { 79, 247 }, + { 119, 246 }, + { 239, 242 }, }, + { { 109, 59 }, + { 123, 197 }, + { 220, 182 }, + { 163, 222 }, }, + { { 111, 210 }, + { 109, 230 }, + { 75, 246 }, + { 103, 182 }, }, + { { 116, 128 }, + { 136, 194 }, + { 1, 46 }, + { 67, 17 }, }, + { { 124, 171 }, + { 155, 199 }, + { 213, 62 }, + { 227, 217 }, }, + { { 126, 104 }, + { 142, 229 }, + { 22, 126 }, + { 167, 113 }, }, + { { 126, 2 }, + { 141, 196 }, + { 64, 126 }, + { 35, 177 }, }, + { { 124, 156 }, + { 168, 215 }, + { 57, 62 }, + { 235, 21 }, }, + { { 116, 54 }, + { 171, 208 }, + { 108, 46 }, + { 11, 213 }, }, + { { 124, 17 }, + { 184, 196 }, + { 136, 62 }, + { 35, 29 }, }, + { { 126, 222 }, + { 173, 247 }, + { 123, 126 }, + { 239, 181 }, }, + { { 126, 182 }, + { 175, 214 }, + { 109, 126 }, + { 107, 245 }, }, + { { 118, 219 }, + { 189, 227 }, + { 219, 110 }, + { 199, 189 }, }, + { { 125, 196 }, + { 200, 246 }, + { 35, 190 }, + { 111, 19 }, }, + { { 125, 138 }, + { 201, 199 }, + { 81, 190 }, + { 227, 147 }, }, + { { 117, 109 }, + { 218, 241 }, + { 182, 174 }, + { 143, 91 }, }, + { { 119, 136 }, + { 204, 195 }, + { 17, 238 }, + { 195, 51 }, }, + { { 119, 32 }, + { 206, 192 }, + { 4, 238 }, + { 3, 115 }, }, + { { 119, 65 }, + { 220, 224 }, + { 130, 238 }, + { 7, 59 }, }, + { { 117, 56 }, + { 234, 193 }, + { 28, 174 }, + { 131, 87 }, }, + { { 117, 190 }, + { 235, 211 }, + { 125, 174 }, + { 203, 215 }, }, + { { 125, 155 }, + { 249, 199 }, + { 217, 190 }, + { 227, 159 }, }, + { { 119, 87 }, + { 253, 240 }, + { 234, 238 }, + { 15, 191 }, }, + { { 136, 40 }, + { 2, 13 }, + { 20, 17 }, + { 176, 64 }, }, + { { 128, 172 }, + { 2, 27 }, + { 53, 1 }, + { 216, 64 }, }, + { { 136, 13 }, + { 16, 29 }, + { 176, 17 }, + { 184, 8 }, }, + { { 136, 103 }, + { 19, 60 }, + { 230, 17 }, + { 60, 200 }, }, + { { 130, 78 }, + { 5, 57 }, + { 114, 65 }, + { 156, 160 }, }, + { { 138, 161 }, + { 22, 14 }, + { 133, 81 }, + { 112, 104 }, }, + { { 130, 43 }, + { 23, 9 }, + { 212, 65 }, + { 144, 232 }, }, + { { 128, 24 }, + { 32, 9 }, + { 24, 1 }, + { 144, 4 }, }, + { { 136, 249 }, + { 50, 47 }, + { 159, 17 }, + { 244, 76 }, }, + { { 128, 157 }, + { 48, 27 }, + { 185, 1 }, + { 216, 12 }, }, + { { 138, 156 }, + { 36, 31 }, + { 57, 81 }, + { 248, 36 }, }, + { { 130, 49 }, + { 54, 8 }, + { 140, 65 }, + { 16, 108 }, }, + { { 138, 117 }, + { 54, 60 }, + { 174, 81 }, + { 60, 108 }, }, + { { 130, 151 }, + { 53, 26 }, + { 233, 65 }, + { 88, 172 }, }, + { { 129, 9 }, + { 80, 9 }, + { 144, 129 }, + { 144, 10 }, }, + { { 129, 235 }, + { 83, 43 }, + { 215, 129 }, + { 212, 202 }, }, + { { 129, 7 }, + { 81, 24 }, + { 224, 129 }, + { 24, 138 }, }, + { { 139, 40 }, + { 70, 13 }, + { 20, 209 }, + { 176, 98 }, }, + { { 139, 172 }, + { 70, 31 }, + { 53, 209 }, + { 248, 98 }, }, + { { 131, 46 }, + { 71, 25 }, + { 116, 193 }, + { 152, 226 }, }, + { { 131, 229 }, + { 86, 58 }, + { 167, 193 }, + { 92, 106 }, }, + { { 129, 80 }, + { 96, 40 }, + { 10, 129 }, + { 20, 6 }, }, + { { 137, 50 }, + { 99, 12 }, + { 76, 145 }, + { 48, 198 }, }, + { { 139, 122 }, + { 103, 45 }, + { 94, 209 }, + { 180, 230 }, }, + { { 139, 150 }, + { 101, 30 }, + { 105, 209 }, + { 120, 166 }, }, + { { 131, 125 }, + { 118, 57 }, + { 190, 193 }, + { 156, 110 }, }, + { { 144, 135 }, + { 145, 26 }, + { 225, 9 }, + { 88, 137 }, }, + { { 154, 252 }, + { 166, 63 }, + { 63, 89 }, + { 252, 101 }, }, + { { 146, 245 }, + { 182, 58 }, + { 175, 73 }, + { 92, 109 }, }, + { { 145, 170 }, + { 195, 11 }, + { 85, 137 }, + { 208, 195 }, }, + { { 147, 65 }, + { 212, 40 }, + { 130, 201 }, + { 20, 43 }, }, + { { 147, 37 }, + { 214, 24 }, + { 164, 201 }, + { 24, 107 }, }, + { { 155, 235 }, + { 215, 47 }, + { 215, 217 }, + { 244, 235 }, }, + { { 153, 52 }, + { 226, 28 }, + { 44, 153 }, + { 56, 71 }, }, + { { 145, 247 }, + { 243, 58 }, + { 239, 137 }, + { 92, 207 }, }, + { { 155, 218 }, + { 229, 47 }, + { 91, 217 }, + { 244, 167 }, }, + { { 147, 86 }, + { 229, 56 }, + { 106, 201 }, + { 28, 167 }, }, + { { 132, 66 }, + { 1, 104 }, + { 66, 33 }, + { 22, 128 }, }, + { { 140, 129 }, + { 16, 78 }, + { 129, 49 }, + { 114, 8 }, }, + { { 140, 79 }, + { 17, 125 }, + { 242, 49 }, + { 190, 136 }, }, + { { 134, 72 }, + { 4, 105 }, + { 18, 97 }, + { 150, 32 }, }, + { { 134, 166 }, + { 7, 90 }, + { 101, 97 }, + { 90, 224 }, }, + { { 142, 3 }, + { 21, 76 }, + { 192, 113 }, + { 50, 168 }, }, + { { 134, 227 }, + { 23, 106 }, + { 199, 97 }, + { 86, 232 }, }, + { { 134, 111 }, + { 23, 121 }, + { 246, 97 }, + { 158, 232 }, }, + { { 142, 175 }, + { 23, 95 }, + { 245, 113 }, + { 250, 232 }, }, + { { 132, 94 }, + { 33, 121 }, + { 122, 33 }, + { 158, 132 }, }, + { { 132, 119 }, + { 51, 120 }, + { 238, 33 }, + { 30, 204 }, }, + { { 134, 250 }, + { 39, 107 }, + { 95, 97 }, + { 214, 228 }, }, + { { 142, 30 }, + { 37, 93 }, + { 120, 113 }, + { 186, 164 }, }, + { { 142, 55 }, + { 55, 92 }, + { 236, 113 }, + { 58, 236 }, }, + { { 135, 10 }, + { 69, 73 }, + { 80, 225 }, + { 146, 162 }, }, + { { 143, 138 }, + { 69, 79 }, + { 81, 241 }, + { 242, 162 }, }, + { { 143, 38 }, + { 71, 92 }, + { 100, 241 }, + { 58, 226 }, }, + { { 135, 33 }, + { 86, 72 }, + { 132, 225 }, + { 18, 106 }, }, + { { 135, 13 }, + { 84, 89 }, + { 176, 225 }, + { 154, 42 }, }, + { { 133, 114 }, + { 99, 104 }, + { 78, 161 }, + { 22, 198 }, }, + { { 135, 62 }, + { 103, 89 }, + { 124, 225 }, + { 154, 230 }, }, + { { 156, 67 }, + { 145, 108 }, + { 194, 57 }, + { 54, 137 }, }, + { { 158, 97 }, + { 150, 108 }, + { 134, 121 }, + { 54, 105 }, }, + { { 148, 88 }, + { 160, 105 }, + { 26, 41 }, + { 150, 5 }, }, + { { 148, 248 }, + { 162, 107 }, + { 31, 41 }, + { 214, 69 }, }, + { { 156, 50 }, + { 163, 76 }, + { 76, 57 }, + { 50, 197 }, }, + { { 148, 118 }, + { 163, 120 }, + { 110, 41 }, + { 30, 197 }, }, + { { 148, 177 }, + { 178, 74 }, + { 141, 41 }, + { 82, 77 }, }, + { { 148, 221 }, + { 176, 123 }, + { 187, 41 }, + { 222, 13 }, }, + { { 148, 155 }, + { 177, 75 }, + { 217, 41 }, + { 210, 141 }, }, + { { 156, 219 }, + { 177, 111 }, + { 219, 57 }, + { 246, 141 }, }, + { { 158, 156 }, + { 164, 95 }, + { 57, 121 }, + { 250, 37 }, }, + { { 158, 210 }, + { 165, 110 }, + { 75, 121 }, + { 118, 165 }, }, + { { 150, 25 }, + { 180, 73 }, + { 152, 105 }, + { 146, 45 }, }, + { { 158, 177 }, + { 182, 78 }, + { 141, 121 }, + { 114, 109 }, }, + { { 149, 105 }, + { 210, 105 }, + { 150, 169 }, + { 150, 75 }, }, + { { 159, 109 }, + { 214, 125 }, + { 182, 249 }, + { 190, 107 }, }, + { { 151, 43 }, + { 215, 73 }, + { 212, 233 }, + { 146, 235 }, }, + { { 149, 182 }, + { 227, 90 }, + { 109, 169 }, + { 90, 199 }, }, + { { 149, 185 }, + { 242, 75 }, + { 157, 169 }, + { 210, 79 }, }, + { { 157, 61 }, + { 242, 93 }, + { 188, 185 }, + { 186, 79 }, }, + { { 157, 87 }, + { 241, 124 }, + { 234, 185 }, + { 62, 143 }, }, + { { 168, 236 }, + { 10, 63 }, + { 55, 21 }, + { 252, 80 }, }, + { { 168, 37 }, + { 26, 28 }, + { 164, 21 }, + { 56, 88 }, }, + { { 162, 172 }, + { 14, 27 }, + { 53, 69 }, + { 216, 112 }, }, + { { 162, 2 }, + { 13, 8 }, + { 64, 69 }, + { 16, 176 }, }, + { { 170, 102 }, + { 15, 60 }, + { 102, 85 }, + { 60, 240 }, }, + { { 170, 143 }, + { 29, 31 }, + { 241, 85 }, + { 248, 184 }, }, + { { 170, 231 }, + { 31, 62 }, + { 231, 85 }, + { 124, 248 }, }, + { { 168, 48 }, + { 42, 12 }, + { 12, 21 }, + { 48, 84 }, }, + { { 168, 122 }, + { 43, 45 }, + { 94, 21 }, + { 180, 212 }, }, + { { 168, 246 }, + { 43, 62 }, + { 111, 21 }, + { 124, 212 }, }, + { { 168, 147 }, + { 57, 14 }, + { 201, 21 }, + { 112, 156 }, }, + { { 162, 20 }, + { 44, 24 }, + { 40, 69 }, + { 24, 52 }, }, + { { 170, 52 }, + { 46, 28 }, + { 44, 85 }, + { 56, 116 }, }, + { { 162, 114 }, + { 47, 40 }, + { 78, 69 }, + { 20, 244 }, }, + { { 170, 242 }, + { 47, 46 }, + { 79, 85 }, + { 116, 244 }, }, + { { 162, 241 }, + { 62, 42 }, + { 143, 69 }, + { 84, 124 }, }, + { { 161, 64 }, + { 72, 40 }, + { 2, 133 }, + { 20, 18 }, }, + { { 169, 10 }, + { 73, 13 }, + { 80, 149 }, + { 176, 146 }, }, + { { 161, 38 }, + { 75, 24 }, + { 100, 133 }, + { 24, 210 }, }, + { { 169, 197 }, + { 88, 62 }, + { 163, 149 }, + { 124, 26 }, }, + { { 169, 207 }, + { 89, 63 }, + { 243, 149 }, + { 252, 154 }, }, + { { 161, 52 }, + { 106, 24 }, + { 44, 133 }, + { 24, 86 }, }, + { { 169, 18 }, + { 105, 12 }, + { 72, 149 }, + { 48, 150 }, }, + { { 161, 250 }, + { 107, 43 }, + { 95, 133 }, + { 212, 214 }, }, + { { 171, 152 }, + { 108, 15 }, + { 25, 213 }, + { 240, 54 }, }, + { { 163, 247 }, + { 127, 58 }, + { 239, 197 }, + { 92, 254 }, }, + { { 176, 6 }, + { 137, 24 }, + { 96, 13 }, + { 24, 145 }, }, + { { 176, 69 }, + { 152, 56 }, + { 162, 13 }, + { 28, 25 }, }, + { { 184, 141 }, + { 152, 31 }, + { 177, 29 }, + { 248, 25 }, }, + { { 178, 132 }, + { 140, 26 }, + { 33, 77 }, + { 88, 49 }, }, + { { 184, 240 }, + { 170, 46 }, + { 15, 29 }, + { 116, 85 }, }, + { { 184, 85 }, + { 184, 60 }, + { 170, 29 }, + { 60, 29 }, }, + { { 178, 118 }, + { 175, 56 }, + { 110, 77 }, + { 28, 245 }, }, + { { 186, 145 }, + { 188, 14 }, + { 137, 93 }, + { 112, 61 }, }, + { { 178, 113 }, + { 190, 40 }, + { 142, 77 }, + { 20, 125 }, }, + { { 185, 192 }, + { 200, 46 }, + { 3, 157 }, + { 116, 19 }, }, + { { 185, 66 }, + { 201, 44 }, + { 66, 157 }, + { 52, 147 }, }, + { { 185, 42 }, + { 203, 13 }, + { 84, 157 }, + { 176, 211 }, }, + { { 179, 140 }, + { 204, 27 }, + { 49, 205 }, + { 216, 51 }, }, + { { 179, 202 }, + { 205, 43 }, + { 83, 205 }, + { 212, 179 }, }, + { { 187, 102 }, + { 207, 60 }, + { 102, 221 }, + { 60, 243 }, }, + { { 179, 15 }, + { 221, 25 }, + { 240, 205 }, + { 152, 187 }, }, + { { 177, 218 }, + { 233, 43 }, + { 91, 141 }, + { 212, 151 }, }, + { { 187, 20 }, + { 236, 28 }, + { 40, 221 }, + { 56, 55 }, }, + { { 187, 246 }, + { 239, 62 }, + { 111, 221 }, + { 124, 247 }, }, + { { 179, 19 }, + { 253, 8 }, + { 200, 205 }, + { 16, 191 }, }, + { { 164, 104 }, + { 10, 105 }, + { 22, 37 }, + { 150, 80 }, }, + { { 172, 44 }, + { 10, 93 }, + { 52, 53 }, + { 186, 80 }, }, + { { 172, 161 }, + { 26, 78 }, + { 133, 53 }, + { 114, 88 }, }, + { { 172, 235 }, + { 27, 111 }, + { 215, 53 }, + { 246, 216 }, }, + { { 172, 199 }, + { 25, 126 }, + { 227, 53 }, + { 126, 152 }, }, + { { 164, 103 }, + { 27, 120 }, + { 230, 37 }, + { 30, 216 }, }, + { { 166, 192 }, + { 12, 106 }, + { 3, 101 }, + { 86, 48 }, }, + { { 174, 224 }, + { 14, 110 }, + { 7, 117 }, + { 118, 112 }, }, + { { 166, 35 }, + { 31, 72 }, + { 196, 101 }, + { 18, 248 }, }, + { { 173, 232 }, + { 74, 111 }, + { 23, 181 }, + { 246, 82 }, }, + { { 165, 204 }, + { 72, 123 }, + { 51, 165 }, + { 222, 18 }, }, + { { 167, 236 }, + { 78, 123 }, + { 55, 229 }, + { 222, 114 }, }, + { { 173, 124 }, + { 106, 125 }, + { 62, 181 }, + { 190, 86 }, }, + { { 165, 26 }, + { 105, 73 }, + { 88, 165 }, + { 146, 150 }, }, + { { 165, 145 }, + { 120, 74 }, + { 137, 165 }, + { 82, 30 }, }, + { { 173, 25 }, + { 120, 77 }, + { 152, 181 }, + { 178, 30 }, }, + { { 165, 151 }, + { 121, 90 }, + { 233, 165 }, + { 90, 158 }, }, + { { 180, 109 }, + { 154, 121 }, + { 182, 45 }, + { 158, 89 }, }, + { { 190, 203 }, + { 157, 111 }, + { 211, 125 }, + { 246, 185 }, }, + { { 188, 58 }, + { 171, 77 }, + { 92, 61 }, + { 178, 213 }, }, + { { 188, 245 }, + { 186, 126 }, + { 175, 61 }, + { 126, 93 }, }, + { { 190, 189 }, + { 190, 95 }, + { 189, 125 }, + { 250, 125 }, }, + { { 190, 243 }, + { 191, 110 }, + { 207, 125 }, + { 118, 253 }, }, + { { 181, 37 }, + { 218, 88 }, + { 164, 173 }, + { 26, 91 }, }, + { { 181, 143 }, + { 217, 91 }, + { 241, 173 }, + { 218, 155 }, }, + { { 183, 104 }, + { 206, 105 }, + { 22, 237 }, + { 150, 115 }, }, + { { 191, 228 }, + { 206, 126 }, + { 39, 253 }, + { 126, 115 }, }, + { { 189, 254 }, + { 235, 127 }, + { 127, 189 }, + { 254, 215 }, }, + { { 189, 157 }, + { 248, 95 }, + { 185, 189 }, + { 250, 31 }, }, + { { 181, 245 }, + { 250, 122 }, + { 175, 173 }, + { 94, 95 }, }, + { { 181, 243 }, + { 251, 106 }, + { 207, 173 }, + { 86, 223 }, }, + { { 191, 176 }, + { 238, 78 }, + { 13, 253 }, + { 114, 119 }, }, + { { 183, 90 }, + { 237, 105 }, + { 90, 237 }, + { 150, 183 }, }, + { { 191, 62 }, + { 239, 93 }, + { 124, 253 }, + { 186, 247 }, }, + { { 183, 57 }, + { 254, 73 }, + { 156, 237 }, + { 146, 127 }, }, + { { 191, 213 }, + { 252, 126 }, + { 171, 253 }, + { 126, 63 }, }, + { { 183, 29 }, + { 252, 89 }, + { 184, 237 }, + { 154, 63 }, }, + { { 191, 53 }, + { 254, 92 }, + { 172, 253 }, + { 58, 127 }, }, + { { 183, 127 }, + { 255, 121 }, + { 254, 237 }, + { 158, 255 }, }, + { { 200, 1 }, + { 16, 140 }, + { 128, 19 }, + { 49, 8 }, }, + { { 192, 165 }, + { 18, 154 }, + { 165, 3 }, + { 89, 72 }, }, + { { 194, 130 }, + { 5, 138 }, + { 65, 67 }, + { 81, 160 }, }, + { { 200, 189 }, + { 50, 159 }, + { 189, 19 }, + { 249, 76 }, }, + { { 194, 252 }, + { 38, 187 }, + { 63, 67 }, + { 221, 100 }, }, + { { 202, 145 }, + { 52, 142 }, + { 137, 83 }, + { 113, 44 }, }, + { { 194, 91 }, + { 53, 169 }, + { 218, 67 }, + { 149, 172 }, }, + { { 201, 68 }, + { 64, 188 }, + { 34, 147 }, + { 61, 2 }, }, + { { 193, 42 }, + { 67, 137 }, + { 84, 131 }, + { 145, 194 }, }, + { { 195, 192 }, + { 68, 170 }, + { 3, 195 }, + { 85, 34 }, }, + { { 201, 122 }, + { 99, 173 }, + { 94, 147 }, + { 181, 198 }, }, + { { 193, 185 }, + { 114, 139 }, + { 157, 131 }, + { 209, 78 }, }, + { { 201, 117 }, + { 114, 188 }, + { 174, 147 }, + { 61, 78 }, }, + { { 193, 247 }, + { 115, 186 }, + { 239, 131 }, + { 93, 206 }, }, + { { 203, 177 }, + { 118, 142 }, + { 141, 211 }, + { 113, 110 }, }, + { { 208, 108 }, + { 130, 185 }, + { 54, 11 }, + { 157, 65 }, }, + { { 216, 135 }, + { 145, 158 }, + { 225, 27 }, + { 121, 137 }, }, + { { 208, 175 }, + { 147, 155 }, + { 245, 11 }, + { 217, 201 }, }, + { { 218, 196 }, + { 132, 190 }, + { 35, 91 }, + { 125, 33 }, }, + { { 210, 12 }, + { 132, 153 }, + { 48, 75 }, + { 153, 33 }, }, + { { 218, 9 }, + { 148, 141 }, + { 144, 91 }, + { 177, 41 }, }, + { { 208, 48 }, + { 162, 136 }, + { 12, 11 }, + { 17, 69 }, }, + { { 216, 148 }, + { 160, 158 }, + { 41, 27 }, + { 121, 5 }, }, + { { 208, 58 }, + { 163, 137 }, + { 92, 11 }, + { 145, 197 }, }, + { { 208, 182 }, + { 163, 154 }, + { 109, 11 }, + { 89, 197 }, }, + { { 208, 117 }, + { 178, 184 }, + { 174, 11 }, + { 29, 77 }, }, + { { 210, 118 }, + { 167, 184 }, + { 110, 75 }, + { 29, 229 }, }, + { { 218, 93 }, + { 180, 189 }, + { 186, 91 }, + { 189, 45 }, }, + { { 218, 53 }, + { 182, 156 }, + { 172, 91 }, + { 57, 109 }, }, + { { 210, 23 }, + { 181, 152 }, + { 232, 75 }, + { 25, 173 }, }, + { { 217, 2 }, + { 193, 140 }, + { 64, 155 }, + { 49, 131 }, }, + { { 211, 232 }, + { 198, 171 }, + { 23, 203 }, + { 213, 99 }, }, + { { 211, 229 }, + { 214, 186 }, + { 167, 203 }, + { 93, 107 }, }, + { { 209, 154 }, + { 225, 139 }, + { 89, 139 }, + { 209, 135 }, }, + { { 209, 246 }, + { 227, 186 }, + { 111, 139 }, + { 93, 199 }, }, + { { 209, 81 }, + { 240, 168 }, + { 138, 139 }, + { 21, 15 }, }, + { { 219, 20 }, + { 228, 156 }, + { 40, 219 }, + { 57, 39 }, }, + { { 211, 62 }, + { 231, 153 }, + { 124, 203 }, + { 153, 231 }, }, + { { 211, 211 }, + { 245, 170 }, + { 203, 203 }, + { 85, 175 }, }, + { { 196, 96 }, + { 2, 232 }, + { 6, 35 }, + { 23, 64 }, }, + { { 204, 167 }, + { 19, 222 }, + { 229, 51 }, + { 123, 200 }, }, + { { 198, 66 }, + { 5, 232 }, + { 66, 99 }, + { 23, 160 }, }, + { { 198, 71 }, + { 21, 248 }, + { 226, 99 }, + { 31, 168 }, }, + { { 206, 231 }, + { 23, 254 }, + { 231, 115 }, + { 127, 232 }, }, + { { 196, 92 }, + { 32, 249 }, + { 58, 35 }, + { 159, 4 }, }, + { { 204, 29 }, + { 48, 221 }, + { 184, 51 }, + { 187, 12 }, }, + { { 204, 53 }, + { 50, 220 }, + { 172, 51 }, + { 59, 76 }, }, + { { 198, 188 }, + { 38, 219 }, + { 61, 99 }, + { 219, 100 }, }, + { { 205, 168 }, + { 66, 207 }, + { 21, 179 }, + { 243, 66 }, }, + { { 197, 12 }, + { 64, 217 }, + { 48, 163 }, + { 155, 2 }, }, + { { 197, 228 }, + { 66, 250 }, + { 39, 163 }, + { 95, 66 }, }, + { { 197, 194 }, + { 65, 234 }, + { 67, 163 }, + { 87, 130 }, }, + { { 205, 45 }, + { 82, 221 }, + { 180, 179 }, + { 187, 74 }, }, + { { 205, 89 }, + { 112, 237 }, + { 154, 179 }, + { 183, 14 }, }, + { { 205, 149 }, + { 112, 222 }, + { 169, 179 }, + { 123, 14 }, }, + { { 197, 147 }, + { 113, 202 }, + { 201, 163 }, + { 83, 142 }, }, + { { 199, 95 }, + { 117, 249 }, + { 250, 227 }, + { 159, 174 }, }, + { { 212, 197 }, + { 144, 250 }, + { 163, 43 }, + { 95, 9 }, }, + { { 222, 136 }, + { 132, 207 }, + { 17, 123 }, + { 243, 33 }, }, + { { 214, 36 }, + { 134, 216 }, + { 36, 107 }, + { 27, 97 }, }, + { { 222, 236 }, + { 134, 255 }, + { 55, 123 }, + { 255, 97 }, }, + { { 214, 226 }, + { 135, 234 }, + { 71, 107 }, + { 87, 225 }, }, + { { 222, 198 }, + { 133, 254 }, + { 99, 123 }, + { 127, 161 }, }, + { { 222, 35 }, + { 151, 204 }, + { 196, 123 }, + { 51, 233 }, }, + { { 220, 220 }, + { 160, 255 }, + { 59, 59 }, + { 255, 5 }, }, + { { 220, 26 }, + { 161, 205 }, + { 88, 59 }, + { 179, 133 }, }, + { { 212, 17 }, + { 176, 200 }, + { 136, 43 }, + { 19, 13 }, }, + { { 222, 84 }, + { 164, 252 }, + { 42, 123 }, + { 63, 37 }, }, + { { 214, 148 }, + { 164, 218 }, + { 41, 107 }, + { 91, 37 }, }, + { { 222, 157 }, + { 180, 223 }, + { 185, 123 }, + { 251, 45 }, }, + { { 221, 129 }, + { 208, 206 }, + { 129, 187 }, + { 115, 11 }, }, + { { 213, 165 }, + { 210, 218 }, + { 165, 171 }, + { 91, 75 }, }, + { { 215, 172 }, + { 198, 219 }, + { 53, 235 }, + { 219, 99 }, }, + { { 215, 102 }, + { 199, 248 }, + { 102, 235 }, + { 31, 227 }, }, + { { 223, 169 }, + { 214, 207 }, + { 149, 251 }, + { 243, 107 }, }, + { { 213, 220 }, + { 224, 251 }, + { 59, 171 }, + { 223, 7 }, }, + { { 221, 31 }, + { 241, 221 }, + { 248, 187 }, + { 187, 143 }, }, + { { 223, 240 }, + { 230, 238 }, + { 15, 251 }, + { 119, 103 }, }, + { { 226, 72 }, + { 12, 169 }, + { 18, 71 }, + { 149, 48 }, }, + { { 226, 232 }, + { 14, 171 }, + { 23, 71 }, + { 213, 112 }, }, + { { 226, 7 }, + { 29, 152 }, + { 224, 71 }, + { 25, 184 }, }, + { { 224, 93 }, + { 56, 185 }, + { 186, 7 }, + { 157, 28 }, }, + { { 234, 245 }, + { 62, 190 }, + { 175, 87 }, + { 125, 124 }, }, + { { 235, 38 }, + { 79, 156 }, + { 100, 215 }, + { 57, 242 }, }, + { { 235, 237 }, + { 94, 191 }, + { 183, 215 }, + { 253, 122 }, }, + { { 225, 82 }, + { 105, 168 }, + { 74, 135 }, + { 21, 150 }, }, + { { 225, 126 }, + { 107, 185 }, + { 126, 135 }, + { 157, 214 }, }, + { { 233, 219 }, + { 121, 175 }, + { 219, 151 }, + { 245, 158 }, }, + { { 248, 6 }, + { 137, 156 }, + { 96, 31 }, + { 57, 145 }, }, + { { 240, 238 }, + { 139, 187 }, + { 119, 15 }, + { 221, 209 }, }, + { { 248, 161 }, + { 154, 142 }, + { 133, 31 }, + { 113, 89 }, }, + { { 250, 0 }, + { 140, 140 }, + { 0, 95 }, + { 49, 49 }, }, + { { 250, 194 }, + { 141, 174 }, + { 67, 95 }, + { 117, 177 }, }, + { { 240, 155 }, + { 185, 139 }, + { 217, 15 }, + { 209, 157 }, }, + { { 250, 244 }, + { 174, 190 }, + { 47, 95 }, + { 125, 117 }, }, + { { 250, 60 }, + { 174, 157 }, + { 60, 95 }, + { 185, 117 }, }, + { { 242, 252 }, + { 174, 187 }, + { 63, 79 }, + { 221, 117 }, }, + { { 242, 189 }, + { 190, 155 }, + { 189, 79 }, + { 217, 125 }, }, + { { 242, 147 }, + { 189, 138 }, + { 201, 79 }, + { 81, 189 }, }, + { { 241, 96 }, + { 202, 168 }, + { 6, 143 }, + { 21, 83 }, }, + { { 249, 236 }, + { 202, 191 }, + { 55, 159 }, + { 253, 83 }, }, + { { 241, 70 }, + { 201, 184 }, + { 98, 143 }, + { 29, 147 }, }, + { { 249, 225 }, + { 218, 174 }, + { 135, 159 }, + { 117, 91 }, }, + { { 243, 72 }, + { 204, 169 }, + { 18, 207 }, + { 149, 51 }, }, + { { 243, 174 }, + { 207, 155 }, + { 117, 207 }, + { 217, 243 }, }, + { { 243, 193 }, + { 220, 170 }, + { 131, 207 }, + { 85, 59 }, }, + { { 243, 139 }, + { 221, 139 }, + { 209, 207 }, + { 209, 187 }, }, + { { 243, 167 }, + { 223, 154 }, + { 229, 207 }, + { 89, 251 }, }, + { { 241, 115 }, + { 251, 168 }, + { 206, 143 }, + { 21, 223 }, }, + { { 241, 151 }, + { 249, 154 }, + { 233, 143 }, + { 89, 159 }, }, + { { 243, 244 }, + { 238, 186 }, + { 47, 207 }, + { 93, 119 }, }, + { { 251, 50 }, + { 239, 140 }, + { 76, 223 }, + { 49, 247 }, }, + { { 228, 7 }, + { 25, 216 }, + { 224, 39 }, + { 27, 152 }, }, + { { 230, 77 }, + { 28, 249 }, + { 178, 103 }, + { 159, 56 }, }, + { { 236, 85 }, + { 56, 252 }, + { 170, 55 }, + { 63, 28 }, }, + { { 237, 192 }, + { 72, 238 }, + { 3, 183 }, + { 119, 18 }, }, + { { 237, 133 }, + { 88, 222 }, + { 161, 183 }, + { 123, 26 }, }, + { { 239, 162 }, + { 79, 206 }, + { 69, 247 }, + { 115, 242 }, }, + { { 231, 78 }, + { 77, 249 }, + { 114, 231 }, + { 159, 178 }, }, + { { 229, 213 }, + { 120, 250 }, + { 171, 167 }, + { 95, 30 }, }, + { { 239, 80 }, + { 108, 236 }, + { 10, 247 }, + { 55, 54 }, }, + { { 244, 34 }, + { 139, 200 }, + { 68, 47 }, + { 19, 209 }, }, + { { 244, 137 }, + { 152, 203 }, + { 145, 47 }, + { 211, 25 }, }, + { { 244, 41 }, + { 154, 201 }, + { 148, 47 }, + { 147, 89 }, }, + { { 246, 106 }, + { 143, 233 }, + { 86, 111 }, + { 151, 241 }, }, + { { 254, 11 }, + { 157, 205 }, + { 208, 127 }, + { 179, 185 }, }, + { { 254, 111 }, + { 159, 253 }, + { 246, 127 }, + { 191, 249 }, }, + { { 244, 149 }, + { 184, 218 }, + { 169, 47 }, + { 91, 29 }, }, + { { 244, 53 }, + { 186, 216 }, + { 172, 47 }, + { 27, 93 }, }, + { { 244, 31 }, + { 185, 217 }, + { 248, 47 }, + { 155, 157 }, }, + { { 246, 176 }, + { 174, 202 }, + { 13, 111 }, + { 83, 117 }, }, + { { 245, 232 }, + { 202, 235 }, + { 23, 175 }, + { 215, 83 }, }, + { { 245, 197 }, + { 216, 250 }, + { 163, 175 }, + { 95, 27 }, }, + { { 253, 35 }, + { 219, 204 }, + { 196, 191 }, + { 51, 219 }, }, + { { 255, 192 }, + { 204, 238 }, + { 3, 255 }, + { 119, 51 }, }, + { { 247, 204 }, + { 204, 251 }, + { 51, 239 }, + { 223, 51 }, }, + { { 247, 233 }, + { 222, 235 }, + { 151, 239 }, + { 215, 123 }, }, + { { 245, 188 }, + { 234, 219 }, + { 61, 175 }, + { 219, 87 }, }, + { { 253, 246 }, + { 235, 254 }, + { 111, 191 }, + { 127, 215 }, }, + { { 245, 217 }, + { 248, 235 }, + { 155, 175 }, + { 215, 31 }, }, + { { 253, 151 }, + { 249, 222 }, + { 233, 191 }, + { 123, 159 }, }, + { { 253, 63 }, + { 251, 221 }, + { 252, 191 }, + { 187, 223 }, }, + { { 255, 156 }, + { 236, 223 }, + { 57, 255 }, + { 251, 55 }, }, + { { 255, 90 }, + { 237, 237 }, + { 90, 255 }, + { 183, 183 }, }, + { { 247, 254 }, + { 239, 251 }, + { 127, 239 }, + { 223, 247 }, }, + { { 255, 17 }, + { 252, 204 }, + { 136, 255 }, + { 51, 63 }, }, + { { 247, 191 }, + { 255, 219 }, + { 253, 239 }, + { 219, 255 }, }, }; + +static unsigned char DICT_5X5_1000_BYTES[][4][4] = + { { { 162, 217, 94, 0 }, + { 82, 46, 217, 1 }, + { 61, 77, 162, 1 }, + { 205, 186, 37, 0 }, }, + { { 14, 3, 115, 0 }, + { 176, 198, 133, 0 }, + { 103, 96, 56, 0 }, + { 80, 177, 134, 1 }, }, + { { 215, 135, 110, 1 }, + { 47, 151, 157, 1 }, + { 187, 112, 245, 1 }, + { 220, 244, 250, 0 }, }, + { { 129, 202, 251, 1 }, + { 122, 222, 40, 1 }, + { 239, 169, 192, 1 }, + { 138, 61, 175, 0 }, }, + { { 215, 90, 146, 0 }, + { 116, 27, 236, 0 }, + { 36, 173, 117, 1 }, + { 27, 236, 23, 0 }, }, + { { 234, 4, 22, 1 }, + { 153, 35, 152, 0 }, + { 180, 16, 43, 1 }, + { 12, 226, 76, 1 }, }, + { { 105, 235, 246, 0 }, + { 242, 191, 51, 0 }, + { 55, 235, 203, 0 }, + { 102, 126, 167, 1 }, }, + { { 113, 10, 53, 1 }, + { 60, 249, 16, 0 }, + { 214, 40, 71, 0 }, + { 4, 79, 158, 0 }, }, + { { 134, 176, 153, 0 }, + { 18, 64, 238, 1 }, + { 76, 134, 176, 1 }, + { 187, 129, 36, 0 }, }, + { { 152, 159, 210, 1 }, + { 191, 14, 105, 0 }, + { 165, 252, 140, 1 }, + { 75, 56, 126, 1 }, }, + { { 158, 119, 1, 1 }, + { 237, 64, 207, 0 }, + { 192, 119, 60, 1 }, + { 121, 129, 91, 1 }, }, + { { 209, 109, 96, 0 }, + { 69, 157, 11, 0 }, + { 3, 91, 69, 1 }, + { 104, 92, 209, 0 }, }, + { { 243, 21, 136, 1 }, + { 13, 49, 233, 1 }, + { 136, 212, 103, 1 }, + { 203, 198, 88, 0 }, }, + { { 47, 56, 179, 0 }, + { 144, 250, 230, 0 }, + { 102, 142, 122, 0 }, + { 51, 175, 132, 1 }, }, + { { 254, 126, 84, 0 }, + { 245, 45, 222, 0 }, + { 21, 63, 63, 1 }, + { 61, 218, 87, 1 }, }, + { { 40, 241, 191, 1 }, + { 218, 226, 115, 1 }, + { 254, 199, 138, 0 }, + { 231, 35, 173, 1 }, }, + { { 75, 211, 172, 0 }, + { 226, 145, 241, 1 }, + { 26, 229, 233, 0 }, + { 199, 196, 163, 1 }, }, + { { 95, 81, 55, 1 }, + { 220, 211, 213, 0 }, + { 246, 69, 125, 0 }, + { 85, 229, 157, 1 }, }, + { { 123, 38, 226, 0 }, + { 165, 183, 162, 0 }, + { 35, 178, 111, 0 }, + { 34, 246, 210, 1 }, }, + { { 131, 14, 244, 0 }, + { 49, 156, 184, 0 }, + { 23, 184, 96, 1 }, + { 14, 156, 198, 0 }, }, + { { 150, 237, 58, 1 }, + { 95, 138, 143, 1 }, + { 174, 91, 180, 1 }, + { 248, 168, 253, 0 }, }, + { { 168, 114, 32, 0 }, + { 224, 160, 74, 0 }, + { 2, 39, 10, 1 }, + { 41, 2, 131, 1 }, }, + { { 181, 134, 80, 1 }, + { 63, 52, 12, 0 }, + { 133, 48, 214, 1 }, + { 24, 22, 126, 0 }, }, + { { 93, 9, 111, 0 }, + { 132, 223, 21, 1 }, + { 123, 72, 93, 0 }, + { 212, 125, 144, 1 }, }, + { { 206, 104, 17, 1 }, + { 216, 73, 142, 0 }, + { 196, 11, 57, 1 }, + { 56, 201, 13, 1 }, }, + { { 210, 204, 185, 0 }, + { 87, 201, 168, 1 }, + { 78, 153, 165, 1 }, + { 138, 201, 245, 0 }, }, + { { 225, 231, 69, 1 }, + { 107, 117, 27, 0 }, + { 209, 115, 195, 1 }, + { 108, 87, 107, 0 }, }, + { { 17, 33, 35, 0 }, + { 4, 210, 3, 0 }, + { 98, 66, 68, 0 }, + { 96, 37, 144, 0 }, }, + { { 29, 203, 57, 0 }, + { 246, 216, 5, 1 }, + { 78, 105, 220, 0 }, + { 208, 13, 183, 1 }, }, + { { 18, 17, 29, 1 }, + { 28, 64, 209, 1 }, + { 220, 68, 36, 0 }, + { 197, 129, 28, 0 }, }, + { { 19, 155, 183, 0 }, + { 54, 218, 241, 0 }, + { 118, 236, 228, 0 }, + { 71, 173, 182, 0 }, }, + { { 27, 68, 57, 1 }, + { 221, 208, 128, 1 }, + { 206, 17, 108, 0 }, + { 128, 133, 221, 1 }, }, + { { 32, 104, 103, 0 }, + { 64, 238, 18, 0 }, + { 115, 11, 2, 0 }, + { 36, 59, 129, 0 }, }, + { { 37, 85, 100, 0 }, + { 65, 180, 85, 0 }, + { 19, 85, 82, 0 }, + { 85, 22, 193, 0 }, }, + { { 35, 33, 221, 0 }, + { 16, 116, 179, 1 }, + { 93, 194, 98, 0 }, + { 230, 151, 4, 0 }, }, + { { 61, 55, 245, 0 }, + { 181, 244, 119, 0 }, + { 87, 246, 94, 0 }, + { 119, 23, 214, 1 }, }, + { { 76, 197, 86, 0 }, + { 211, 7, 21, 0 }, + { 53, 81, 153, 0 }, + { 84, 112, 101, 1 }, }, + { { 65, 104, 128, 1 }, + { 72, 25, 34, 0 }, + { 128, 139, 65, 0 }, + { 34, 76, 9, 0 }, }, + { { 77, 86, 142, 1 }, + { 233, 19, 116, 1 }, + { 184, 181, 89, 0 }, + { 151, 100, 75, 1 }, }, + { { 67, 30, 57, 0 }, + { 49, 217, 192, 1 }, + { 78, 60, 97, 0 }, + { 129, 205, 198, 0 }, }, + { { 86, 148, 18, 1 }, + { 31, 3, 196, 0 }, + { 164, 20, 181, 0 }, + { 17, 224, 124, 0 }, }, + { { 82, 151, 207, 0 }, + { 39, 71, 241, 1 }, + { 121, 244, 165, 0 }, + { 199, 241, 114, 0 }, }, + { { 108, 36, 251, 1 }, + { 153, 231, 38, 1 }, + { 239, 146, 27, 0 }, + { 178, 115, 204, 1 }, }, + { { 97, 132, 236, 1 }, + { 11, 181, 48, 1 }, + { 155, 144, 195, 0 }, + { 134, 86, 232, 0 }, }, + { { 109, 63, 24, 1 }, + { 185, 57, 71, 1 }, + { 140, 126, 91, 0 }, + { 241, 78, 78, 1 }, }, + { { 116, 177, 61, 0 }, + { 22, 225, 87, 1 }, + { 94, 70, 151, 0 }, + { 245, 67, 180, 0 }, }, + { { 116, 220, 203, 1 }, + { 79, 111, 100, 1 }, + { 233, 157, 151, 0 }, + { 147, 123, 121, 0 }, }, + { { 124, 164, 3, 0 }, + { 135, 99, 6, 0 }, + { 96, 18, 159, 0 }, + { 48, 99, 112, 1 }, }, + { { 122, 200, 146, 1 }, + { 222, 43, 160, 0 }, + { 164, 137, 175, 0 }, + { 2, 234, 61, 1 }, }, + { { 123, 91, 235, 1 }, + { 236, 255, 225, 1 }, + { 235, 237, 111, 0 }, + { 195, 255, 155, 1 }, }, + { { 141, 172, 114, 0 }, + { 147, 158, 14, 0 }, + { 39, 26, 216, 1 }, + { 56, 60, 228, 1 }, }, + { { 141, 105, 60, 1 }, + { 216, 152, 31, 1 }, + { 158, 75, 88, 1 }, + { 252, 12, 141, 1 }, }, + { { 143, 28, 5, 1 }, + { 137, 88, 220, 0 }, + { 208, 28, 120, 1 }, + { 29, 141, 72, 1 }, }, + { { 139, 74, 34, 1 }, + { 232, 154, 136, 0 }, + { 162, 41, 104, 1 }, + { 8, 172, 139, 1 }, }, + { { 151, 253, 165, 0 }, + { 71, 216, 255, 0 }, + { 82, 223, 244, 1 }, + { 127, 141, 241, 0 }, }, + { { 172, 101, 198, 1 }, + { 201, 38, 63, 0 }, + { 177, 211, 26, 1 }, + { 126, 50, 73, 1 }, }, + { { 172, 195, 248, 0 }, + { 242, 164, 45, 1 }, + { 15, 225, 154, 1 }, + { 218, 18, 167, 1 }, }, + { { 161, 23, 239, 1 }, + { 41, 246, 121, 1 }, + { 251, 244, 66, 1 }, + { 207, 55, 202, 0 }, }, + { { 167, 9, 19, 1 }, + { 24, 122, 141, 0 }, + { 228, 72, 114, 1 }, + { 88, 175, 12, 0 }, }, + { { 171, 111, 145, 0 }, + { 241, 120, 171, 0 }, + { 68, 251, 106, 1 }, + { 106, 143, 71, 1 }, }, + { { 185, 237, 248, 1 }, + { 223, 188, 43, 1 }, + { 143, 219, 206, 1 }, + { 234, 30, 253, 1 }, }, + { { 178, 100, 158, 0 }, + { 85, 34, 186, 1 }, + { 60, 147, 38, 1 }, + { 174, 162, 85, 0 }, }, + { { 190, 93, 195, 0 }, + { 197, 110, 237, 0 }, + { 97, 221, 62, 1 }, + { 91, 187, 81, 1 }, }, + { { 196, 5, 67, 1 }, + { 9, 71, 13, 0 }, + { 225, 80, 17, 1 }, + { 88, 113, 72, 0 }, }, + { { 200, 163, 238, 1 }, + { 170, 135, 59, 1 }, + { 187, 226, 137, 1 }, + { 238, 112, 170, 1 }, }, + { { 194, 117, 197, 0 }, + { 65, 69, 251, 0 }, + { 81, 215, 33, 1 }, + { 111, 209, 65, 0 }, }, + { { 198, 194, 214, 1 }, + { 122, 7, 188, 0 }, + { 181, 161, 177, 1 }, + { 30, 240, 47, 0 }, }, + { { 217, 102, 212, 1 }, + { 253, 21, 58, 0 }, + { 149, 179, 77, 1 }, + { 46, 84, 95, 1 }, }, + { { 221, 94, 185, 1 }, + { 253, 217, 108, 1 }, + { 206, 189, 93, 1 }, + { 155, 77, 223, 1 }, }, + { { 244, 234, 25, 0 }, + { 118, 105, 14, 1 }, + { 76, 43, 151, 1 }, + { 184, 75, 55, 0 }, }, + { { 243, 178, 148, 0 }, + { 54, 49, 250, 0 }, + { 20, 166, 231, 1 }, + { 47, 198, 54, 0 }, }, + { { 122, 186, 5, 0 }, + { 166, 105, 210, 0 }, + { 80, 46, 175, 0 }, + { 37, 203, 50, 1 }, }, + { { 216, 141, 41, 1 }, + { 143, 201, 9, 1 }, + { 202, 88, 141, 1 }, + { 200, 73, 248, 1 }, }, + { { 12, 103, 50, 1 }, + { 249, 130, 7, 0 }, + { 166, 115, 24, 0 }, + { 112, 32, 207, 1 }, }, + { { 21, 89, 12, 1 }, + { 76, 24, 85, 1 }, + { 152, 77, 84, 0 }, + { 213, 12, 25, 0 }, }, + { { 76, 116, 192, 1 }, + { 201, 5, 102, 0 }, + { 129, 151, 25, 0 }, + { 51, 80, 73, 1 }, }, + { { 84, 3, 14, 0 }, + { 36, 3, 21, 1 }, + { 56, 96, 21, 0 }, + { 212, 96, 18, 0 }, }, + { { 160, 208, 172, 0 }, + { 66, 160, 120, 1 }, + { 26, 133, 130, 1 }, + { 143, 2, 161, 0 }, }, + { { 194, 152, 166, 0 }, + { 2, 139, 248, 0 }, + { 50, 140, 161, 1 }, + { 15, 232, 160, 0 }, }, + { { 203, 104, 150, 0 }, + { 208, 27, 186, 0 }, + { 52, 139, 105, 1 }, + { 46, 236, 5, 1 }, }, + { { 253, 105, 209, 0 }, + { 212, 125, 47, 0 }, + { 69, 203, 95, 1 }, + { 122, 95, 21, 1 }, }, + { { 4, 145, 90, 1 }, + { 26, 6, 69, 1 }, + { 173, 68, 144, 0 }, + { 209, 48, 44, 0 }, }, + { { 12, 222, 112, 0 }, + { 243, 140, 68, 0 }, + { 7, 61, 152, 0 }, + { 17, 24, 231, 1 }, }, + { { 5, 170, 62, 0 }, + { 50, 154, 22, 1 }, + { 62, 42, 208, 0 }, + { 180, 44, 166, 0 }, }, + { { 1, 99, 183, 1 }, + { 120, 210, 51, 0 }, + { 246, 227, 64, 0 }, + { 102, 37, 143, 0 }, }, + { { 9, 145, 68, 1 }, + { 138, 20, 81, 0 }, + { 145, 68, 200, 0 }, + { 69, 20, 40, 1 }, }, + { { 9, 105, 83, 1 }, + { 216, 94, 3, 0 }, + { 229, 75, 72, 0 }, + { 96, 61, 13, 1 }, }, + { { 6, 37, 161, 1 }, + { 9, 192, 167, 0 }, + { 194, 210, 48, 0 }, + { 114, 129, 200, 0 }, }, + { { 3, 61, 226, 1 }, + { 9, 158, 227, 0 }, + { 163, 222, 96, 0 }, + { 99, 188, 200, 0 }, }, + { { 7, 213, 6, 1 }, + { 75, 18, 213, 0 }, + { 176, 85, 240, 0 }, + { 85, 164, 105, 0 }, }, + { { 15, 143, 170, 0 }, + { 163, 154, 165, 1 }, + { 42, 248, 248, 0 }, + { 210, 172, 226, 1 }, }, + { { 28, 116, 60, 1 }, + { 221, 128, 86, 1 }, + { 158, 23, 28, 0 }, + { 181, 0, 221, 1 }, }, + { { 25, 29, 145, 1 }, + { 157, 88, 97, 0 }, + { 196, 220, 76, 0 }, + { 67, 13, 92, 1 }, }, + { { 22, 27, 35, 1 }, + { 44, 202, 197, 0 }, + { 226, 108, 52, 0 }, + { 81, 169, 154, 0 }, }, + { { 22, 210, 141, 1 }, + { 110, 64, 244, 1 }, + { 216, 165, 180, 0 }, + { 151, 129, 59, 0 }, }, + { { 23, 78, 198, 1 }, + { 109, 30, 180, 0 }, + { 177, 185, 116, 0 }, + { 22, 188, 91, 0 }, }, + { { 19, 226, 177, 0 }, + { 118, 208, 162, 0 }, + { 70, 163, 228, 0 }, + { 34, 133, 183, 0 }, }, + { { 31, 126, 250, 1 }, + { 253, 158, 230, 1 }, + { 175, 191, 124, 0 }, + { 179, 188, 223, 1 }, }, + { { 41, 15, 112, 0 }, + { 177, 188, 1, 0 }, + { 7, 120, 74, 0 }, + { 64, 30, 198, 1 }, }, + { { 34, 71, 126, 1 }, + { 121, 166, 145, 1 }, + { 191, 113, 34, 0 }, + { 196, 178, 207, 0 }, }, + { { 42, 23, 192, 1 }, + { 169, 36, 225, 0 }, + { 129, 244, 42, 0 }, + { 67, 146, 74, 1 }, }, + { { 42, 251, 100, 1 }, + { 234, 172, 211, 0 }, + { 147, 111, 170, 0 }, + { 101, 154, 171, 1 }, }, + { { 35, 240, 4, 0 }, + { 66, 48, 210, 0 }, + { 16, 7, 226, 0 }, + { 37, 134, 33, 0 }, }, + { { 39, 205, 252, 0 }, + { 83, 188, 181, 1 }, + { 31, 217, 242, 0 }, + { 214, 158, 229, 0 }, }, + { { 35, 106, 172, 0 }, + { 96, 184, 178, 1 }, + { 26, 171, 98, 0 }, + { 166, 142, 131, 0 }, }, + { { 47, 123, 47, 0 }, + { 224, 250, 215, 1 }, + { 122, 111, 122, 0 }, + { 245, 175, 131, 1 }, }, + { { 52, 144, 196, 0 }, + { 6, 36, 116, 0 }, + { 17, 132, 150, 0 }, + { 23, 18, 48, 0 }, }, + { { 48, 9, 214, 1 }, + { 28, 46, 49, 0 }, + { 181, 200, 6, 0 }, + { 70, 58, 28, 0 }, }, + { { 52, 94, 16, 1 }, + { 125, 40, 68, 0 }, + { 132, 61, 22, 0 }, + { 17, 10, 95, 0 }, }, + { { 57, 82, 232, 0 }, + { 228, 180, 96, 1 }, + { 11, 165, 78, 0 }, + { 131, 22, 147, 1 }, }, + { { 50, 0, 113, 0 }, + { 20, 228, 128, 0 }, + { 71, 0, 38, 0 }, + { 0, 147, 148, 0 }, }, + { { 54, 231, 29, 0 }, + { 119, 96, 151, 1 }, + { 92, 115, 182, 0 }, + { 244, 131, 119, 0 }, }, + { { 68, 138, 4, 1 }, + { 42, 9, 20, 0 }, + { 144, 40, 145, 0 }, + { 20, 72, 42, 0 }, }, + { { 68, 236, 254, 1 }, + { 91, 143, 54, 1 }, + { 191, 155, 145, 0 }, + { 182, 120, 237, 0 }, }, + { { 69, 31, 114, 1 }, + { 57, 159, 69, 0 }, + { 167, 124, 81, 0 }, + { 81, 124, 206, 0 }, }, + { { 69, 74, 149, 0 }, + { 112, 89, 52, 0 }, + { 84, 169, 81, 0 }, + { 22, 77, 7, 0 }, }, + { { 70, 19, 190, 1 }, + { 56, 131, 245, 1 }, + { 190, 228, 49, 0 }, + { 215, 224, 142, 0 }, }, + { { 78, 131, 9, 0 }, + { 162, 65, 133, 1 }, + { 72, 96, 185, 0 }, + { 208, 193, 34, 1 }, }, + { { 71, 114, 153, 1 }, + { 120, 81, 230, 1 }, + { 204, 167, 113, 0 }, + { 179, 197, 15, 0 }, }, + { { 79, 130, 125, 1 }, + { 186, 213, 148, 1 }, + { 223, 32, 249, 0 }, + { 148, 213, 174, 1 }, }, + { { 92, 55, 141, 1 }, + { 173, 65, 119, 1 }, + { 216, 246, 29, 0 }, + { 247, 65, 90, 1 }, }, + { { 88, 253, 119, 0 }, + { 215, 207, 83, 0 }, + { 119, 95, 141, 0 }, + { 101, 121, 245, 1 }, }, + { { 81, 183, 248, 0 }, + { 55, 149, 99, 1 }, + { 15, 246, 197, 0 }, + { 227, 84, 246, 0 }, }, + { { 89, 58, 251, 0 }, + { 180, 223, 98, 1 }, + { 111, 174, 77, 0 }, + { 163, 125, 150, 1 }, }, + { { 90, 153, 153, 0 }, + { 150, 73, 225, 1 }, + { 76, 204, 173, 0 }, + { 195, 201, 52, 1 }, }, + { { 83, 1, 240, 1 }, + { 28, 149, 161, 0 }, + { 135, 192, 101, 0 }, + { 66, 212, 156, 0 }, }, + { { 83, 204, 103, 1 }, + { 79, 223, 144, 0 }, + { 243, 25, 229, 0 }, + { 4, 253, 249, 0 }, }, + { { 83, 110, 1, 0 }, + { 101, 89, 130, 0 }, + { 64, 59, 101, 0 }, + { 32, 205, 83, 0 }, }, + { { 91, 37, 175, 1 }, + { 141, 211, 179, 1 }, + { 250, 210, 109, 0 }, + { 230, 229, 216, 1 }, }, + { { 95, 213, 204, 1 }, + { 207, 21, 245, 1 }, + { 153, 213, 253, 0 }, + { 215, 212, 121, 1 }, }, + { { 101, 102, 66, 1 }, + { 105, 55, 6, 0 }, + { 161, 51, 83, 0 }, + { 48, 118, 75, 0 }, }, + { { 101, 199, 175, 0 }, + { 99, 243, 53, 1 }, + { 122, 241, 211, 0 }, + { 214, 103, 227, 0 }, }, + { { 102, 55, 81, 0 }, + { 49, 101, 199, 0 }, + { 69, 118, 51, 0 }, + { 113, 211, 70, 0 }, }, + { { 103, 12, 197, 1 }, + { 9, 125, 180, 0 }, + { 209, 152, 115, 0 }, + { 22, 223, 72, 0 }, }, + { { 112, 150, 93, 1 }, + { 63, 101, 80, 1 }, + { 221, 52, 135, 0 }, + { 133, 83, 126, 0 }, }, + { { 124, 99, 128, 1 }, + { 236, 33, 39, 0 }, + { 128, 227, 31, 0 }, + { 114, 66, 27, 1 }, }, + { { 121, 171, 169, 0 }, + { 166, 249, 35, 1 }, + { 74, 234, 207, 0 }, + { 226, 79, 178, 1 }, }, + { { 114, 61, 70, 0 }, + { 5, 47, 211, 0 }, + { 49, 94, 39, 0 }, + { 101, 250, 80, 0 }, }, + { { 115, 229, 178, 1 }, + { 95, 179, 163, 0 }, + { 166, 211, 231, 0 }, + { 98, 230, 253, 0 }, }, + { { 132, 38, 140, 0 }, + { 33, 0, 62, 1 }, + { 24, 178, 16, 1 }, + { 190, 0, 66, 0 }, }, + { { 137, 241, 1, 0 }, + { 194, 80, 75, 0 }, + { 64, 71, 200, 1 }, + { 105, 5, 33, 1 }, }, + { { 134, 174, 233, 1 }, + { 43, 204, 174, 1 }, + { 203, 186, 176, 1 }, + { 186, 153, 234, 0 }, }, + { { 138, 35, 249, 1 }, + { 184, 196, 171, 1 }, + { 207, 226, 40, 1 }, + { 234, 145, 142, 1 }, }, + { { 148, 53, 113, 1 }, + { 29, 196, 79, 0 }, + { 199, 86, 20, 1 }, + { 121, 17, 220, 0 }, }, + { { 156, 69, 27, 0 }, + { 213, 66, 13, 1 }, + { 108, 81, 28, 1 }, + { 216, 33, 85, 1 }, }, + { { 149, 31, 164, 1 }, + { 45, 152, 125, 0 }, + { 146, 252, 84, 1 }, + { 95, 12, 218, 0 }, }, + { { 150, 141, 144, 0 }, + { 23, 8, 173, 0 }, + { 4, 216, 180, 1 }, + { 90, 136, 116, 0 }, }, + { { 151, 38, 183, 0 }, + { 53, 210, 190, 0 }, + { 118, 178, 116, 1 }, + { 62, 165, 214, 0 }, }, + { { 155, 55, 103, 0 }, + { 165, 214, 219, 0 }, + { 115, 118, 108, 1 }, + { 109, 181, 210, 1 }, }, + { { 155, 198, 224, 1 }, + { 239, 148, 168, 0 }, + { 131, 177, 236, 1 }, + { 10, 148, 251, 1 }, }, + { { 160, 154, 110, 1 }, + { 42, 174, 88, 1 }, + { 187, 44, 130, 1 }, + { 141, 58, 170, 0 }, }, + { { 164, 82, 134, 1 }, + { 104, 34, 124, 0 }, + { 176, 165, 18, 1 }, + { 31, 34, 11, 0 }, }, + { { 168, 176, 133, 1 }, + { 138, 96, 122, 0 }, + { 208, 134, 138, 1 }, + { 47, 3, 40, 1 }, }, + { { 173, 155, 66, 0 }, + { 162, 62, 77, 0 }, + { 33, 108, 218, 1 }, + { 89, 62, 34, 1 }, }, + { { 175, 240, 88, 1 }, + { 218, 52, 206, 1 }, + { 141, 7, 250, 1 }, + { 185, 150, 45, 1 }, }, + { { 176, 46, 99, 1 }, + { 45, 238, 10, 0 }, + { 227, 58, 6, 1 }, + { 40, 59, 218, 0 }, }, + { { 176, 15, 24, 0 }, + { 53, 40, 9, 1 }, + { 12, 120, 6, 1 }, + { 200, 10, 86, 0 }, }, + { { 188, 234, 178, 1 }, + { 254, 170, 46, 0 }, + { 166, 171, 158, 1 }, + { 58, 42, 191, 1 }, }, + { { 190, 196, 76, 1 }, + { 207, 36, 156, 1 }, + { 153, 17, 190, 1 }, + { 156, 146, 121, 1 }, }, + { { 179, 130, 250, 0 }, + { 54, 182, 168, 1 }, + { 47, 160, 230, 1 }, + { 138, 182, 182, 0 }, }, + { { 179, 88, 60, 1 }, + { 92, 184, 216, 1 }, + { 158, 13, 102, 1 }, + { 141, 142, 157, 0 }, }, + { { 191, 69, 238, 0 }, + { 197, 182, 189, 1 }, + { 59, 209, 126, 1 }, + { 222, 182, 209, 1 }, }, + { { 191, 86, 51, 1 }, + { 253, 242, 204, 0 }, + { 230, 53, 126, 1 }, + { 25, 167, 223, 1 }, }, + { { 196, 58, 240, 0 }, + { 48, 141, 110, 0 }, + { 7, 174, 17, 1 }, + { 59, 88, 134, 0 }, }, + { { 204, 233, 131, 0 }, + { 194, 75, 47, 0 }, + { 96, 203, 153, 1 }, + { 122, 105, 33, 1 }, }, + { { 197, 51, 0, 1 }, + { 40, 17, 79, 0 }, + { 128, 102, 81, 1 }, + { 121, 68, 10, 0 }, }, + { { 205, 34, 99, 1 }, + { 168, 215, 14, 0 }, + { 227, 34, 89, 1 }, + { 56, 117, 138, 1 }, }, + { { 198, 8, 138, 1 }, + { 8, 11, 172, 1 }, + { 168, 136, 49, 1 }, + { 154, 232, 8, 0 }, }, + { { 198, 148, 63, 0 }, + { 19, 195, 220, 1 }, + { 126, 20, 177, 1 }, + { 157, 225, 228, 0 }, }, + { { 202, 238, 132, 1 }, + { 235, 9, 186, 0 }, + { 144, 187, 169, 1 }, + { 46, 200, 107, 1 }, }, + { { 207, 167, 18, 1 }, + { 187, 19, 143, 0 }, + { 164, 114, 249, 1 }, + { 120, 228, 110, 1 }, }, + { { 203, 97, 226, 1 }, + { 200, 151, 171, 0 }, + { 163, 195, 105, 1 }, + { 106, 244, 137, 1 }, }, + { { 208, 2, 233, 0 }, + { 36, 197, 40, 1 }, + { 75, 160, 5, 1 }, + { 138, 81, 146, 0 }, }, + { { 220, 183, 70, 0 }, + { 167, 7, 95, 0 }, + { 49, 118, 157, 1 }, + { 125, 112, 114, 1 }, }, + { { 217, 139, 132, 1 }, + { 174, 25, 57, 0 }, + { 144, 232, 205, 1 }, + { 78, 76, 58, 1 }, }, + { { 217, 206, 178, 0 }, + { 247, 155, 40, 0 }, + { 38, 185, 205, 1 }, + { 10, 108, 247, 1 }, }, + { { 210, 153, 64, 1 }, + { 14, 13, 201, 0 }, + { 129, 76, 165, 1 }, + { 73, 216, 56, 0 }, }, + { { 210, 209, 23, 0 }, + { 86, 67, 217, 0 }, + { 116, 69, 165, 1 }, + { 77, 225, 53, 0 }, }, + { { 222, 90, 110, 1 }, + { 236, 143, 220, 1 }, + { 187, 45, 61, 1 }, + { 157, 248, 155, 1 }, }, + { { 218, 71, 66, 1 }, + { 237, 7, 137, 0 }, + { 161, 113, 45, 1 }, + { 72, 240, 91, 1 }, }, + { { 219, 60, 16, 0 }, + { 149, 25, 202, 0 }, + { 4, 30, 109, 1 }, + { 41, 204, 84, 1 }, }, + { { 219, 143, 201, 0 }, + { 167, 93, 169, 1 }, + { 73, 248, 237, 1 }, + { 202, 221, 114, 1 }, }, + { { 232, 24, 227, 1 }, + { 136, 239, 104, 0 }, + { 227, 140, 11, 1 }, + { 11, 123, 136, 1 }, }, + { { 229, 28, 111, 0 }, + { 1, 255, 92, 1 }, + { 123, 28, 83, 1 }, + { 157, 127, 192, 0 }, }, + { { 225, 174, 56, 1 }, + { 59, 185, 10, 1 }, + { 142, 58, 195, 1 }, + { 168, 78, 238, 0 }, }, + { { 237, 185, 123, 1 }, + { 154, 255, 79, 1 }, + { 239, 78, 219, 1 }, + { 249, 127, 172, 1 }, }, + { { 233, 224, 50, 0 }, + { 210, 179, 10, 0 }, + { 38, 3, 203, 1 }, + { 40, 102, 165, 1 }, }, + { { 235, 93, 12, 0 }, + { 193, 57, 217, 1 }, + { 24, 93, 107, 1 }, + { 205, 206, 65, 1 }, }, + { { 239, 249, 157, 0 }, + { 210, 121, 255, 1 }, + { 92, 207, 251, 1 }, + { 255, 207, 37, 1 }, }, + { { 248, 17, 1, 0 }, + { 132, 97, 73, 0 }, + { 64, 68, 15, 1 }, + { 73, 67, 16, 1 }, }, + { { 248, 7, 211, 0 }, + { 181, 103, 41, 0 }, + { 101, 240, 15, 1 }, + { 74, 115, 86, 1 }, }, + { { 246, 2, 32, 1 }, + { 44, 161, 140, 0 }, + { 130, 32, 55, 1 }, + { 24, 194, 154, 0 }, }, + { { 246, 27, 95, 1 }, + { 60, 111, 221, 1 }, + { 253, 108, 55, 1 }, + { 221, 251, 30, 0 }, }, + { { 254, 181, 237, 0 }, + { 135, 229, 255, 1 }, + { 91, 214, 191, 1 }, + { 255, 211, 240, 1 }, }, + { { 250, 51, 56, 1 }, + { 188, 161, 203, 1 }, + { 142, 102, 47, 1 }, + { 233, 194, 158, 1 }, }, + { { 250, 74, 193, 1 }, + { 236, 109, 168, 0 }, + { 193, 169, 47, 1 }, + { 10, 219, 27, 1 }, }, + { { 247, 47, 112, 1 }, + { 61, 189, 143, 0 }, + { 135, 122, 119, 1 }, + { 120, 222, 222, 0 }, }, + { { 247, 234, 252, 1 }, + { 126, 189, 190, 1 }, + { 159, 171, 247, 1 }, + { 190, 222, 191, 0 }, }, + { { 255, 24, 148, 1 }, + { 156, 57, 252, 0 }, + { 148, 140, 127, 1 }, + { 31, 206, 28, 1 }, }, + { { 251, 163, 94, 0 }, + { 182, 55, 155, 1 }, + { 61, 98, 239, 1 }, + { 236, 246, 54, 1 }, }, + { { 104, 184, 47, 0 }, + { 130, 235, 82, 1 }, + { 122, 14, 139, 0 }, + { 165, 107, 160, 1 }, }, + { { 153, 15, 11, 1 }, + { 173, 90, 9, 1 }, + { 232, 120, 76, 1 }, + { 200, 45, 90, 1 }, }, + { { 153, 216, 38, 1 }, + { 206, 154, 88, 0 }, + { 178, 13, 204, 1 }, + { 13, 44, 185, 1 }, }, + { { 228, 95, 14, 1 }, + { 105, 43, 93, 1 }, + { 184, 125, 19, 1 }, + { 221, 106, 75, 0 }, }, + { { 29, 16, 110, 1 }, + { 140, 150, 84, 1 }, + { 187, 4, 92, 0 }, + { 149, 52, 152, 1 }, }, + { { 42, 193, 48, 1 }, + { 218, 160, 129, 0 }, + { 134, 65, 170, 0 }, + { 64, 130, 173, 1 }, }, + { { 52, 65, 99, 1 }, + { 76, 230, 5, 0 }, + { 227, 65, 22, 0 }, + { 80, 51, 153, 0 }, }, + { { 55, 192, 116, 1 }, + { 94, 180, 148, 0 }, + { 151, 1, 246, 0 }, + { 20, 150, 189, 0 }, }, + { { 63, 53, 203, 1 }, + { 141, 118, 231, 1 }, + { 233, 214, 126, 0 }, + { 243, 183, 88, 1 }, }, + { { 86, 160, 76, 0 }, + { 6, 5, 150, 1 }, + { 25, 2, 181, 0 }, + { 180, 208, 48, 0 }, }, + { { 87, 56, 57, 1 }, + { 28, 217, 198, 1 }, + { 206, 14, 117, 0 }, + { 177, 205, 156, 0 }, }, + { { 102, 152, 184, 0 }, + { 18, 169, 228, 1 }, + { 14, 140, 179, 0 }, + { 147, 202, 164, 0 }, }, + { { 115, 165, 23, 0 }, + { 23, 115, 147, 0 }, + { 116, 82, 231, 0 }, + { 100, 231, 116, 0 }, }, + { { 127, 44, 253, 1 }, + { 157, 253, 182, 1 }, + { 223, 154, 127, 0 }, + { 182, 223, 220, 1 }, }, + { { 139, 71, 233, 0 }, + { 225, 212, 169, 1 }, + { 75, 241, 104, 1 }, + { 202, 149, 195, 1 }, }, + { { 165, 195, 151, 0 }, + { 114, 114, 61, 0 }, + { 116, 225, 210, 1 }, + { 94, 39, 39, 0 }, }, + { { 169, 169, 235, 0 }, + { 130, 254, 43, 1 }, + { 107, 202, 202, 1 }, + { 234, 63, 160, 1 }, }, + { { 181, 25, 183, 0 }, + { 20, 250, 125, 0 }, + { 118, 204, 86, 1 }, + { 95, 47, 148, 0 }, }, + { { 178, 218, 153, 1 }, + { 126, 104, 232, 1 }, + { 204, 173, 166, 1 }, + { 139, 139, 63, 0 }, }, + { { 196, 193, 244, 0 }, + { 82, 133, 61, 0 }, + { 23, 193, 145, 1 }, + { 94, 80, 165, 0 }, }, + { { 202, 185, 30, 0 }, + { 146, 11, 219, 1 }, + { 60, 78, 169, 1 }, + { 237, 232, 36, 1 }, }, + { { 216, 111, 163, 1 }, + { 237, 203, 43, 0 }, + { 226, 251, 13, 1 }, + { 106, 105, 219, 1 }, }, + { { 223, 141, 142, 0 }, + { 135, 27, 189, 1 }, + { 56, 216, 253, 1 }, + { 222, 236, 112, 1 }, }, + { { 229, 102, 143, 1 }, + { 105, 115, 62, 1 }, + { 248, 179, 83, 1 }, + { 190, 103, 75, 0 }, }, + { { 237, 71, 26, 1 }, + { 249, 51, 13, 1 }, + { 172, 113, 91, 1 }, + { 216, 102, 79, 1 }, }, + { { 240, 103, 134, 1 }, + { 109, 35, 59, 0 }, + { 176, 243, 7, 1 }, + { 110, 98, 91, 0 }, }, + { { 4, 41, 238, 1 }, + { 8, 142, 55, 1 }, + { 187, 202, 16, 0 }, + { 246, 56, 136, 0 }, }, + { { 0, 35, 71, 1 }, + { 40, 70, 19, 0 }, + { 241, 98, 0, 0 }, + { 100, 49, 10, 0 }, }, + { { 0, 162, 251, 0 }, + { 50, 198, 34, 1 }, + { 111, 162, 128, 0 }, + { 162, 49, 166, 0 }, }, + { { 4, 200, 206, 0 }, + { 66, 14, 52, 1 }, + { 57, 137, 144, 0 }, + { 150, 56, 33, 0 }, }, + { { 0, 210, 225, 1 }, + { 106, 196, 96, 0 }, + { 195, 165, 128, 0 }, + { 3, 17, 171, 0 }, }, + { { 12, 10, 15, 0 }, + { 160, 74, 20, 1 }, + { 120, 40, 24, 0 }, + { 148, 41, 2, 1 }, }, + { { 8, 139, 198, 0 }, + { 162, 14, 49, 0 }, + { 49, 232, 136, 0 }, + { 70, 56, 34, 1 }, }, + { { 12, 134, 91, 0 }, + { 179, 70, 4, 1 }, + { 109, 48, 152, 0 }, + { 144, 49, 102, 1 }, }, + { { 5, 129, 253, 0 }, + { 18, 212, 53, 1 }, + { 95, 192, 208, 0 }, + { 214, 21, 164, 0 }, }, + { { 1, 113, 30, 0 }, + { 80, 18, 83, 1 }, + { 60, 71, 64, 0 }, + { 229, 36, 5, 0 }, }, + { { 1, 194, 21, 1 }, + { 122, 80, 16, 0 }, + { 212, 33, 192, 0 }, + { 4, 5, 47, 0 }, }, + { { 1, 231, 113, 0 }, + { 115, 212, 3, 0 }, + { 71, 115, 192, 0 }, + { 96, 21, 231, 0 }, }, + { { 9, 41, 191, 0 }, + { 144, 218, 51, 1 }, + { 126, 202, 72, 0 }, + { 230, 45, 132, 1 }, }, + { { 13, 16, 27, 1 }, + { 152, 82, 68, 1 }, + { 236, 4, 88, 0 }, + { 145, 37, 12, 1 }, }, + { { 13, 5, 24, 0 }, + { 145, 16, 5, 1 }, + { 12, 80, 88, 0 }, + { 208, 4, 68, 1 }, }, + { { 13, 237, 47, 1 }, + { 203, 218, 23, 1 }, + { 250, 91, 216, 0 }, + { 244, 45, 233, 1 }, }, + { { 13, 102, 222, 0 }, + { 241, 22, 54, 1 }, + { 61, 179, 88, 0 }, + { 182, 52, 71, 1 }, }, + { { 9, 242, 118, 0 }, + { 242, 150, 82, 0 }, + { 55, 39, 200, 0 }, + { 37, 52, 167, 1 }, }, + { { 2, 208, 130, 1 }, + { 74, 2, 224, 0 }, + { 160, 133, 160, 0 }, + { 3, 160, 41, 0 }, }, + { { 2, 98, 201, 0 }, + { 96, 68, 162, 1 }, + { 73, 163, 32, 0 }, + { 162, 145, 3, 0 }, }, + { { 6, 243, 169, 0 }, + { 98, 192, 231, 1 }, + { 74, 231, 176, 0 }, + { 243, 129, 163, 0 }, }, + { { 6, 239, 35, 0 }, + { 99, 202, 135, 0 }, + { 98, 123, 176, 0 }, + { 112, 169, 227, 0 }, }, + { { 14, 62, 18, 0 }, + { 177, 10, 198, 0 }, + { 36, 62, 56, 0 }, + { 49, 168, 70, 1 }, }, + { { 10, 154, 221, 1 }, + { 186, 76, 240, 1 }, + { 221, 172, 168, 0 }, + { 135, 153, 46, 1 }, }, + { { 10, 97, 109, 1 }, + { 200, 196, 147, 1 }, + { 219, 67, 40, 0 }, + { 228, 145, 137, 1 }, }, + { { 10, 255, 81, 0 }, + { 243, 76, 195, 0 }, + { 69, 127, 168, 0 }, + { 97, 153, 103, 1 }, }, + { { 7, 230, 114, 1 }, + { 123, 150, 134, 0 }, + { 167, 51, 240, 0 }, + { 48, 180, 239, 0 }, }, + { { 11, 154, 1, 0 }, + { 162, 88, 192, 0 }, + { 64, 44, 232, 0 }, + { 1, 141, 34, 1 }, }, + { { 15, 208, 142, 0 }, + { 194, 18, 244, 1 }, + { 56, 133, 248, 0 }, + { 151, 164, 33, 1 }, }, + { { 15, 73, 147, 0 }, + { 208, 90, 165, 0 }, + { 100, 201, 120, 0 }, + { 82, 173, 5, 1 }, }, + { { 15, 247, 123, 0 }, + { 243, 214, 199, 1 }, + { 111, 119, 248, 0 }, + { 241, 181, 231, 1 }, }, + { { 16, 158, 96, 1 }, + { 47, 140, 64, 0 }, + { 131, 60, 132, 0 }, + { 1, 24, 250, 0 }, }, + { { 16, 81, 237, 0 }, + { 68, 196, 113, 1 }, + { 91, 197, 4, 0 }, + { 199, 17, 145, 0 }, }, + { { 20, 122, 222, 1 }, + { 124, 14, 118, 1 }, + { 189, 175, 20, 0 }, + { 183, 56, 31, 0 }, }, + { { 28, 133, 62, 1 }, + { 159, 130, 21, 1 }, + { 190, 80, 156, 0 }, + { 212, 32, 252, 1 }, }, + { { 28, 35, 104, 0 }, + { 164, 132, 7, 1 }, + { 11, 98, 28, 0 }, + { 240, 16, 146, 1 }, }, + { { 24, 199, 52, 0 }, + { 247, 128, 17, 0 }, + { 22, 113, 140, 0 }, + { 68, 0, 247, 1 }, }, + { { 21, 251, 130, 1 }, + { 110, 26, 103, 0 }, + { 160, 239, 212, 0 }, + { 115, 44, 59, 0 }, }, + { { 17, 78, 147, 1 }, + { 125, 90, 32, 0 }, + { 228, 185, 68, 0 }, + { 2, 45, 95, 0 }, }, + { { 25, 160, 234, 1 }, + { 142, 150, 34, 1 }, + { 171, 130, 204, 0 }, + { 162, 52, 184, 1 }, }, + { { 18, 24, 76, 0 }, + { 4, 12, 208, 1 }, + { 25, 12, 36, 0 }, + { 133, 152, 16, 0 }, }, + { { 18, 163, 174, 0 }, + { 38, 130, 179, 1 }, + { 58, 226, 164, 0 }, + { 230, 160, 178, 0 }, }, + { { 30, 172, 60, 0 }, + { 151, 136, 150, 1 }, + { 30, 26, 188, 0 }, + { 180, 136, 244, 1 }, }, + { { 30, 201, 108, 0 }, + { 198, 140, 149, 1 }, + { 27, 73, 188, 0 }, + { 212, 152, 177, 1 }, }, + { { 30, 233, 163, 1 }, + { 206, 202, 167, 0 }, + { 226, 203, 188, 0 }, + { 114, 169, 185, 1 }, }, + { { 26, 118, 216, 0 }, + { 245, 4, 226, 1 }, + { 13, 183, 44, 0 }, + { 163, 144, 87, 1 }, }, + { { 19, 182, 7, 1 }, + { 47, 82, 210, 0 }, + { 240, 54, 228, 0 }, + { 37, 165, 122, 0 }, }, + { { 23, 70, 188, 0 }, + { 117, 144, 180, 1 }, + { 30, 177, 116, 0 }, + { 150, 132, 215, 0 }, }, + { { 31, 21, 121, 0 }, + { 149, 212, 197, 1 }, + { 79, 84, 124, 0 }, + { 209, 149, 212, 1 }, }, + { { 27, 35, 125, 0 }, + { 180, 212, 147, 1 }, + { 95, 98, 108, 0 }, + { 228, 149, 150, 1 }, }, + { { 27, 191, 146, 0 }, + { 183, 26, 227, 0 }, + { 36, 254, 236, 0 }, + { 99, 172, 118, 1 }, }, + { { 31, 150, 154, 1 }, + { 191, 18, 228, 1 }, + { 172, 180, 252, 0 }, + { 147, 164, 126, 1 }, }, + { { 27, 72, 235, 0 }, + { 196, 222, 160, 1 }, + { 107, 137, 108, 0 }, + { 130, 189, 145, 1 }, }, + { { 32, 12, 253, 0 }, + { 17, 236, 48, 1 }, + { 95, 152, 2, 0 }, + { 134, 27, 196, 0 }, }, + { { 32, 23, 86, 0 }, + { 49, 38, 81, 0 }, + { 53, 116, 2, 0 }, + { 69, 50, 70, 0 }, }, + { { 40, 1, 153, 1 }, + { 152, 96, 33, 1 }, + { 204, 192, 10, 0 }, + { 194, 3, 12, 1 }, }, + { { 44, 50, 55, 1 }, + { 184, 226, 86, 0 }, + { 246, 38, 26, 0 }, + { 53, 35, 142, 1 }, }, + { { 37, 140, 230, 0 }, + { 3, 190, 52, 0 }, + { 51, 152, 210, 0 }, + { 22, 62, 224, 0 }, }, + { { 33, 30, 140, 0 }, + { 33, 56, 112, 1 }, + { 24, 188, 66, 0 }, + { 135, 14, 66, 0 }, }, + { { 45, 125, 105, 0 }, + { 193, 252, 71, 1 }, + { 75, 95, 90, 0 }, + { 241, 31, 193, 1 }, }, + { { 41, 192, 73, 0 }, + { 194, 116, 0, 1 }, + { 73, 1, 202, 0 }, + { 128, 23, 33, 1 }, }, + { { 38, 177, 32, 0 }, + { 2, 160, 199, 0 }, + { 2, 70, 178, 0 }, + { 113, 130, 160, 0 }, }, + { { 38, 128, 28, 1 }, + { 26, 32, 148, 1 }, + { 156, 0, 178, 0 }, + { 148, 130, 44, 0 }, }, + { { 34, 180, 26, 0 }, + { 19, 34, 194, 1 }, + { 44, 22, 162, 0 }, + { 161, 162, 100, 0 }, }, + { { 38, 42, 68, 1 }, + { 40, 44, 150, 0 }, + { 145, 42, 50, 0 }, + { 52, 154, 10, 0 }, }, + { { 38, 171, 89, 0 }, + { 50, 108, 135, 1 }, + { 77, 106, 178, 0 }, + { 240, 155, 38, 0 }, }, + { { 38, 201, 132, 0 }, + { 66, 40, 181, 0 }, + { 16, 201, 178, 0 }, + { 86, 138, 33, 0 }, }, + { { 42, 48, 98, 1 }, + { 136, 166, 194, 0 }, + { 163, 6, 42, 0 }, + { 33, 178, 136, 1 }, }, + { { 46, 50, 173, 0 }, + { 160, 224, 246, 1 }, + { 90, 166, 58, 0 }, + { 183, 131, 130, 1 }, }, + { { 35, 63, 1, 1 }, + { 41, 120, 195, 0 }, + { 192, 126, 98, 0 }, + { 97, 143, 74, 0 }, }, + { { 35, 131, 80, 0 }, + { 50, 52, 129, 0 }, + { 5, 96, 226, 0 }, + { 64, 150, 38, 0 }, }, + { { 39, 100, 46, 1 }, + { 73, 178, 150, 1 }, + { 186, 19, 114, 0 }, + { 180, 166, 201, 0 }, }, + { { 39, 212, 176, 1 }, + { 91, 176, 228, 0 }, + { 134, 149, 242, 0 }, + { 19, 134, 237, 0 }, }, + { { 47, 186, 44, 1 }, + { 170, 184, 214, 1 }, + { 154, 46, 250, 0 }, + { 181, 142, 170, 1 }, }, + { { 43, 38, 186, 1 }, + { 185, 178, 162, 1 }, + { 174, 178, 106, 0 }, + { 162, 166, 206, 1 }, }, + { { 48, 11, 141, 0 }, + { 36, 104, 49, 1 }, + { 88, 232, 6, 0 }, + { 198, 11, 18, 0 }, }, + { { 52, 38, 146, 1 }, + { 61, 34, 38, 0 }, + { 164, 178, 22, 0 }, + { 50, 34, 94, 0 }, }, + { { 48, 130, 25, 0 }, + { 54, 96, 0, 1 }, + { 76, 32, 134, 0 }, + { 128, 3, 54, 0 }, }, + { { 48, 123, 44, 1 }, + { 108, 168, 83, 1 }, + { 154, 111, 6, 0 }, + { 229, 10, 155, 0 }, }, + { { 60, 184, 59, 1 }, + { 158, 234, 70, 1 }, + { 238, 14, 158, 0 }, + { 177, 43, 188, 1 }, }, + { { 60, 38, 15, 1 }, + { 173, 98, 22, 1 }, + { 248, 50, 30, 0 }, + { 180, 35, 90, 1 }, }, + { { 56, 233, 220, 0 }, + { 214, 44, 51, 1 }, + { 29, 203, 142, 0 }, + { 230, 26, 53, 1 }, }, + { { 60, 192, 247, 0 }, + { 214, 230, 52, 0 }, + { 119, 129, 158, 0 }, + { 22, 51, 181, 1 }, }, + { { 56, 247, 84, 1 }, + { 255, 36, 83, 0 }, + { 149, 119, 142, 0 }, + { 101, 18, 127, 1 }, }, + { { 49, 5, 180, 1 }, + { 29, 176, 49, 0 }, + { 150, 208, 70, 0 }, + { 70, 6, 220, 0 }, }, + { { 53, 184, 254, 1 }, + { 30, 190, 118, 1 }, + { 191, 142, 214, 0 }, + { 183, 62, 188, 0 }, }, + { { 53, 178, 211, 1 }, + { 62, 118, 102, 0 }, + { 229, 166, 214, 0 }, + { 51, 55, 62, 0 }, }, + { { 57, 155, 206, 1 }, + { 174, 62, 113, 1 }, + { 185, 236, 206, 0 }, + { 199, 62, 58, 1 }, }, + { { 57, 109, 46, 0 }, + { 197, 186, 19, 1 }, + { 58, 91, 78, 0 }, + { 228, 46, 209, 1 }, }, + { { 61, 224, 131, 1 }, + { 206, 114, 38, 0 }, + { 224, 131, 222, 0 }, + { 50, 39, 57, 1 }, }, + { { 57, 244, 208, 0 }, + { 215, 52, 98, 0 }, + { 5, 151, 206, 0 }, + { 35, 22, 117, 1 }, }, + { { 57, 255, 32, 0 }, + { 231, 184, 67, 0 }, + { 2, 127, 206, 0 }, + { 97, 14, 243, 1 }, }, + { { 57, 199, 136, 1 }, + { 239, 48, 33, 1 }, + { 136, 241, 206, 0 }, + { 194, 6, 123, 1 }, }, + { { 54, 15, 36, 0 }, + { 37, 168, 149, 0 }, + { 18, 120, 54, 0 }, + { 84, 138, 210, 0 }, }, + { { 50, 30, 213, 0 }, + { 53, 108, 240, 0 }, + { 85, 188, 38, 0 }, + { 7, 155, 86, 0 }, }, + { { 54, 191, 209, 1 }, + { 63, 108, 231, 0 }, + { 197, 254, 182, 0 }, + { 115, 155, 126, 0 }, }, + { { 54, 70, 235, 0 }, + { 101, 230, 164, 1 }, + { 107, 177, 54, 0 }, + { 146, 179, 211, 0 }, }, + { { 58, 45, 168, 0 }, + { 133, 168, 163, 1 }, + { 10, 218, 46, 0 }, + { 226, 138, 208, 1 }, }, + { { 58, 147, 187, 0 }, + { 182, 226, 225, 1 }, + { 110, 228, 174, 0 }, + { 195, 163, 182, 1 }, }, + { { 55, 149, 173, 0 }, + { 7, 240, 245, 1 }, + { 90, 212, 246, 0 }, + { 215, 135, 240, 0 }, }, + { { 55, 4, 147, 0 }, + { 21, 114, 164, 0 }, + { 100, 144, 118, 0 }, + { 18, 167, 84, 0 }, }, + { { 51, 160, 216, 1 }, + { 30, 52, 162, 1 }, + { 141, 130, 230, 0 }, + { 162, 150, 60, 0 }, }, + { { 55, 67, 167, 0 }, + { 100, 242, 181, 0 }, + { 114, 225, 118, 0 }, + { 86, 167, 147, 0 }, }, + { { 51, 250, 92, 0 }, + { 118, 60, 210, 1 }, + { 29, 47, 230, 0 }, + { 165, 158, 55, 0 }, }, + { { 63, 131, 110, 0 }, + { 166, 182, 149, 1 }, + { 59, 96, 254, 0 }, + { 212, 182, 178, 1 }, }, + { { 63, 107, 96, 0 }, + { 228, 188, 135, 0 }, + { 3, 107, 126, 0 }, + { 112, 158, 147, 1 }, }, + { { 64, 225, 205, 0 }, + { 66, 69, 51, 1 }, + { 89, 195, 129, 0 }, + { 230, 81, 33, 0 }, }, + { { 76, 144, 234, 0 }, + { 130, 135, 100, 1 }, + { 43, 132, 153, 0 }, + { 147, 112, 160, 1 }, }, + { { 72, 154, 105, 1 }, + { 170, 205, 64, 1 }, + { 203, 44, 137, 0 }, + { 129, 89, 170, 1 }, }, + { { 76, 190, 247, 0 }, + { 179, 207, 118, 0 }, + { 119, 190, 153, 0 }, + { 55, 121, 230, 1 }, }, + { { 72, 235, 138, 1 }, + { 234, 11, 35, 1 }, + { 168, 235, 137, 0 }, + { 226, 104, 43, 1 }, }, + { { 72, 211, 7, 1 }, + { 234, 67, 81, 0 }, + { 240, 101, 137, 0 }, + { 69, 97, 43, 1 }, }, + { { 76, 90, 152, 1 }, + { 248, 9, 100, 1 }, + { 140, 173, 25, 0 }, + { 147, 72, 15, 1 }, }, + { { 65, 36, 19, 1 }, + { 25, 83, 2, 0 }, + { 228, 18, 65, 0 }, + { 32, 101, 76, 0 }, }, + { { 77, 11, 248, 0 }, + { 176, 157, 37, 1 }, + { 15, 232, 89, 0 }, + { 210, 92, 134, 1 }, }, + { { 73, 91, 189, 1 }, + { 248, 217, 113, 1 }, + { 222, 237, 73, 0 }, + { 199, 77, 143, 1 }, }, + { { 70, 141, 101, 0 }, + { 3, 205, 149, 0 }, + { 83, 88, 177, 0 }, + { 84, 217, 224, 0 }, }, + { { 66, 181, 107, 1 }, + { 11, 199, 195, 1 }, + { 235, 86, 161, 0 }, + { 225, 241, 232, 0 }, }, + { { 70, 92, 131, 0 }, + { 65, 75, 228, 0 }, + { 96, 157, 49, 0 }, + { 19, 233, 65, 0 }, }, + { { 70, 244, 103, 0 }, + { 67, 199, 214, 0 }, + { 115, 23, 177, 0 }, + { 53, 241, 225, 0 }, }, + { { 70, 121, 187, 0 }, + { 80, 203, 231, 1 }, + { 110, 207, 49, 0 }, + { 243, 233, 133, 0 }, }, + { { 74, 163, 157, 1 }, + { 186, 65, 179, 1 }, + { 220, 226, 169, 0 }, + { 230, 193, 46, 1 }, }, + { { 78, 65, 78, 1 }, + { 200, 7, 149, 1 }, + { 185, 65, 57, 0 }, + { 212, 240, 9, 1 }, }, + { { 78, 75, 194, 0 }, + { 224, 15, 165, 0 }, + { 33, 233, 57, 0 }, + { 82, 248, 3, 1 }, }, + { { 67, 137, 211, 1 }, + { 26, 95, 161, 0 }, + { 229, 200, 225, 0 }, + { 66, 253, 44, 0 }, }, + { { 71, 170, 200, 1 }, + { 42, 29, 166, 1 }, + { 137, 170, 241, 0 }, + { 178, 220, 42, 0 }, }, + { { 67, 59, 19, 0 }, + { 48, 91, 195, 0 }, + { 100, 110, 97, 0 }, + { 97, 237, 6, 0 }, }, + { { 71, 74, 105, 1 }, + { 104, 221, 132, 1 }, + { 203, 41, 113, 0 }, + { 144, 221, 139, 0 }, }, + { { 79, 22, 235, 1 }, + { 169, 215, 228, 1 }, + { 235, 180, 121, 0 }, + { 147, 245, 202, 1 }, }, + { { 79, 69, 160, 0 }, + { 193, 145, 165, 0 }, + { 2, 209, 121, 0 }, + { 82, 196, 193, 1 }, }, + { { 80, 145, 108, 1 }, + { 14, 133, 81, 1 }, + { 155, 68, 133, 0 }, + { 197, 80, 184, 0 }, }, + { { 84, 16, 187, 0 }, + { 20, 195, 100, 1 }, + { 110, 132, 21, 0 }, + { 147, 97, 148, 0 }, }, + { { 80, 90, 77, 1 }, + { 108, 77, 80, 1 }, + { 217, 45, 5, 0 }, + { 133, 89, 27, 0 }, }, + { { 80, 114, 212, 0 }, + { 116, 5, 114, 0 }, + { 21, 167, 5, 0 }, + { 39, 80, 23, 0 }, }, + { { 84, 234, 241, 1 }, + { 126, 205, 38, 0 }, + { 199, 171, 149, 0 }, + { 50, 89, 191, 0 }, }, + { { 88, 9, 195, 1 }, + { 140, 79, 33, 0 }, + { 225, 200, 13, 0 }, + { 66, 121, 24, 1 }, }, + { { 92, 89, 121, 0 }, + { 212, 205, 69, 1 }, + { 79, 77, 29, 0 }, + { 209, 89, 149, 1 }, }, + { { 88, 107, 27, 0 }, + { 244, 75, 3, 1 }, + { 108, 107, 13, 0 }, + { 224, 105, 23, 1 }, }, + { { 81, 52, 246, 1 }, + { 29, 151, 114, 0 }, + { 183, 150, 69, 0 }, + { 39, 116, 220, 0 }, }, + { { 85, 169, 118, 1 }, + { 30, 159, 23, 0 }, + { 183, 74, 213, 0 }, + { 116, 124, 188, 0 }, }, + { { 85, 151, 183, 1 }, + { 63, 211, 117, 0 }, + { 246, 244, 213, 0 }, + { 87, 101, 254, 0 }, }, + { { 85, 72, 67, 1 }, + { 76, 95, 4, 0 }, + { 225, 9, 85, 0 }, + { 16, 125, 25, 0 }, }, + { { 81, 203, 28, 0 }, + { 118, 25, 17, 1 }, + { 28, 105, 197, 0 }, + { 196, 76, 55, 0 }, }, + { { 89, 103, 105, 1 }, + { 237, 213, 3, 1 }, + { 203, 115, 77, 0 }, + { 224, 85, 219, 1 }, }, + { { 89, 87, 155, 0 }, + { 245, 83, 97, 1 }, + { 108, 245, 77, 0 }, + { 195, 101, 87, 1 }, }, + { { 86, 160, 171, 1 }, + { 14, 195, 166, 1 }, + { 234, 130, 181, 0 }, + { 178, 225, 184, 0 }, }, + { { 82, 76, 88, 0 }, + { 85, 13, 128, 1 }, + { 13, 25, 37, 0 }, + { 128, 216, 85, 0 }, }, + { { 86, 87, 68, 1 }, + { 109, 5, 213, 0 }, + { 145, 117, 53, 0 }, + { 85, 208, 91, 0 }, }, + { { 94, 1, 56, 0 }, + { 148, 129, 133, 1 }, + { 14, 64, 61, 0 }, + { 208, 192, 148, 1 }, }, + { { 90, 1, 255, 0 }, + { 148, 199, 177, 1 }, + { 127, 192, 45, 0 }, + { 198, 241, 148, 1 }, }, + { { 90, 53, 19, 1 }, + { 157, 67, 195, 0 }, + { 228, 86, 45, 0 }, + { 97, 225, 92, 1 }, }, + { { 94, 29, 215, 1 }, + { 157, 79, 245, 0 }, + { 245, 220, 61, 0 }, + { 87, 249, 92, 1 }, }, + { { 94, 146, 70, 1 }, + { 174, 7, 212, 0 }, + { 177, 36, 189, 0 }, + { 21, 240, 58, 1 }, }, + { { 90, 108, 162, 0 }, + { 197, 139, 162, 0 }, + { 34, 155, 45, 0 }, + { 34, 232, 209, 1 }, }, + { { 87, 57, 5, 0 }, + { 4, 89, 215, 0 }, + { 80, 78, 117, 0 }, + { 117, 205, 16, 0 }, }, + { { 87, 185, 238, 0 }, + { 6, 159, 247, 1 }, + { 59, 206, 245, 0 }, + { 247, 252, 176, 0 }, }, + { { 83, 27, 44, 1 }, + { 44, 153, 209, 1 }, + { 154, 108, 101, 0 }, + { 197, 204, 154, 0 }, }, + { { 87, 134, 8, 0 }, + { 39, 17, 132, 1 }, + { 8, 48, 245, 0 }, + { 144, 196, 114, 0 }, }, + { { 87, 179, 31, 1 }, + { 62, 83, 215, 1 }, + { 252, 102, 245, 0 }, + { 245, 229, 62, 0 }, }, + { { 87, 175, 125, 0 }, + { 55, 221, 151, 1 }, + { 95, 122, 245, 0 }, + { 244, 221, 246, 0 }, }, + { { 83, 240, 235, 0 }, + { 70, 215, 226, 1 }, + { 107, 135, 229, 0 }, + { 163, 245, 177, 0 }, }, + { { 91, 30, 236, 0 }, + { 165, 157, 240, 1 }, + { 27, 188, 109, 0 }, + { 135, 220, 210, 1 }, }, + { { 95, 6, 166, 1 }, + { 173, 147, 180, 0 }, + { 178, 176, 125, 0 }, + { 22, 228, 218, 1 }, }, + { { 95, 46, 87, 1 }, + { 189, 95, 150, 0 }, + { 245, 58, 125, 0 }, + { 52, 253, 94, 1 }, }, + { { 91, 89, 192, 0 }, + { 196, 29, 225, 0 }, + { 1, 205, 109, 0 }, + { 67, 220, 17, 1 }, }, + { { 91, 221, 125, 1 }, + { 223, 221, 209, 1 }, + { 223, 93, 237, 0 }, + { 197, 221, 253, 1 }, }, + { { 91, 78, 47, 0 }, + { 229, 219, 144, 1 }, + { 122, 57, 109, 0 }, + { 132, 237, 211, 1 }, }, + { { 95, 111, 137, 1 }, + { 237, 89, 167, 1 }, + { 200, 251, 125, 0 }, + { 242, 205, 91, 1 }, }, + { { 91, 99, 218, 0 }, + { 244, 23, 163, 1 }, + { 45, 227, 109, 0 }, + { 226, 244, 23, 1 }, }, + { { 96, 32, 36, 1 }, + { 8, 161, 18, 0 }, + { 146, 2, 3, 0 }, + { 36, 66, 136, 0 }, }, + { { 100, 167, 30, 1 }, + { 59, 35, 23, 1 }, + { 188, 114, 147, 0 }, + { 244, 98, 110, 0 }, }, + { { 96, 84, 84, 1 }, + { 89, 37, 80, 0 }, + { 149, 21, 3, 0 }, + { 5, 82, 77, 0 }, }, + { { 104, 166, 103, 0 }, + { 163, 231, 18, 0 }, + { 115, 50, 139, 0 }, + { 36, 115, 226, 1 }, }, + { { 108, 235, 188, 1 }, + { 250, 169, 55, 1 }, + { 158, 235, 155, 0 }, + { 246, 74, 175, 1 }, }, + { { 105, 70, 205, 1 }, + { 233, 117, 48, 1 }, + { 217, 177, 75, 0 }, + { 134, 87, 75, 1 }, }, + { { 109, 243, 169, 1 }, + { 234, 241, 103, 1 }, + { 202, 231, 219, 0 }, + { 243, 71, 171, 1 }, }, + { { 109, 94, 31, 0 }, + { 241, 123, 84, 1 }, + { 124, 61, 91, 0 }, + { 149, 111, 71, 1 }, }, + { { 102, 32, 99, 0 }, + { 0, 231, 134, 0 }, + { 99, 2, 51, 0 }, + { 48, 243, 128, 0 }, }, + { { 102, 219, 83, 1 }, + { 122, 111, 197, 0 }, + { 229, 109, 179, 0 }, + { 81, 251, 47, 0 }, }, + { { 98, 215, 16, 1 }, + { 123, 33, 193, 0 }, + { 132, 117, 163, 0 }, + { 65, 194, 111, 0 }, }, + { { 106, 165, 84, 1 }, + { 155, 37, 147, 0 }, + { 149, 82, 171, 0 }, + { 100, 210, 108, 1 }, }, + { { 110, 46, 41, 1 }, + { 169, 233, 134, 1 }, + { 202, 58, 59, 0 }, + { 176, 203, 202, 1 }, }, + { { 103, 54, 146, 0 }, + { 49, 51, 230, 0 }, + { 36, 182, 115, 0 }, + { 51, 230, 70, 0 }, }, + { { 103, 196, 202, 0 }, + { 67, 55, 164, 1 }, + { 41, 145, 243, 0 }, + { 146, 246, 97, 0 }, }, + { { 99, 83, 34, 1 }, + { 104, 179, 193, 0 }, + { 162, 101, 99, 0 }, + { 65, 230, 139, 0 }, }, + { { 107, 59, 196, 1 }, + { 168, 61, 243, 0 }, + { 145, 238, 107, 0 }, + { 103, 222, 10, 1 }, }, + { { 111, 22, 80, 1 }, + { 185, 53, 196, 0 }, + { 133, 52, 123, 0 }, + { 17, 214, 78, 1 }, }, + { { 107, 88, 37, 1 }, + { 200, 249, 208, 0 }, + { 210, 13, 107, 0 }, + { 5, 207, 137, 1 }, }, + { { 111, 99, 213, 1 }, + { 248, 117, 183, 0 }, + { 213, 227, 123, 0 }, + { 118, 215, 15, 1 }, }, + { { 112, 192, 248, 1 }, + { 94, 165, 32, 1 }, + { 143, 129, 135, 0 }, + { 130, 82, 189, 0 }, }, + { { 116, 215, 208, 0 }, + { 119, 37, 101, 0 }, + { 5, 245, 151, 0 }, + { 83, 82, 119, 0 }, }, + { { 113, 43, 110, 1 }, + { 44, 191, 19, 1 }, + { 187, 106, 71, 0 }, + { 228, 126, 154, 0 }, }, + { { 113, 121, 27, 1 }, + { 92, 123, 67, 1 }, + { 236, 79, 71, 0 }, + { 225, 111, 29, 0 }, }, + { { 113, 66, 210, 0 }, + { 116, 55, 32, 0 }, + { 37, 161, 71, 0 }, + { 2, 118, 23, 0 }, }, + { { 113, 99, 253, 0 }, + { 116, 245, 51, 1 }, + { 95, 227, 71, 0 }, + { 230, 87, 151, 0 }, }, + { { 117, 254, 122, 1 }, + { 127, 191, 70, 1 }, + { 175, 63, 215, 0 }, + { 177, 126, 255, 0 }, }, + { { 125, 152, 140, 0 }, + { 134, 57, 116, 1 }, + { 24, 140, 223, 0 }, + { 151, 78, 48, 1 }, }, + { { 125, 185, 247, 0 }, + { 150, 255, 119, 0 }, + { 119, 206, 223, 0 }, + { 119, 127, 180, 1 }, }, + { { 125, 19, 147, 1 }, + { 188, 115, 101, 0 }, + { 228, 228, 95, 0 }, + { 83, 103, 30, 1 }, }, + { { 121, 71, 103, 0 }, + { 229, 247, 17, 0 }, + { 115, 113, 79, 0 }, + { 68, 119, 211, 1 }, }, + { { 125, 223, 195, 0 }, + { 231, 127, 101, 0 }, + { 97, 253, 223, 0 }, + { 83, 127, 115, 1 }, }, + { { 121, 239, 82, 1 }, + { 255, 63, 3, 0 }, + { 165, 123, 207, 0 }, + { 96, 126, 127, 1 }, }, + { { 114, 0, 182, 0 }, + { 20, 163, 176, 0 }, + { 54, 128, 39, 0 }, + { 6, 226, 148, 0 }, }, + { { 118, 68, 152, 1 }, + { 93, 33, 164, 1 }, + { 140, 145, 55, 0 }, + { 146, 194, 93, 0 }, }, + { { 118, 122, 66, 0 }, + { 100, 47, 198, 0 }, + { 33, 47, 55, 0 }, + { 49, 250, 19, 0 }, }, + { { 126, 11, 181, 1 }, + { 188, 233, 181, 0 }, + { 214, 232, 63, 0 }, + { 86, 203, 158, 1 }, }, + { { 115, 58, 225, 1 }, + { 44, 253, 226, 0 }, + { 195, 174, 103, 0 }, + { 35, 223, 154, 0 }, }, + { { 119, 139, 59, 1 }, + { 62, 251, 133, 1 }, + { 238, 104, 247, 0 }, + { 208, 239, 190, 0 }, }, + { { 119, 93, 234, 0 }, + { 69, 191, 229, 1 }, + { 43, 221, 119, 0 }, + { 211, 254, 209, 0 }, }, + { { 127, 76, 0, 1 }, + { 205, 57, 132, 0 }, + { 128, 25, 127, 0 }, + { 16, 206, 89, 1 }, }, + { { 123, 117, 224, 1 }, + { 205, 181, 227, 0 }, + { 131, 215, 111, 0 }, + { 99, 214, 217, 1 }, }, + { { 127, 226, 186, 0 }, + { 246, 179, 166, 1 }, + { 46, 163, 255, 0 }, + { 178, 230, 183, 1 }, }, + { { 128, 176, 67, 1 }, + { 10, 70, 74, 0 }, + { 225, 6, 128, 1 }, + { 41, 49, 40, 0 }, }, + { { 128, 188, 61, 0 }, + { 19, 200, 90, 1 }, + { 94, 30, 128, 1 }, + { 173, 9, 228, 0 }, }, + { { 128, 50, 88, 0 }, + { 48, 4, 74, 1 }, + { 13, 38, 0, 1 }, + { 169, 16, 6, 0 }, }, + { { 128, 92, 26, 1 }, + { 89, 10, 72, 1 }, + { 172, 29, 0, 1 }, + { 137, 40, 77, 0 }, }, + { { 132, 251, 36, 0 }, + { 98, 136, 95, 0 }, + { 18, 111, 144, 1 }, + { 125, 8, 163, 0 }, }, + { { 128, 127, 245, 1 }, + { 121, 204, 123, 0 }, + { 215, 255, 0, 1 }, + { 111, 25, 207, 0 }, }, + { { 136, 45, 197, 1 }, + { 137, 76, 59, 0 }, + { 209, 218, 8, 1 }, + { 110, 25, 72, 1 }, }, + { { 136, 153, 224, 1 }, + { 138, 140, 105, 0 }, + { 131, 204, 136, 1 }, + { 75, 24, 168, 1 }, }, + { { 136, 25, 149, 0 }, + { 144, 72, 121, 0 }, + { 84, 204, 8, 1 }, + { 79, 9, 4, 1 }, }, + { { 133, 29, 199, 1 }, + { 9, 94, 125, 0 }, + { 241, 220, 80, 1 }, + { 95, 61, 72, 0 }, }, + { { 129, 187, 135, 0 }, + { 34, 90, 123, 0 }, + { 112, 238, 192, 1 }, + { 111, 45, 34, 0 }, }, + { { 129, 225, 96, 1 }, + { 74, 148, 11, 0 }, + { 131, 67, 192, 1 }, + { 104, 20, 169, 0 }, }, + { { 133, 126, 33, 1 }, + { 105, 216, 78, 0 }, + { 194, 63, 80, 1 }, + { 57, 13, 203, 0 }, }, + { { 129, 242, 146, 0 }, + { 114, 18, 106, 0 }, + { 36, 167, 192, 1 }, + { 43, 36, 39, 0 }, }, + { { 137, 61, 108, 1 }, + { 137, 156, 91, 1 }, + { 155, 94, 72, 1 }, + { 237, 28, 200, 1 }, }, + { { 137, 100, 178, 1 }, + { 217, 146, 42, 0 }, + { 166, 147, 72, 1 }, + { 42, 36, 205, 1 }, }, + { { 141, 212, 30, 1 }, + { 219, 18, 92, 1 }, + { 188, 21, 216, 1 }, + { 157, 36, 109, 1 }, }, + { { 137, 94, 209, 1 }, + { 249, 92, 104, 0 }, + { 197, 189, 72, 1 }, + { 11, 29, 79, 1 }, }, + { { 138, 4, 172, 0 }, + { 129, 128, 184, 1 }, + { 26, 144, 40, 1 }, + { 142, 128, 192, 1 }, }, + { { 142, 200, 22, 0 }, + { 210, 10, 156, 0 }, + { 52, 9, 184, 1 }, + { 28, 168, 37, 1 }, }, + { { 142, 242, 252, 1 }, + { 250, 132, 254, 1 }, + { 159, 167, 184, 1 }, + { 191, 144, 175, 1 }, }, + { { 131, 11, 137, 0 }, + { 32, 88, 169, 1 }, + { 72, 232, 96, 1 }, + { 202, 141, 2, 0 }, }, + { { 135, 43, 109, 0 }, + { 32, 220, 159, 1 }, + { 91, 106, 112, 1 }, + { 252, 157, 130, 0 }, }, + { { 135, 69, 49, 0 }, + { 81, 208, 141, 0 }, + { 70, 81, 112, 1 }, + { 88, 133, 197, 0 }, }, + { { 131, 108, 120, 1 }, + { 89, 156, 138, 1 }, + { 143, 27, 96, 1 }, + { 168, 156, 205, 0 }, }, + { { 135, 238, 13, 0 }, + { 99, 88, 158, 1 }, + { 88, 59, 240, 1 }, + { 188, 141, 99, 0 }, }, + { { 139, 177, 43, 1 }, + { 138, 210, 203, 1 }, + { 234, 70, 232, 1 }, + { 233, 165, 168, 1 }, }, + { { 139, 145, 216, 0 }, + { 146, 20, 233, 1 }, + { 13, 196, 232, 1 }, + { 203, 148, 36, 1 }, }, + { { 139, 89, 102, 0 }, + { 192, 158, 217, 0 }, + { 51, 77, 104, 1 }, + { 77, 188, 129, 1 }, }, + { { 143, 229, 85, 0 }, + { 211, 84, 159, 0 }, + { 85, 83, 248, 1 }, + { 124, 149, 101, 1 }, }, + { { 143, 243, 101, 1 }, + { 234, 212, 223, 0 }, + { 211, 103, 248, 1 }, + { 125, 149, 171, 1 }, }, + { { 148, 26, 21, 0 }, + { 52, 72, 92, 0 }, + { 84, 44, 20, 1 }, + { 29, 9, 22, 0 }, }, + { { 144, 225, 167, 0 }, + { 70, 194, 59, 0 }, + { 114, 195, 132, 1 }, + { 110, 33, 177, 0 }, }, + { { 144, 71, 255, 0 }, + { 117, 198, 57, 1 }, + { 127, 241, 4, 1 }, + { 206, 49, 215, 0 }, }, + { { 156, 188, 213, 0 }, + { 151, 76, 126, 0 }, + { 85, 158, 156, 1 }, + { 63, 25, 116, 1 }, }, + { { 152, 146, 244, 0 }, + { 182, 132, 120, 0 }, + { 23, 164, 140, 1 }, + { 15, 16, 182, 1 }, }, + { { 156, 83, 168, 1 }, + { 236, 128, 109, 1 }, + { 138, 229, 28, 1 }, + { 219, 0, 155, 1 }, }, + { { 152, 195, 107, 0 }, + { 230, 198, 9, 1 }, + { 107, 97, 140, 1 }, + { 200, 49, 179, 1 }, }, + { { 152, 114, 63, 1 }, + { 252, 194, 90, 1 }, + { 254, 39, 12, 1 }, + { 173, 33, 159, 1 }, }, + { { 145, 19, 213, 0 }, + { 52, 84, 121, 0 }, + { 85, 228, 68, 1 }, + { 79, 21, 22, 0 }, }, + { { 149, 191, 251, 0 }, + { 55, 222, 111, 1 }, + { 111, 254, 212, 1 }, + { 251, 61, 246, 0 }, }, + { { 149, 69, 22, 1 }, + { 93, 18, 29, 0 }, + { 180, 81, 84, 1 }, + { 92, 36, 93, 0 }, }, + { { 149, 238, 216, 0 }, + { 119, 28, 46, 1 }, + { 13, 187, 212, 1 }, + { 186, 28, 119, 0 }, }, + { { 157, 214, 4, 0 }, + { 231, 16, 92, 0 }, + { 16, 53, 220, 1 }, + { 29, 4, 115, 1 }, }, + { { 157, 98, 112, 0 }, + { 244, 148, 14, 0 }, + { 7, 35, 92, 1 }, + { 56, 20, 151, 1 }, }, + { { 150, 175, 206, 0 }, + { 39, 14, 191, 1 }, + { 57, 250, 180, 1 }, + { 254, 184, 114, 0 }, }, + { { 150, 186, 24, 1 }, + { 62, 8, 206, 1 }, + { 140, 46, 180, 1 }, + { 185, 136, 62, 0 }, }, + { { 150, 217, 61, 1 }, + { 94, 200, 221, 1 }, + { 222, 77, 180, 1 }, + { 221, 137, 189, 0 }, }, + { { 158, 33, 46, 0 }, + { 132, 130, 159, 1 }, + { 58, 66, 60, 1 }, + { 252, 160, 144, 1 }, }, + { { 154, 185, 132, 0 }, + { 134, 8, 251, 0 }, + { 16, 206, 172, 1 }, + { 111, 136, 48, 1 }, }, + { { 154, 129, 79, 1 }, + { 142, 70, 153, 1 }, + { 249, 64, 172, 1 }, + { 204, 177, 56, 1 }, }, + { { 154, 59, 54, 1 }, + { 188, 138, 219, 0 }, + { 182, 110, 44, 1 }, + { 109, 168, 158, 1 }, }, + { { 158, 195, 37, 0 }, + { 230, 192, 157, 0 }, + { 82, 97, 188, 1 }, + { 92, 129, 179, 1 }, }, + { { 147, 149, 40, 0 }, + { 7, 144, 201, 1 }, + { 10, 84, 228, 1 }, + { 201, 132, 240, 0 }, }, + { { 151, 50, 46, 1 }, + { 44, 146, 222, 1 }, + { 186, 38, 116, 1 }, + { 189, 164, 154, 0 }, }, + { { 151, 211, 152, 0 }, + { 118, 16, 237, 1 }, + { 12, 229, 244, 1 }, + { 219, 132, 55, 0 }, }, + { { 155, 157, 163, 1 }, + { 143, 218, 233, 0 }, + { 226, 220, 236, 1 }, + { 75, 173, 248, 1 }, }, + { { 159, 28, 240, 0 }, + { 149, 156, 236, 0 }, + { 7, 156, 124, 1 }, + { 27, 156, 212, 1 }, }, + { { 159, 51, 82, 1 }, + { 188, 22, 207, 0 }, + { 165, 102, 124, 1 }, + { 121, 180, 30, 1 }, }, + { { 155, 250, 202, 0 }, + { 230, 30, 234, 1 }, + { 41, 175, 236, 1 }, + { 171, 188, 51, 1 }, }, + { { 159, 250, 148, 0 }, + { 246, 24, 254, 0 }, + { 20, 175, 252, 1 }, + { 63, 140, 55, 1 }, }, + { { 160, 60, 203, 1 }, + { 9, 110, 106, 1 }, + { 233, 158, 2, 1 }, + { 171, 59, 72, 0 }, }, + { { 164, 168, 105, 0 }, + { 2, 236, 14, 1 }, + { 75, 10, 146, 1 }, + { 184, 27, 160, 0 }, }, + { { 160, 179, 245, 0 }, + { 50, 228, 123, 0 }, + { 87, 230, 130, 1 }, + { 111, 19, 166, 0 }, }, + { { 164, 106, 191, 0 }, + { 112, 234, 62, 1 }, + { 126, 171, 18, 1 }, + { 190, 43, 135, 0 }, }, + { { 160, 91, 120, 1 }, + { 120, 172, 73, 1 }, + { 143, 109, 2, 1 }, + { 201, 26, 143, 0 }, }, + { { 168, 104, 152, 1 }, + { 216, 40, 42, 1 }, + { 140, 139, 10, 1 }, + { 170, 10, 13, 1 }, }, + { { 172, 205, 54, 1 }, + { 219, 170, 29, 0 }, + { 182, 89, 154, 1 }, + { 92, 42, 237, 1 }, }, + { { 172, 220, 121, 1 }, + { 219, 236, 76, 1 }, + { 207, 29, 154, 1 }, + { 153, 27, 237, 1 }, }, + { { 161, 73, 35, 0 }, + { 64, 250, 9, 0 }, + { 98, 73, 66, 1 }, + { 72, 47, 129, 0 }, }, + { { 161, 67, 217, 0 }, + { 112, 116, 41, 1 }, + { 77, 225, 66, 1 }, + { 202, 23, 7, 0 }, }, + { { 173, 176, 209, 0 }, + { 146, 116, 110, 0 }, + { 69, 134, 218, 1 }, + { 59, 23, 36, 1 }, }, + { { 169, 155, 28, 1 }, + { 186, 56, 89, 1 }, + { 156, 108, 202, 1 }, + { 205, 14, 46, 1 }, }, + { { 169, 138, 215, 1 }, + { 186, 126, 56, 0 }, + { 245, 168, 202, 1 }, + { 14, 63, 46, 1 }, }, + { { 173, 84, 106, 1 }, + { 201, 182, 76, 1 }, + { 171, 21, 90, 1 }, + { 153, 54, 201, 1 }, }, + { { 173, 235, 81, 1 }, + { 250, 124, 15, 0 }, + { 197, 107, 218, 1 }, + { 120, 31, 47, 1 }, }, + { { 166, 29, 142, 0 }, + { 1, 42, 253, 1 }, + { 56, 220, 50, 1 }, + { 223, 170, 64, 0 }, }, + { { 166, 92, 102, 1 }, + { 73, 174, 220, 0 }, + { 179, 29, 50, 1 }, + { 29, 186, 201, 0 }, }, + { { 174, 188, 36, 0 }, + { 131, 168, 222, 0 }, + { 18, 30, 186, 1 }, + { 61, 138, 224, 1 }, }, + { { 170, 171, 48, 0 }, + { 178, 168, 139, 0 }, + { 6, 106, 170, 1 }, + { 104, 138, 166, 1 }, }, + { { 174, 204, 216, 0 }, + { 211, 44, 172, 1 }, + { 13, 153, 186, 1 }, + { 154, 154, 101, 1 }, }, + { { 170, 237, 187, 0 }, + { 211, 234, 171, 1 }, + { 110, 219, 170, 1 }, + { 234, 171, 229, 1 }, }, + { { 174, 227, 205, 1 }, + { 234, 100, 191, 1 }, + { 217, 227, 186, 1 }, + { 254, 147, 43, 1 }, }, + { { 167, 54, 8, 1 }, + { 41, 48, 206, 1 }, + { 136, 54, 114, 1 }, + { 185, 134, 74, 0 }, }, + { { 163, 236, 229, 0 }, + { 67, 252, 186, 0 }, + { 83, 155, 226, 1 }, + { 46, 159, 225, 0 }, }, + { { 163, 193, 155, 1 }, + { 90, 114, 169, 1 }, + { 236, 193, 226, 1 }, + { 202, 167, 45, 0 }, }, + { { 167, 250, 58, 0 }, + { 114, 186, 206, 1 }, + { 46, 47, 242, 1 }, + { 185, 174, 167, 0 }, }, + { { 175, 165, 105, 1 }, + { 139, 244, 143, 1 }, + { 203, 82, 250, 1 }, + { 248, 151, 232, 1 }, }, + { { 175, 200, 189, 1 }, + { 218, 248, 188, 1 }, + { 222, 137, 250, 1 }, + { 158, 143, 173, 1 }, }, + { { 175, 239, 164, 0 }, + { 227, 184, 191, 0 }, + { 18, 251, 250, 1 }, + { 126, 142, 227, 1 }, }, + { { 171, 103, 246, 0 }, + { 241, 182, 187, 0 }, + { 55, 243, 106, 1 }, + { 110, 182, 199, 1 }, }, + { { 180, 28, 82, 0 }, + { 21, 46, 76, 0 }, + { 37, 28, 22, 1 }, + { 25, 58, 84, 0 }, }, + { { 180, 63, 68, 1 }, + { 45, 44, 95, 0 }, + { 145, 126, 22, 1 }, + { 125, 26, 90, 0 }, }, + { { 180, 240, 202, 0 }, + { 70, 38, 110, 1 }, + { 41, 135, 150, 1 }, + { 187, 50, 49, 0 }, }, + { { 184, 24, 45, 0 }, + { 132, 232, 88, 1 }, + { 90, 12, 14, 1 }, + { 141, 11, 144, 1 }, }, + { { 188, 191, 136, 1 }, + { 175, 40, 111, 1 }, + { 136, 254, 158, 1 }, + { 251, 10, 122, 1 }, }, + { { 184, 142, 4, 1 }, + { 175, 40, 24, 0 }, + { 144, 56, 142, 1 }, + { 12, 10, 122, 1 }, }, + { { 188, 120, 40, 1 }, + { 204, 168, 78, 1 }, + { 138, 15, 30, 1 }, + { 185, 10, 153, 1 }, }, + { { 184, 200, 67, 1 }, + { 206, 110, 8, 0 }, + { 225, 9, 142, 1 }, + { 8, 59, 57, 1 }, }, + { { 188, 243, 60, 1 }, + { 254, 160, 95, 1 }, + { 158, 103, 158, 1 }, + { 253, 2, 191, 1 }, }, + { { 181, 19, 12, 0 }, + { 36, 48, 93, 1 }, + { 24, 100, 86, 1 }, + { 221, 6, 18, 0 }, }, + { { 177, 63, 106, 0 }, + { 37, 190, 75, 1 }, + { 43, 126, 70, 1 }, + { 233, 62, 210, 0 }, }, + { { 177, 101, 123, 1 }, + { 93, 246, 11, 1 }, + { 239, 83, 70, 1 }, + { 232, 55, 221, 0 }, }, + { { 177, 203, 237, 0 }, + { 102, 252, 57, 1 }, + { 91, 233, 198, 1 }, + { 206, 31, 179, 0 }, }, + { { 177, 194, 103, 1 }, + { 110, 246, 24, 0 }, + { 243, 33, 198, 1 }, + { 12, 55, 187, 0 }, }, + { { 177, 90, 242, 1 }, + { 124, 190, 104, 0 }, + { 167, 173, 70, 1 }, + { 11, 62, 159, 0 }, }, + { { 181, 238, 31, 1 }, + { 127, 122, 30, 1 }, + { 252, 59, 214, 1 }, + { 188, 47, 127, 0 }, }, + { { 185, 144, 112, 1 }, + { 158, 180, 72, 0 }, + { 135, 4, 206, 1 }, + { 9, 22, 188, 1 }, }, + { { 185, 163, 64, 1 }, + { 174, 52, 11, 0 }, + { 129, 98, 206, 1 }, + { 104, 22, 58, 1 }, }, + { { 185, 120, 90, 0 }, + { 212, 62, 74, 1 }, + { 45, 15, 78, 1 }, + { 169, 62, 21, 1 }, }, + { { 185, 247, 203, 0 }, + { 231, 118, 107, 1 }, + { 105, 247, 206, 1 }, + { 235, 55, 115, 1 }, }, + { { 182, 5, 1, 1 }, + { 13, 96, 141, 0 }, + { 192, 80, 54, 1 }, + { 88, 131, 88, 0 }, }, + { { 178, 255, 194, 0 }, + { 103, 46, 235, 0 }, + { 33, 255, 166, 1 }, + { 107, 186, 115, 0 }, }, + { { 178, 123, 21, 0 }, + { 116, 104, 219, 0 }, + { 84, 111, 38, 1 }, + { 109, 139, 23, 0 }, }, + { { 178, 110, 90, 1 }, + { 125, 46, 138, 1 }, + { 173, 59, 38, 1 }, + { 168, 186, 95, 0 }, }, + { { 190, 181, 243, 1 }, + { 159, 230, 239, 0 }, + { 231, 214, 190, 1 }, + { 123, 179, 252, 1 }, }, + { { 186, 167, 161, 0 }, + { 167, 224, 171, 0 }, + { 66, 242, 174, 1 }, + { 106, 131, 242, 1 }, }, + { { 186, 114, 10, 1 }, + { 236, 34, 202, 1 }, + { 168, 39, 46, 1 }, + { 169, 162, 27, 1 }, }, + { { 179, 48, 64, 0 }, + { 4, 52, 202, 0 }, + { 1, 6, 102, 1 }, + { 41, 150, 16, 0 }, }, + { { 183, 181, 52, 1 }, + { 31, 176, 223, 0 }, + { 150, 86, 246, 1 }, + { 125, 134, 252, 0 }, }, + { { 183, 105, 76, 0 }, + { 68, 60, 159, 1 }, + { 25, 75, 118, 1 }, + { 252, 158, 17, 0 }, }, + { { 183, 197, 193, 0 }, + { 71, 116, 173, 0 }, + { 65, 209, 246, 1 }, + { 90, 151, 113, 0 }, }, + { { 179, 87, 222, 0 }, + { 117, 54, 249, 1 }, + { 61, 245, 102, 1 }, + { 207, 182, 87, 0 }, }, + { { 187, 84, 251, 0 }, + { 213, 246, 232, 1 }, + { 111, 149, 110, 1 }, + { 139, 183, 213, 1 }, }, + { { 187, 202, 108, 1 }, + { 238, 188, 152, 1 }, + { 155, 41, 238, 1 }, + { 140, 158, 187, 1 }, }, + { { 187, 251, 225, 1 }, + { 238, 252, 235, 0 }, + { 195, 239, 238, 1 }, + { 107, 159, 187, 1 }, }, + { { 191, 246, 134, 1 }, + { 239, 50, 254, 0 }, + { 176, 183, 254, 1 }, + { 63, 166, 123, 1 }, }, + { { 196, 45, 124, 0 }, + { 17, 141, 31, 1 }, + { 31, 90, 17, 1 }, + { 252, 88, 196, 0 }, }, + { { 200, 28, 52, 0 }, + { 145, 137, 88, 0 }, + { 22, 28, 9, 1 }, + { 13, 72, 196, 1 }, }, + { { 204, 67, 229, 1 }, + { 232, 197, 61, 0 }, + { 211, 225, 25, 1 }, + { 94, 81, 139, 1 }, }, + { { 197, 53, 174, 1 }, + { 9, 147, 127, 1 }, + { 186, 214, 81, 1 }, + { 255, 100, 200, 0 }, }, + { { 193, 146, 192, 0 }, + { 34, 21, 104, 0 }, + { 1, 164, 193, 1 }, + { 11, 84, 34, 0 }, }, + { { 197, 35, 247, 0 }, + { 48, 215, 63, 0 }, + { 119, 226, 81, 1 }, + { 126, 117, 134, 0 }, }, + { { 193, 159, 254, 0 }, + { 51, 159, 121, 1 }, + { 63, 252, 193, 1 }, + { 207, 124, 230, 0 }, }, + { { 193, 84, 97, 0 }, + { 65, 213, 72, 0 }, + { 67, 21, 65, 1 }, + { 9, 85, 193, 0 }, }, + { { 193, 87, 130, 0 }, + { 97, 19, 105, 0 }, + { 32, 245, 65, 1 }, + { 75, 100, 67, 0 }, }, + { { 193, 199, 55, 1 }, + { 123, 211, 25, 0 }, + { 246, 113, 193, 1 }, + { 76, 101, 239, 0 }, }, + { { 201, 140, 239, 1 }, + { 139, 223, 56, 1 }, + { 251, 152, 201, 1 }, + { 142, 125, 232, 1 }, }, + { { 205, 240, 111, 0 }, + { 194, 215, 94, 1 }, + { 123, 7, 217, 1 }, + { 189, 117, 161, 1 }, }, + { { 201, 95, 103, 1 }, + { 233, 223, 89, 0 }, + { 243, 125, 73, 1 }, + { 77, 125, 203, 1 }, }, + { { 201, 119, 241, 0 }, + { 241, 213, 107, 0 }, + { 71, 247, 73, 1 }, + { 107, 85, 199, 1 }, }, + { { 205, 226, 149, 1 }, + { 250, 81, 62, 0 }, + { 212, 163, 217, 1 }, + { 62, 69, 47, 1 }, }, + { { 198, 191, 7, 1 }, + { 43, 75, 223, 0 }, + { 240, 126, 177, 1 }, + { 125, 233, 106, 0 }, }, + { { 198, 191, 184, 0 }, + { 51, 137, 239, 1 }, + { 14, 254, 177, 1 }, + { 251, 200, 230, 0 }, }, + { { 198, 219, 201, 1 }, + { 106, 77, 237, 1 }, + { 201, 237, 177, 1 }, + { 219, 217, 43, 0 }, }, + { { 202, 49, 136, 0 }, + { 128, 1, 235, 1 }, + { 8, 198, 41, 1 }, + { 235, 192, 0, 1 }, }, + { { 206, 222, 45, 1 }, + { 235, 201, 220, 1 }, + { 218, 61, 185, 1 }, + { 157, 201, 235, 1 }, }, + { { 199, 120, 44, 1 }, + { 72, 153, 222, 1 }, + { 154, 15, 113, 1 }, + { 189, 204, 137, 0 }, }, + { { 203, 171, 75, 1 }, + { 170, 95, 139, 1 }, + { 233, 106, 233, 1 }, + { 232, 253, 42, 1 }, }, + { { 207, 67, 191, 0 }, + { 240, 211, 189, 1 }, + { 126, 225, 121, 1 }, + { 222, 229, 135, 1 }, }, + { { 208, 174, 86, 1 }, + { 63, 15, 26, 0 }, + { 181, 58, 133, 1 }, + { 44, 120, 126, 0 }, }, + { { 208, 115, 51, 0 }, + { 116, 195, 75, 0 }, + { 102, 103, 5, 1 }, + { 105, 97, 151, 0 }, }, + { { 216, 20, 152, 0 }, + { 149, 1, 104, 1 }, + { 12, 148, 13, 1 }, + { 139, 64, 84, 1 }, }, + { { 220, 96, 159, 1 }, + { 220, 67, 62, 1 }, + { 252, 131, 29, 1 }, + { 190, 97, 29, 1 }, }, + { { 213, 117, 88, 0 }, + { 85, 21, 79, 1 }, + { 13, 87, 85, 1 }, + { 249, 84, 85, 0 }, }, + { { 209, 224, 215, 0 }, + { 86, 87, 58, 0 }, + { 117, 131, 197, 1 }, + { 46, 117, 53, 0 }, }, + { { 213, 251, 177, 0 }, + { 118, 217, 111, 0 }, + { 70, 239, 213, 1 }, + { 123, 77, 183, 0 }, }, + { { 213, 218, 116, 1 }, + { 126, 157, 92, 0 }, + { 151, 45, 213, 1 }, + { 29, 92, 191, 0 }, }, + { { 221, 60, 116, 1 }, + { 157, 157, 94, 0 }, + { 151, 30, 93, 1 }, + { 61, 92, 220, 1 }, }, + { { 221, 151, 109, 0 }, + { 167, 213, 93, 1 }, + { 91, 116, 221, 1 }, + { 221, 85, 242, 1 }, }, + { { 217, 27, 49, 0 }, + { 180, 217, 73, 0 }, + { 70, 108, 77, 1 }, + { 73, 77, 150, 1 }, }, + { { 217, 98, 166, 0 }, + { 228, 147, 58, 0 }, + { 50, 163, 77, 1 }, + { 46, 100, 147, 1 }, }, + { { 210, 171, 124, 1 }, + { 62, 141, 155, 1 }, + { 159, 106, 165, 1 }, + { 236, 216, 190, 0 }, }, + { { 210, 197, 236, 0 }, + { 71, 133, 185, 1 }, + { 27, 209, 165, 1 }, + { 206, 208, 241, 0 }, }, + { { 210, 202, 106, 0 }, + { 102, 143, 136, 1 }, + { 43, 41, 165, 1 }, + { 136, 248, 179, 0 }, }, + { { 210, 107, 214, 0 }, + { 116, 15, 187, 0 }, + { 53, 235, 37, 1 }, + { 110, 248, 23, 0 }, }, + { { 222, 155, 52, 0 }, + { 182, 137, 221, 0 }, + { 22, 108, 189, 1 }, + { 93, 200, 182, 1 }, }, + { { 222, 146, 158, 0 }, + { 182, 3, 252, 1 }, + { 60, 164, 189, 1 }, + { 159, 224, 54, 1 }, }, + { { 218, 96, 216, 1 }, + { 220, 5, 170, 1 }, + { 141, 131, 45, 1 }, + { 170, 208, 29, 1 }, }, + { { 215, 57, 178, 1 }, + { 28, 155, 239, 0 }, + { 166, 206, 117, 1 }, + { 123, 236, 156, 0 }, }, + { { 215, 95, 38, 0 }, + { 101, 155, 221, 0 }, + { 50, 125, 117, 1 }, + { 93, 236, 211, 0 }, }, + { { 223, 1, 65, 1 }, + { 140, 85, 141, 0 }, + { 193, 64, 125, 1 }, + { 88, 213, 24, 1 }, }, + { { 223, 121, 15, 1 }, + { 204, 91, 223, 1 }, + { 248, 79, 125, 1 }, + { 253, 237, 25, 1 }, }, + { { 219, 247, 116, 0 }, + { 247, 149, 219, 0 }, + { 23, 119, 237, 1 }, + { 109, 212, 247, 1 }, }, + { { 224, 67, 79, 1 }, + { 104, 103, 25, 1 }, + { 249, 97, 3, 1 }, + { 204, 115, 11, 0 }, }, + { { 224, 198, 227, 0 }, + { 99, 231, 40, 0 }, + { 99, 177, 131, 1 }, + { 10, 115, 227, 0 }, }, + { { 236, 56, 189, 1 }, + { 152, 233, 126, 1 }, + { 222, 142, 27, 1 }, + { 191, 75, 140, 1 }, }, + { { 236, 81, 253, 0 }, + { 208, 229, 125, 1 }, + { 95, 197, 27, 1 }, + { 223, 83, 133, 1 }, }, + { { 229, 141, 36, 0 }, + { 3, 185, 29, 0 }, + { 18, 88, 211, 1 }, + { 92, 78, 224, 0 }, }, + { { 229, 5, 121, 1 }, + { 25, 245, 13, 1 }, + { 207, 80, 83, 1 }, + { 216, 87, 204, 0 }, }, + { { 225, 78, 248, 0 }, + { 113, 189, 40, 1 }, + { 15, 185, 67, 1 }, + { 138, 94, 199, 0 }, }, + { { 229, 87, 220, 1 }, + { 121, 53, 125, 1 }, + { 157, 245, 83, 1 }, + { 223, 86, 79, 0 }, }, + { { 225, 247, 242, 1 }, + { 123, 183, 107, 0 }, + { 167, 247, 195, 1 }, + { 107, 118, 239, 0 }, }, + { { 226, 133, 77, 0 }, + { 3, 101, 153, 1 }, + { 89, 80, 163, 1 }, + { 204, 211, 96, 0 }, }, + { { 226, 24, 26, 0 }, + { 16, 43, 200, 1 }, + { 44, 12, 35, 1 }, + { 137, 234, 4, 0 }, }, + { { 226, 43, 203, 0 }, + { 32, 111, 171, 1 }, + { 105, 234, 35, 1 }, + { 234, 251, 2, 0 }, }, + { { 230, 236, 2, 0 }, + { 67, 43, 142, 0 }, + { 32, 27, 179, 1 }, + { 56, 234, 97, 0 }, }, + { { 230, 98, 129, 0 }, + { 96, 97, 174, 0 }, + { 64, 163, 51, 1 }, + { 58, 195, 3, 0 }, }, + { { 234, 38, 64, 0 }, + { 161, 37, 138, 0 }, + { 1, 50, 43, 1 }, + { 40, 210, 66, 1 }, }, + { { 234, 224, 196, 0 }, + { 194, 37, 186, 0 }, + { 17, 131, 171, 1 }, + { 46, 210, 33, 1 }, }, + { { 234, 114, 148, 1 }, + { 248, 33, 250, 0 }, + { 148, 167, 43, 1 }, + { 47, 194, 15, 1 }, }, + { { 227, 17, 63, 1 }, + { 24, 243, 217, 1 }, + { 254, 68, 99, 1 }, + { 205, 231, 140, 0 }, }, + { { 231, 11, 230, 1 }, + { 40, 191, 189, 0 }, + { 179, 232, 115, 1 }, + { 94, 254, 138, 0 }, }, + { { 227, 100, 241, 1 }, + { 89, 245, 170, 0 }, + { 199, 147, 99, 1 }, + { 42, 215, 205, 0 }, }, + { { 231, 250, 226, 1 }, + { 106, 191, 238, 0 }, + { 163, 175, 243, 1 }, + { 59, 254, 171, 0 }, }, + { { 227, 219, 243, 0 }, + { 114, 255, 233, 0 }, + { 103, 237, 227, 1 }, + { 75, 255, 167, 0 }, }, + { { 239, 135, 98, 0 }, + { 163, 183, 141, 0 }, + { 35, 112, 251, 1 }, + { 88, 246, 226, 1 }, }, + { { 239, 93, 112, 0 }, + { 209, 189, 205, 0 }, + { 7, 93, 123, 1 }, + { 89, 222, 197, 1 }, }, + { { 235, 102, 14, 0 }, + { 225, 51, 154, 1 }, + { 56, 51, 107, 1 }, + { 172, 230, 67, 1 }, }, + { { 239, 74, 60, 0 }, + { 240, 185, 156, 1 }, + { 30, 41, 123, 1 }, + { 156, 206, 135, 1 }, }, + { { 240, 178, 33, 1 }, + { 46, 225, 74, 0 }, + { 194, 38, 135, 1 }, + { 41, 67, 186, 0 }, }, + { { 240, 175, 79, 0 }, + { 39, 111, 27, 1 }, + { 121, 122, 135, 1 }, + { 236, 123, 114, 0 }, }, + { { 244, 159, 60, 1 }, + { 63, 169, 93, 1 }, + { 158, 124, 151, 1 }, + { 221, 74, 254, 0 }, }, + { { 240, 245, 4, 0 }, + { 71, 33, 91, 0 }, + { 16, 87, 135, 1 }, + { 109, 66, 113, 0 }, }, + { { 240, 242, 120, 0 }, + { 118, 165, 74, 1 }, + { 15, 39, 135, 1 }, + { 169, 82, 183, 0 }, }, + { { 248, 52, 99, 0 }, + { 133, 231, 74, 0 }, + { 99, 22, 15, 1 }, + { 41, 115, 208, 1 }, }, + { { 252, 219, 143, 0 }, + { 230, 107, 125, 1 }, + { 120, 237, 159, 1 }, + { 223, 107, 51, 1 }, }, + { { 252, 254, 77, 1 }, + { 239, 109, 94, 1 }, + { 217, 63, 159, 1 }, + { 189, 91, 123, 1 }, }, + { { 253, 176, 69, 1 }, + { 142, 117, 94, 0 }, + { 209, 6, 223, 1 }, + { 61, 87, 56, 1 }, }, + { { 253, 51, 235, 0 }, + { 164, 247, 111, 1 }, + { 107, 230, 95, 1 }, + { 251, 119, 146, 1 }, }, + { { 249, 196, 217, 1 }, + { 223, 117, 40, 1 }, + { 205, 145, 207, 1 }, + { 138, 87, 125, 1 }, }, + { { 242, 35, 145, 1 }, + { 60, 97, 171, 0 }, + { 196, 226, 39, 1 }, + { 106, 195, 30, 0 }, }, + { { 246, 210, 251, 1 }, + { 126, 231, 236, 1 }, + { 239, 165, 183, 1 }, + { 155, 243, 191, 0 }, }, + { { 254, 239, 211, 1 }, + { 255, 111, 175, 0 }, + { 229, 251, 191, 1 }, + { 122, 251, 127, 1 }, }, + { { 243, 176, 58, 1 }, + { 30, 179, 202, 1 }, + { 174, 6, 231, 1 }, + { 169, 230, 188, 0 }, }, + { { 247, 138, 77, 1 }, + { 46, 125, 156, 1 }, + { 217, 40, 247, 1 }, + { 156, 223, 58, 0 }, }, + { { 243, 143, 157, 1 }, + { 63, 121, 185, 1 }, + { 220, 248, 231, 1 }, + { 206, 207, 126, 0 }, }, + { { 247, 242, 39, 0 }, + { 102, 243, 222, 0 }, + { 114, 39, 247, 1 }, + { 61, 231, 179, 0 }, }, + { { 255, 189, 133, 1 }, + { 143, 121, 255, 0 }, + { 208, 222, 255, 1 }, + { 127, 207, 120, 1 }, }, + { { 255, 38, 197, 1 }, + { 173, 117, 190, 0 }, + { 209, 178, 127, 1 }, + { 62, 215, 90, 1 }, }, + { { 251, 224, 97, 0 }, + { 198, 245, 138, 0 }, + { 67, 3, 239, 1 }, + { 40, 215, 177, 1 }, }, + { { 255, 220, 26, 0 }, + { 215, 59, 204, 1 }, + { 44, 29, 255, 1 }, + { 153, 238, 117, 1 }, }, + { { 25, 248, 99, 1 }, + { 206, 222, 66, 0 }, + { 227, 15, 204, 0 }, + { 33, 61, 185, 1 }, }, + { { 169, 93, 31, 1 }, + { 217, 122, 89, 1 }, + { 252, 93, 74, 1 }, + { 205, 47, 77, 1 }, }, + { { 0, 184, 72, 1 }, + { 10, 12, 66, 1 }, + { 137, 14, 128, 0 }, + { 161, 24, 40, 0 }, }, + { { 0, 236, 225, 0 }, + { 67, 204, 34, 0 }, + { 67, 155, 128, 0 }, + { 34, 25, 225, 0 }, }, + { { 8, 97, 102, 0 }, + { 192, 134, 19, 0 }, + { 51, 67, 8, 0 }, + { 100, 48, 129, 1 }, }, + { { 8, 244, 131, 0 }, + { 195, 66, 98, 0 }, + { 96, 151, 136, 0 }, + { 35, 33, 97, 1 }, }, + { { 12, 248, 157, 0 }, + { 210, 72, 118, 1 }, + { 92, 143, 152, 0 }, + { 183, 9, 37, 1 }, }, + { { 8, 192, 62, 1 }, + { 218, 130, 16, 1 }, + { 190, 1, 136, 0 }, + { 132, 32, 173, 1 }, }, + { { 1, 57, 192, 0 }, + { 0, 28, 99, 0 }, + { 1, 206, 64, 0 }, + { 99, 28, 0, 0 }, }, + { { 1, 40, 10, 0 }, + { 0, 26, 2, 1 }, + { 40, 10, 64, 0 }, + { 160, 44, 0, 0 }, }, + { { 1, 190, 179, 1 }, + { 59, 218, 98, 0 }, + { 230, 190, 192, 0 }, + { 35, 45, 238, 0 }, }, + { { 13, 128, 85, 0 }, + { 146, 84, 20, 0 }, + { 85, 0, 216, 0 }, + { 20, 21, 36, 1 }, }, + { { 13, 175, 99, 1 }, + { 171, 222, 7, 0 }, + { 227, 122, 216, 0 }, + { 112, 61, 234, 1 }, }, + { { 6, 181, 236, 0 }, + { 3, 132, 247, 1 }, + { 27, 214, 176, 0 }, + { 247, 144, 224, 0 }, }, + { { 2, 153, 54, 1 }, + { 26, 138, 209, 0 }, + { 182, 76, 160, 0 }, + { 69, 168, 172, 0 }, }, + { { 6, 182, 143, 0 }, + { 35, 66, 246, 1 }, + { 120, 182, 176, 0 }, + { 183, 161, 98, 0 }, }, + { { 6, 125, 64, 1 }, + { 73, 12, 199, 0 }, + { 129, 95, 48, 0 }, + { 113, 152, 73, 0 }, }, + { { 2, 114, 104, 1 }, + { 104, 132, 194, 1 }, + { 139, 39, 32, 0 }, + { 161, 144, 139, 0 }, }, + { { 6, 203, 237, 1 }, + { 106, 204, 181, 1 }, + { 219, 233, 176, 0 }, + { 214, 153, 171, 0 }, }, + { { 14, 15, 61, 0 }, + { 177, 200, 149, 1 }, + { 94, 120, 56, 0 }, + { 212, 137, 198, 1 }, }, + { { 14, 213, 203, 0 }, + { 195, 70, 229, 1 }, + { 105, 213, 184, 0 }, + { 211, 177, 97, 1 }, }, + { { 3, 125, 39, 1 }, + { 73, 218, 211, 0 }, + { 242, 95, 96, 0 }, + { 101, 173, 201, 0 }, }, + { { 3, 233, 143, 0 }, + { 66, 90, 179, 1 }, + { 120, 203, 224, 0 }, + { 230, 173, 33, 0 }, }, + { { 7, 250, 159, 1 }, + { 122, 90, 246, 1 }, + { 252, 175, 240, 0 }, + { 183, 173, 47, 0 }, }, + { { 11, 76, 16, 0 }, + { 209, 24, 128, 0 }, + { 4, 25, 104, 0 }, + { 0, 140, 69, 1 }, }, + { { 16, 185, 87, 1 }, + { 30, 78, 83, 0 }, + { 245, 78, 132, 0 }, + { 101, 57, 60, 0 }, }, + { { 16, 22, 41, 0 }, + { 37, 192, 64, 1 }, + { 74, 52, 4, 0 }, + { 129, 1, 210, 0 }, }, + { { 16, 98, 139, 1 }, + { 108, 66, 34, 1 }, + { 232, 163, 4, 0 }, + { 162, 33, 27, 0 }, }, + { { 24, 157, 182, 0 }, + { 151, 138, 113, 0 }, + { 54, 220, 140, 0 }, + { 71, 40, 244, 1 }, }, + { { 24, 109, 0, 1 }, + { 205, 8, 3, 0 }, + { 128, 91, 12, 0 }, + { 96, 8, 89, 1 }, }, + { { 28, 118, 42, 0 }, + { 229, 130, 70, 1 }, + { 42, 55, 28, 0 }, + { 177, 32, 211, 1 }, }, + { { 24, 95, 228, 1 }, + { 237, 140, 113, 0 }, + { 147, 253, 12, 0 }, + { 71, 24, 219, 1 }, }, + { { 28, 66, 22, 0 }, + { 244, 2, 20, 0 }, + { 52, 33, 28, 0 }, + { 20, 32, 23, 1 }, }, + { { 17, 60, 169, 0 }, + { 5, 216, 98, 1 }, + { 74, 158, 68, 0 }, + { 163, 13, 208, 0 }, }, + { { 17, 152, 57, 1 }, + { 30, 216, 64, 1 }, + { 206, 12, 196, 0 }, + { 129, 13, 188, 0 }, }, + { { 21, 58, 99, 0 }, + { 36, 222, 70, 0 }, + { 99, 46, 84, 0 }, + { 49, 61, 146, 0 }, }, + { { 21, 18, 117, 1 }, + { 60, 212, 84, 0 }, + { 215, 36, 84, 0 }, + { 21, 21, 158, 0 }, }, + { { 21, 222, 162, 0 }, + { 103, 154, 100, 0 }, + { 34, 189, 212, 0 }, + { 19, 44, 243, 0 }, }, + { { 21, 106, 24, 1 }, + { 124, 24, 6, 1 }, + { 140, 43, 84, 0 }, + { 176, 12, 31, 0 }, }, + { { 29, 209, 40, 0 }, + { 198, 144, 69, 1 }, + { 10, 69, 220, 0 }, + { 209, 4, 177, 1 }, }, + { { 29, 113, 148, 1 }, + { 220, 16, 119, 0 }, + { 148, 199, 92, 0 }, + { 119, 4, 29, 1 }, }, + { { 18, 57, 139, 1 }, + { 12, 74, 227, 1 }, + { 232, 206, 36, 0 }, + { 227, 169, 24, 0 }, }, + { { 18, 5, 152, 0 }, + { 21, 0, 161, 1 }, + { 12, 208, 36, 0 }, + { 194, 128, 84, 0 }, }, + { { 22, 2, 152, 1 }, + { 60, 0, 164, 1 }, + { 140, 160, 52, 0 }, + { 146, 128, 30, 0 }, }, + { { 26, 52, 127, 1 }, + { 157, 198, 210, 1 }, + { 255, 22, 44, 0 }, + { 165, 177, 220, 1 }, }, + { { 30, 220, 239, 1 }, + { 207, 206, 244, 1 }, + { 251, 157, 188, 0 }, + { 151, 185, 249, 1 }, }, + { { 30, 112, 115, 0 }, + { 212, 198, 198, 0 }, + { 103, 7, 60, 0 }, + { 49, 177, 149, 1 }, }, + { { 26, 67, 149, 0 }, + { 244, 64, 177, 0 }, + { 84, 225, 44, 0 }, + { 70, 129, 23, 1 }, }, + { { 26, 126, 27, 1 }, + { 253, 74, 194, 1 }, + { 236, 63, 44, 0 }, + { 161, 169, 95, 1 }, }, + { { 23, 173, 194, 0 }, + { 7, 30, 167, 0 }, + { 33, 218, 244, 0 }, + { 114, 188, 112, 0 }, }, + { { 19, 25, 222, 1 }, + { 28, 30, 241, 1 }, + { 189, 204, 100, 0 }, + { 199, 188, 28, 0 }, }, + { { 27, 168, 147, 1 }, + { 158, 90, 162, 0 }, + { 228, 138, 236, 0 }, + { 34, 173, 60, 1 }, }, + { { 27, 147, 73, 1 }, + { 174, 84, 193, 1 }, + { 201, 100, 236, 0 }, + { 193, 149, 58, 1 }, }, + { { 27, 208, 27, 0 }, + { 214, 82, 192, 1 }, + { 108, 5, 236, 0 }, + { 129, 165, 53, 1 }, }, + { { 31, 66, 219, 1 }, + { 252, 86, 164, 1 }, + { 237, 161, 124, 0 }, + { 146, 181, 31, 1 }, }, + { { 32, 185, 170, 0 }, + { 2, 170, 99, 1 }, + { 42, 206, 130, 0 }, + { 227, 42, 160, 0 }, }, + { { 36, 196, 186, 0 }, + { 83, 162, 36, 1 }, + { 46, 145, 146, 0 }, + { 146, 34, 229, 0 }, }, + { { 40, 173, 5, 0 }, + { 131, 104, 19, 0 }, + { 80, 90, 138, 0 }, + { 100, 11, 96, 1 }, }, + { { 44, 35, 100, 1 }, + { 168, 164, 23, 0 }, + { 147, 98, 26, 0 }, + { 116, 18, 138, 1 }, }, + { { 44, 14, 154, 1 }, + { 185, 42, 36, 1 }, + { 172, 184, 26, 0 }, + { 146, 42, 78, 1 }, }, + { { 44, 81, 3, 1 }, + { 200, 98, 69, 0 }, + { 224, 69, 26, 0 }, + { 81, 35, 9, 1 }, }, + { { 37, 197, 207, 1 }, + { 75, 118, 53, 1 }, + { 249, 209, 210, 0 }, + { 214, 55, 105, 0 }, }, + { { 37, 200, 211, 1 }, + { 90, 126, 36, 0 }, + { 229, 137, 210, 0 }, + { 18, 63, 45, 0 }, }, + { { 33, 119, 232, 1 }, + { 105, 180, 99, 1 }, + { 139, 247, 66, 0 }, + { 227, 22, 203, 0 }, }, + { { 34, 141, 218, 1 }, + { 27, 46, 161, 1 }, + { 173, 216, 162, 0 }, + { 194, 186, 108, 0 }, }, + { { 34, 10, 238, 0 }, + { 32, 174, 176, 1 }, + { 59, 168, 34, 0 }, + { 134, 186, 130, 0 }, }, + { { 46, 134, 44, 0 }, + { 163, 160, 148, 1 }, + { 26, 48, 186, 0 }, + { 148, 130, 226, 1 }, }, + { { 42, 182, 47, 1 }, + { 171, 226, 210, 1 }, + { 250, 54, 170, 0 }, + { 165, 163, 234, 1 }, }, + { { 39, 60, 45, 0 }, + { 1, 248, 214, 1 }, + { 90, 30, 114, 0 }, + { 181, 143, 192, 0 }, }, + { { 39, 157, 74, 0 }, + { 3, 62, 197, 1 }, + { 41, 92, 242, 0 }, + { 209, 190, 96, 0 }, }, + { { 39, 155, 7, 1 }, + { 42, 122, 213, 0 }, + { 240, 108, 242, 0 }, + { 85, 175, 42, 0 }, }, + { { 35, 87, 132, 0 }, + { 97, 48, 241, 0 }, + { 16, 245, 98, 0 }, + { 71, 134, 67, 0 }, }, + { { 35, 78, 67, 1 }, + { 105, 126, 128, 0 }, + { 225, 57, 98, 0 }, + { 0, 191, 75, 0 }, }, + { { 47, 171, 185, 1 }, + { 186, 248, 167, 1 }, + { 206, 234, 250, 0 }, + { 242, 143, 174, 1 }, }, + { { 48, 176, 46, 1 }, + { 14, 162, 82, 1 }, + { 186, 6, 134, 0 }, + { 165, 34, 184, 0 }, }, + { { 48, 40, 122, 0 }, + { 20, 174, 2, 1 }, + { 47, 10, 6, 0 }, + { 160, 58, 148, 0 }, }, + { { 52, 253, 160, 1 }, + { 79, 168, 103, 0 }, + { 130, 223, 150, 0 }, + { 115, 10, 249, 0 }, }, + { { 60, 214, 137, 0 }, + { 231, 96, 100, 1 }, + { 72, 181, 158, 0 }, + { 147, 3, 115, 1 }, }, + { { 56, 126, 182, 0 }, + { 245, 170, 114, 0 }, + { 54, 191, 14, 0 }, + { 39, 42, 215, 1 }, }, + { { 49, 0, 109, 0 }, + { 4, 244, 16, 1 }, + { 91, 0, 70, 0 }, + { 132, 23, 144, 0 }, }, + { { 49, 23, 219, 1 }, + { 61, 118, 97, 1 }, + { 237, 244, 70, 0 }, + { 195, 55, 94, 0 }, }, + { { 49, 167, 119, 1 }, + { 63, 246, 19, 0 }, + { 247, 114, 198, 0 }, + { 100, 55, 254, 0 }, }, + { { 61, 128, 10, 1 }, + { 142, 50, 4, 1 }, + { 168, 0, 222, 0 }, + { 144, 38, 56, 1 }, }, + { { 61, 226, 157, 0 }, + { 246, 112, 54, 1 }, + { 92, 163, 222, 0 }, + { 182, 7, 55, 1 }, }, + { { 54, 106, 119, 1 }, + { 124, 238, 150, 0 }, + { 247, 43, 54, 0 }, + { 52, 187, 159, 0 }, }, + { { 54, 199, 190, 1 }, + { 127, 162, 181, 1 }, + { 190, 241, 182, 0 }, + { 214, 162, 255, 0 }, }, + { { 62, 187, 41, 0 }, + { 166, 232, 199, 1 }, + { 74, 110, 190, 0 }, + { 241, 139, 178, 1 }, }, + { { 62, 104, 154, 0 }, + { 212, 42, 166, 1 }, + { 44, 139, 62, 0 }, + { 178, 170, 21, 1 }, }, + { { 62, 95, 2, 1 }, + { 237, 42, 197, 0 }, + { 160, 125, 62, 0 }, + { 81, 170, 91, 1 }, }, + { { 58, 86, 206, 1 }, + { 237, 38, 240, 1 }, + { 185, 181, 46, 0 }, + { 135, 178, 91, 1 }, }, + { { 62, 95, 252, 0 }, + { 245, 172, 245, 1 }, + { 31, 253, 62, 0 }, + { 215, 154, 215, 1 }, }, + { { 51, 185, 172, 0 }, + { 6, 184, 243, 1 }, + { 26, 206, 230, 0 }, + { 231, 142, 176, 0 }, }, + { { 51, 84, 174, 0 }, + { 69, 178, 240, 1 }, + { 58, 149, 102, 0 }, + { 135, 166, 209, 0 }, }, + { { 63, 25, 160, 0 }, + { 132, 184, 229, 0 }, + { 2, 204, 126, 0 }, + { 83, 142, 144, 1 }, }, + { { 59, 173, 97, 1 }, + { 143, 252, 131, 0 }, + { 195, 90, 238, 0 }, + { 96, 159, 248, 1 }, }, + { { 59, 112, 47, 1 }, + { 204, 242, 210, 1 }, + { 250, 7, 110, 0 }, + { 165, 167, 153, 1 }, }, + { { 59, 235, 58, 1 }, + { 254, 186, 131, 1 }, + { 174, 107, 238, 0 }, + { 224, 174, 191, 1 }, }, + { { 64, 83, 90, 0 }, + { 112, 7, 65, 1 }, + { 45, 101, 1, 0 }, + { 193, 112, 7, 0 }, }, + { { 65, 152, 22, 0 }, + { 18, 27, 80, 0 }, + { 52, 12, 193, 0 }, + { 5, 108, 36, 0 }, }, + { { 65, 143, 177, 0 }, + { 51, 217, 33, 0 }, + { 70, 248, 193, 0 }, + { 66, 77, 230, 0 }, }, + { { 69, 217, 33, 1 }, + { 74, 217, 69, 0 }, + { 194, 77, 209, 0 }, + { 81, 77, 169, 0 }, }, + { { 69, 235, 1, 0 }, + { 98, 89, 7, 0 }, + { 64, 107, 209, 0 }, + { 112, 77, 35, 0 }, }, + { { 73, 157, 82, 0 }, + { 147, 31, 65, 0 }, + { 37, 92, 201, 0 }, + { 65, 124, 100, 1 }, }, + { { 66, 56, 79, 1 }, + { 8, 79, 210, 1 }, + { 249, 14, 33, 0 }, + { 165, 249, 8, 0 }, }, + { { 74, 166, 163, 1 }, + { 171, 195, 162, 0 }, + { 226, 178, 169, 0 }, + { 34, 225, 234, 1 }, }, + { { 74, 197, 42, 1 }, + { 203, 131, 129, 1 }, + { 170, 81, 169, 0 }, + { 192, 224, 233, 1 }, }, + { { 74, 200, 115, 0 }, + { 210, 207, 128, 0 }, + { 103, 9, 169, 0 }, + { 0, 249, 165, 1 }, }, + { { 71, 180, 198, 1 }, + { 11, 23, 246, 0 }, + { 177, 150, 241, 0 }, + { 55, 244, 104, 0 }, }, + { { 67, 147, 93, 0 }, + { 50, 85, 209, 1 }, + { 93, 100, 225, 0 }, + { 197, 213, 38, 0 }, }, + { { 67, 120, 117, 0 }, + { 80, 221, 210, 0 }, + { 87, 15, 97, 0 }, + { 37, 221, 133, 0 }, }, + { { 79, 58, 175, 1 }, + { 168, 219, 246, 1 }, + { 250, 174, 121, 0 }, + { 183, 237, 138, 1 }, }, + { { 79, 135, 68, 1 }, + { 171, 21, 149, 0 }, + { 145, 112, 249, 0 }, + { 84, 212, 106, 1 }, }, + { { 79, 135, 223, 0 }, + { 179, 87, 181, 1 }, + { 125, 240, 249, 0 }, + { 214, 245, 102, 1 }, }, + { { 79, 196, 222, 1 }, + { 219, 23, 180, 1 }, + { 189, 145, 249, 0 }, + { 150, 244, 109, 1 }, }, + { { 84, 31, 71, 0 }, + { 37, 79, 85, 0 }, + { 113, 124, 21, 0 }, + { 85, 121, 82, 0 }, }, + { { 80, 31, 148, 0 }, + { 53, 9, 113, 0 }, + { 20, 252, 5, 0 }, + { 71, 72, 86, 0 }, }, + { { 92, 145, 67, 0 }, + { 134, 71, 69, 0 }, + { 97, 68, 157, 0 }, + { 81, 113, 48, 1 }, }, + { { 88, 189, 239, 1 }, + { 143, 207, 115, 1 }, + { 251, 222, 141, 0 }, + { 231, 121, 248, 1 }, }, + { { 92, 221, 7, 1 }, + { 207, 75, 85, 0 }, + { 240, 93, 157, 0 }, + { 85, 105, 121, 1 }, }, + { { 92, 102, 88, 1 }, + { 253, 5, 6, 1 }, + { 141, 51, 29, 0 }, + { 176, 80, 95, 1 }, }, + { { 92, 219, 50, 1 }, + { 254, 139, 69, 0 }, + { 166, 109, 157, 0 }, + { 81, 104, 191, 1 }, }, + { { 85, 69, 212, 0 }, + { 85, 21, 53, 0 }, + { 21, 209, 85, 0 }, + { 86, 84, 85, 0 }, }, + { { 81, 68, 62, 0 }, + { 85, 147, 16, 1 }, + { 62, 17, 69, 0 }, + { 132, 100, 213, 0 }, }, + { { 81, 231, 230, 0 }, + { 103, 151, 51, 0 }, + { 51, 243, 197, 0 }, + { 102, 116, 243, 0 }, }, + { { 89, 11, 167, 0 }, + { 164, 219, 49, 0 }, + { 114, 232, 77, 0 }, + { 70, 109, 146, 1 }, }, + { { 93, 186, 56, 1 }, + { 190, 153, 70, 1 }, + { 142, 46, 221, 0 }, + { 177, 76, 190, 1 }, }, + { { 93, 166, 55, 1 }, + { 191, 211, 22, 0 }, + { 246, 50, 221, 0 }, + { 52, 101, 254, 1 }, }, + { { 89, 249, 149, 0 }, + { 214, 89, 115, 0 }, + { 84, 207, 205, 0 }, + { 103, 77, 53, 1 }, }, + { { 93, 255, 236, 0 }, + { 231, 157, 119, 1 }, + { 27, 255, 221, 0 }, + { 247, 92, 243, 1 }, }, + { { 82, 157, 58, 0 }, + { 23, 139, 193, 1 }, + { 46, 92, 165, 0 }, + { 193, 232, 244, 0 }, }, + { { 86, 58, 151, 0 }, + { 52, 75, 246, 0 }, + { 116, 174, 53, 0 }, + { 55, 233, 22, 0 }, }, + { { 82, 99, 34, 0 }, + { 100, 131, 131, 0 }, + { 34, 99, 37, 0 }, + { 96, 224, 147, 0 }, }, + { { 82, 203, 49, 1 }, + { 126, 201, 129, 0 }, + { 198, 105, 165, 0 }, + { 64, 201, 191, 0 }, }, + { { 90, 139, 204, 1 }, + { 174, 13, 177, 1 }, + { 153, 232, 173, 0 }, + { 198, 216, 58, 1 }, }, + { { 83, 33, 106, 0 }, + { 4, 151, 131, 1 }, + { 43, 66, 101, 0 }, + { 224, 244, 144, 0 }, }, + { { 87, 245, 17, 0 }, + { 87, 81, 199, 0 }, + { 68, 87, 245, 0 }, + { 113, 197, 117, 0 }, }, + { { 95, 220, 33, 0 }, + { 199, 217, 196, 0 }, + { 66, 29, 253, 0 }, + { 17, 205, 241, 1 }, }, + { { 96, 140, 28, 0 }, + { 19, 41, 16, 1 }, + { 28, 24, 131, 0 }, + { 132, 74, 100, 0 }, }, + { { 96, 162, 213, 1 }, + { 58, 101, 50, 0 }, + { 213, 162, 131, 0 }, + { 38, 83, 46, 0 }, }, + { { 96, 222, 5, 1 }, + { 107, 105, 80, 0 }, + { 208, 61, 131, 0 }, + { 5, 75, 107, 0 }, }, + { { 108, 76, 110, 0 }, + { 193, 175, 20, 1 }, + { 59, 25, 27, 0 }, + { 148, 122, 193, 1 }, }, + { { 104, 82, 12, 0 }, + { 224, 33, 80, 1 }, + { 24, 37, 11, 0 }, + { 133, 66, 3, 1 }, }, + { { 101, 59, 167, 0 }, + { 32, 251, 119, 0 }, + { 114, 238, 83, 0 }, + { 119, 111, 130, 0 }, }, + { { 97, 170, 234, 0 }, + { 34, 191, 34, 1 }, + { 43, 170, 195, 0 }, + { 162, 126, 162, 0 }, }, + { { 101, 113, 110, 1 }, + { 72, 183, 87, 1 }, + { 187, 71, 83, 0 }, + { 245, 118, 137, 0 }, }, + { { 101, 124, 89, 1 }, + { 89, 125, 70, 1 }, + { 205, 31, 83, 0 }, + { 177, 95, 77, 0 }, }, + { { 105, 131, 46, 1 }, + { 170, 179, 17, 1 }, + { 186, 96, 203, 0 }, + { 196, 102, 170, 1 }, }, + { { 109, 196, 12, 0 }, + { 195, 49, 20, 1 }, + { 24, 17, 219, 0 }, + { 148, 70, 97, 1 }, }, + { { 109, 210, 20, 1 }, + { 250, 49, 84, 0 }, + { 148, 37, 219, 0 }, + { 21, 70, 47, 1 }, }, + { { 98, 150, 165, 1 }, + { 43, 225, 240, 0 }, + { 210, 180, 163, 0 }, + { 7, 195, 234, 0 }, }, + { { 98, 117, 38, 1 }, + { 73, 163, 211, 0 }, + { 178, 87, 35, 0 }, + { 101, 226, 201, 0 }, }, + { { 98, 100, 214, 1 }, + { 89, 39, 178, 0 }, + { 181, 147, 35, 0 }, + { 38, 242, 77, 0 }, }, + { { 98, 91, 220, 1 }, + { 120, 45, 241, 1 }, + { 157, 237, 35, 0 }, + { 199, 218, 15, 0 }, }, + { { 110, 51, 2, 1 }, + { 168, 35, 199, 0 }, + { 160, 102, 59, 0 }, + { 113, 226, 10, 1 }, }, + { { 110, 90, 137, 0 }, + { 224, 105, 228, 1 }, + { 72, 173, 59, 0 }, + { 147, 203, 3, 1 }, }, + { { 99, 209, 212, 0 }, + { 82, 53, 241, 0 }, + { 21, 197, 227, 0 }, + { 71, 214, 37, 0 }, }, + { { 99, 75, 129, 1 }, + { 104, 121, 161, 0 }, + { 192, 233, 99, 0 }, + { 66, 207, 11, 0 }, }, + { { 107, 56, 145, 1 }, + { 152, 121, 226, 0 }, + { 196, 142, 107, 0 }, + { 35, 207, 12, 1 }, }, + { { 107, 122, 42, 0 }, + { 224, 187, 194, 1 }, + { 42, 47, 107, 0 }, + { 161, 238, 131, 1 }, }, + { { 116, 56, 51, 0 }, + { 20, 235, 70, 0 }, + { 102, 14, 23, 0 }, + { 49, 107, 148, 0 }, }, + { { 112, 6, 44, 0 }, + { 37, 161, 16, 1 }, + { 26, 48, 7, 0 }, + { 132, 66, 210, 0 }, }, + { { 116, 170, 141, 1 }, + { 46, 105, 54, 1 }, + { 216, 170, 151, 0 }, + { 182, 75, 58, 0 }, }, + { { 116, 76, 143, 0 }, + { 69, 107, 52, 1 }, + { 120, 153, 23, 0 }, + { 150, 107, 81, 0 }, }, + { { 124, 183, 167, 0 }, + { 167, 227, 119, 0 }, + { 114, 246, 159, 0 }, + { 119, 99, 242, 1 }, }, + { { 120, 3, 22, 1 }, + { 188, 35, 17, 0 }, + { 180, 96, 15, 0 }, + { 68, 98, 30, 1 }, }, + { { 124, 199, 198, 1 }, + { 239, 39, 53, 0 }, + { 177, 241, 159, 0 }, + { 86, 114, 123, 1 }, }, + { { 117, 182, 4, 1 }, + { 47, 49, 86, 0 }, + { 144, 54, 215, 0 }, + { 53, 70, 122, 0 }, }, + { { 113, 254, 101, 0 }, + { 103, 253, 82, 0 }, + { 83, 63, 199, 0 }, + { 37, 95, 243, 0 }, }, + { { 113, 79, 154, 1 }, + { 125, 59, 33, 1 }, + { 172, 249, 71, 0 }, + { 194, 110, 95, 0 }, }, + { { 121, 32, 119, 0 }, + { 148, 247, 18, 0 }, + { 119, 2, 79, 0 }, + { 36, 119, 148, 1 }, }, + { { 125, 121, 205, 0 }, + { 196, 125, 119, 1 }, + { 89, 207, 95, 0 }, + { 247, 95, 17, 1 }, }, + { { 125, 67, 92, 0 }, + { 244, 53, 21, 1 }, + { 29, 97, 95, 0 }, + { 212, 86, 23, 1 }, }, + { { 125, 127, 59, 0 }, + { 245, 251, 71, 1 }, + { 110, 127, 95, 0 }, + { 241, 111, 215, 1 }, }, + { { 118, 85, 8, 0 }, + { 69, 33, 197, 1 }, + { 8, 85, 55, 0 }, + { 209, 194, 81, 0 }, }, + { { 122, 152, 199, 0 }, + { 134, 111, 240, 0 }, + { 113, 140, 175, 0 }, + { 7, 251, 48, 1 }, }, + { { 126, 19, 192, 0 }, + { 164, 37, 229, 0 }, + { 1, 228, 63, 0 }, + { 83, 210, 18, 1 }, }, + { { 126, 94, 89, 1 }, + { 253, 109, 196, 1 }, + { 205, 61, 63, 0 }, + { 145, 219, 95, 1 }, }, + { { 115, 36, 132, 0 }, + { 5, 49, 178, 0 }, + { 16, 146, 103, 0 }, + { 38, 198, 80, 0 }, }, + { { 115, 46, 39, 1 }, + { 45, 251, 146, 0 }, + { 242, 58, 103, 0 }, + { 36, 239, 218, 0 }, }, + { { 115, 139, 84, 1 }, + { 62, 61, 145, 0 }, + { 149, 104, 231, 0 }, + { 68, 222, 62, 0 }, }, + { { 132, 4, 34, 0 }, + { 1, 130, 12, 0 }, + { 34, 16, 16, 1 }, + { 24, 32, 192, 0 }, }, + { { 128, 232, 46, 0 }, + { 66, 138, 26, 1 }, + { 58, 11, 128, 1 }, + { 172, 40, 161, 0 }, }, + { { 128, 237, 48, 0 }, + { 83, 136, 11, 0 }, + { 6, 91, 128, 1 }, + { 104, 8, 229, 0 }, }, + { { 140, 53, 96, 0 }, + { 129, 132, 79, 0 }, + { 3, 86, 24, 1 }, + { 121, 16, 192, 1 }, }, + { { 140, 120, 240, 1 }, + { 216, 140, 110, 0 }, + { 135, 143, 24, 1 }, + { 59, 24, 141, 1 }, }, + { { 136, 194, 128, 1 }, + { 234, 0, 40, 0 }, + { 128, 161, 136, 1 }, + { 10, 0, 43, 1 }, }, + { { 129, 26, 100, 1 }, + { 40, 156, 88, 0 }, + { 147, 44, 64, 1 }, + { 13, 28, 138, 0 }, }, + { { 129, 38, 163, 0 }, + { 33, 210, 42, 0 }, + { 98, 178, 64, 1 }, + { 42, 37, 194, 0 }, }, + { { 133, 154, 140, 1 }, + { 42, 24, 124, 1 }, + { 152, 172, 208, 1 }, + { 159, 12, 42, 0 }, }, + { { 137, 19, 23, 1 }, + { 184, 82, 89, 0 }, + { 244, 100, 72, 1 }, + { 77, 37, 14, 1 }, }, + { { 130, 9, 37, 0 }, + { 0, 200, 153, 0 }, + { 82, 72, 32, 1 }, + { 76, 137, 128, 0 }, }, + { { 130, 183, 201, 1 }, + { 43, 68, 235, 1 }, + { 201, 246, 160, 1 }, + { 235, 145, 106, 0 }, }, + { { 142, 185, 191, 1 }, + { 154, 202, 255, 1 }, + { 254, 206, 184, 1 }, + { 255, 169, 172, 1 }, }, + { { 138, 159, 236, 0 }, + { 163, 140, 249, 1 }, + { 27, 252, 168, 1 }, + { 207, 152, 226, 1 }, }, + { { 142, 228, 226, 0 }, + { 195, 134, 174, 0 }, + { 35, 147, 184, 1 }, + { 58, 176, 225, 1 }, }, + { { 135, 32, 232, 0 }, + { 0, 148, 174, 1 }, + { 11, 130, 112, 1 }, + { 186, 148, 128, 0 }, }, + { { 135, 205, 96, 1 }, + { 75, 156, 141, 0 }, + { 131, 89, 240, 1 }, + { 88, 156, 233, 0 }, }, + { { 135, 71, 172, 1 }, + { 105, 144, 189, 1 }, + { 154, 241, 112, 1 }, + { 222, 132, 203, 0 }, }, + { { 139, 10, 91, 0 }, + { 176, 94, 136, 1 }, + { 109, 40, 104, 1 }, + { 136, 189, 6, 1 }, }, + { { 139, 92, 190, 1 }, + { 217, 154, 248, 1 }, + { 190, 157, 104, 1 }, + { 143, 172, 205, 1 }, }, + { { 143, 213, 180, 0 }, + { 211, 144, 253, 0 }, + { 22, 213, 248, 1 }, + { 95, 132, 229, 1 }, }, + { { 139, 118, 41, 0 }, + { 225, 208, 202, 1 }, + { 74, 55, 104, 1 }, + { 169, 133, 195, 1 }, }, + { { 148, 51, 175, 0 }, + { 36, 194, 127, 1 }, + { 122, 230, 20, 1 }, + { 255, 33, 146, 0 }, }, + { { 144, 85, 72, 1 }, + { 77, 4, 73, 1 }, + { 137, 85, 4, 1 }, + { 201, 16, 89, 0 }, }, + { { 156, 62, 39, 0 }, + { 165, 202, 94, 0 }, + { 114, 62, 28, 1 }, + { 61, 41, 210, 1 }, }, + { { 152, 151, 159, 0 }, + { 183, 66, 121, 1 }, + { 124, 244, 140, 1 }, + { 207, 33, 118, 1 }, }, + { { 152, 88, 144, 1 }, + { 220, 8, 104, 0 }, + { 132, 141, 12, 1 }, + { 11, 8, 29, 1 }, }, + { { 149, 182, 31, 0 }, + { 55, 82, 94, 1 }, + { 124, 54, 212, 1 }, + { 189, 37, 118, 0 }, }, + { { 145, 64, 55, 0 }, + { 84, 210, 24, 0 }, + { 118, 1, 68, 1 }, + { 12, 37, 149, 0 }, }, + { { 157, 179, 128, 0 }, + { 166, 16, 111, 0 }, + { 0, 230, 220, 1 }, + { 123, 4, 50, 1 }, }, + { { 153, 65, 137, 1 }, + { 204, 80, 41, 1 }, + { 200, 193, 76, 1 }, + { 202, 5, 25, 1 }, }, + { { 146, 173, 38, 0 }, + { 7, 138, 155, 0 }, + { 50, 90, 164, 1 }, + { 108, 168, 240, 0 }, }, + { { 146, 101, 228, 1 }, + { 77, 132, 187, 0 }, + { 147, 211, 36, 1 }, + { 110, 144, 217, 0 }, }, + { { 146, 111, 141, 1 }, + { 109, 72, 187, 1 }, + { 216, 251, 36, 1 }, + { 238, 137, 91, 0 }, }, + { { 150, 94, 153, 0 }, + { 117, 72, 236, 1 }, + { 76, 189, 52, 1 }, + { 155, 137, 87, 0 }, }, + { { 154, 11, 77, 0 }, + { 164, 76, 153, 1 }, + { 89, 104, 44, 1 }, + { 204, 153, 18, 1 }, }, + { { 154, 221, 17, 0 }, + { 215, 72, 201, 0 }, + { 68, 93, 172, 1 }, + { 73, 137, 117, 1 }, }, + { { 147, 246, 156, 1 }, + { 127, 16, 250, 1 }, + { 156, 183, 228, 1 }, + { 175, 132, 127, 0 }, }, + { { 159, 52, 189, 1 }, + { 157, 208, 254, 1 }, + { 222, 150, 124, 1 }, + { 191, 133, 220, 1 }, }, + { { 159, 80, 9, 0 }, + { 196, 80, 204, 1 }, + { 72, 5, 124, 1 }, + { 153, 133, 17, 1 }, }, + { { 160, 153, 27, 1 }, + { 26, 106, 73, 1 }, + { 236, 76, 130, 1 }, + { 201, 43, 44, 0 }, }, + { { 164, 15, 234, 0 }, + { 33, 174, 45, 1 }, + { 43, 248, 18, 1 }, + { 218, 58, 194, 0 }, }, + { { 160, 15, 83, 1 }, + { 57, 110, 9, 0 }, + { 229, 120, 2, 1 }, + { 72, 59, 78, 0 }, }, + { { 168, 2, 105, 1 }, + { 168, 228, 8, 1 }, + { 203, 32, 10, 1 }, + { 136, 19, 138, 1 }, }, + { { 172, 27, 205, 1 }, + { 168, 108, 125, 1 }, + { 217, 236, 26, 1 }, + { 223, 27, 10, 1 }, }, + { { 172, 135, 77, 0 }, + { 163, 100, 29, 1 }, + { 89, 112, 154, 1 }, + { 220, 19, 98, 1 }, }, + { { 172, 118, 200, 0 }, + { 225, 36, 110, 1 }, + { 9, 183, 26, 1 }, + { 187, 18, 67, 1 }, }, + { { 165, 68, 167, 0 }, + { 65, 242, 60, 0 }, + { 114, 145, 82, 1 }, + { 30, 39, 193, 0 }, }, + { { 161, 244, 92, 0 }, + { 83, 52, 90, 1 }, + { 29, 23, 194, 1 }, + { 173, 22, 101, 0 }, }, + { { 169, 157, 169, 0 }, + { 131, 248, 105, 1 }, + { 74, 220, 202, 1 }, + { 203, 15, 224, 1 }, }, + { { 169, 135, 255, 0 }, + { 179, 246, 57, 1 }, + { 127, 240, 202, 1 }, + { 206, 55, 230, 1 }, }, + { { 169, 71, 98, 1 }, + { 233, 182, 9, 0 }, + { 163, 113, 74, 1 }, + { 72, 54, 203, 1 }, }, + { { 166, 129, 114, 1 }, + { 26, 166, 141, 0 }, + { 167, 64, 178, 1 }, + { 88, 178, 172, 0 }, }, + { { 166, 249, 172, 1 }, + { 74, 168, 255, 1 }, + { 154, 207, 178, 1 }, + { 255, 138, 169, 0 }, }, + { { 162, 196, 38, 0 }, + { 67, 162, 152, 0 }, + { 50, 17, 162, 1 }, + { 12, 162, 225, 0 }, }, + { { 170, 53, 101, 1 }, + { 137, 228, 219, 0 }, + { 211, 86, 42, 1 }, + { 109, 147, 200, 1 }, }, + { { 170, 168, 102, 0 }, + { 130, 174, 154, 0 }, + { 51, 10, 170, 1 }, + { 44, 186, 160, 1 }, }, + { { 170, 200, 143, 0 }, + { 194, 106, 184, 1 }, + { 120, 137, 170, 1 }, + { 142, 171, 33, 1 }, }, + { { 167, 110, 148, 1 }, + { 121, 56, 190, 0 }, + { 148, 187, 114, 1 }, + { 62, 142, 79, 0 }, }, + { { 175, 4, 36, 1 }, + { 137, 176, 156, 0 }, + { 146, 16, 122, 1 }, + { 28, 134, 200, 1 }, }, + { { 176, 65, 44, 0 }, + { 68, 160, 25, 1 }, + { 26, 65, 6, 1 }, + { 204, 2, 145, 0 }, }, + { { 176, 208, 227, 0 }, + { 70, 230, 104, 0 }, + { 99, 133, 134, 1 }, + { 11, 51, 177, 0 }, }, + { { 176, 237, 129, 1 }, + { 79, 104, 43, 0 }, + { 192, 219, 134, 1 }, + { 106, 11, 121, 0 }, }, + { { 180, 231, 236, 0 }, + { 103, 164, 63, 1 }, + { 27, 243, 150, 1 }, + { 254, 18, 243, 0 }, }, + { { 184, 160, 236, 1 }, + { 142, 164, 58, 1 }, + { 155, 130, 142, 1 }, + { 174, 18, 184, 1 }, }, + { { 184, 188, 60, 1 }, + { 159, 168, 90, 1 }, + { 158, 30, 142, 1 }, + { 173, 10, 252, 1 }, }, + { { 177, 102, 224, 1 }, + { 109, 180, 42, 0 }, + { 131, 179, 70, 1 }, + { 42, 22, 219, 0 }, }, + { { 189, 49, 10, 1 }, + { 140, 50, 79, 1 }, + { 168, 70, 94, 1 }, + { 249, 38, 24, 1 }, }, + { { 189, 63, 186, 1 }, + { 189, 186, 111, 1 }, + { 174, 254, 94, 1 }, + { 251, 46, 222, 1 }, }, + { { 185, 190, 220, 0 }, + { 183, 60, 122, 1 }, + { 29, 190, 206, 1 }, + { 175, 30, 118, 1 }, }, + { { 178, 58, 240, 1 }, + { 60, 172, 234, 0 }, + { 135, 174, 38, 1 }, + { 43, 154, 158, 0 }, }, + { { 182, 207, 169, 0 }, + { 103, 232, 173, 1 }, + { 74, 249, 182, 1 }, + { 218, 139, 243, 0 }, }, + { { 186, 35, 226, 1 }, + { 172, 166, 171, 0 }, + { 163, 226, 46, 1 }, + { 106, 178, 154, 1 }, }, + { { 183, 36, 171, 1 }, + { 13, 242, 174, 1 }, + { 234, 146, 118, 1 }, + { 186, 167, 216, 0 }, }, + { { 183, 41, 251, 0 }, + { 20, 254, 175, 1 }, + { 111, 202, 118, 1 }, + { 250, 191, 148, 0 }, }, + { { 179, 174, 8, 0 }, + { 39, 56, 138, 1 }, + { 8, 58, 230, 1 }, + { 168, 142, 114, 0 }, }, + { { 183, 86, 202, 1 }, + { 109, 54, 236, 1 }, + { 169, 181, 118, 1 }, + { 155, 182, 91, 0 }, }, + { { 191, 168, 244, 0 }, + { 150, 188, 190, 0 }, + { 23, 138, 254, 1 }, + { 62, 158, 180, 1 }, }, + { { 191, 148, 18, 1 }, + { 159, 50, 204, 0 }, + { 164, 20, 254, 1 }, + { 25, 166, 124, 1 }, }, + { { 191, 122, 64, 1 }, + { 236, 60, 206, 0 }, + { 129, 47, 126, 1 }, + { 57, 158, 27, 1 }, }, + { { 196, 133, 200, 0 }, + { 3, 5, 45, 1 }, + { 9, 208, 145, 1 }, + { 218, 80, 96, 0 }, }, + { { 192, 14, 9, 1 }, + { 41, 73, 8, 1 }, + { 200, 56, 1, 1 }, + { 136, 73, 74, 0 }, }, + { { 192, 112, 254, 0 }, + { 80, 135, 122, 1 }, + { 63, 135, 1, 1 }, + { 175, 112, 133, 0 }, }, + { { 204, 116, 17, 0 }, + { 209, 65, 78, 0 }, + { 68, 23, 25, 1 }, + { 57, 65, 69, 1 }, }, + { { 193, 8, 90, 1 }, + { 24, 31, 8, 1 }, + { 173, 8, 65, 1 }, + { 136, 124, 12, 0 }, }, + { { 193, 47, 130, 1 }, + { 41, 27, 43, 0 }, + { 160, 250, 65, 1 }, + { 106, 108, 74, 0 }, }, + { { 193, 35, 120, 0 }, + { 48, 149, 11, 1 }, + { 15, 98, 65, 1 }, + { 232, 84, 134, 0 }, }, + { { 198, 52, 219, 1 }, + { 25, 71, 238, 1 }, + { 237, 150, 49, 1 }, + { 187, 241, 76, 0 }, }, + { { 198, 164, 244, 1 }, + { 27, 133, 190, 0 }, + { 151, 146, 177, 1 }, + { 62, 208, 236, 0 }, }, + { { 194, 228, 25, 0 }, + { 83, 65, 138, 1 }, + { 76, 19, 161, 1 }, + { 168, 193, 101, 0 }, }, + { { 195, 29, 35, 0 }, + { 1, 219, 201, 0 }, + { 98, 92, 97, 1 }, + { 73, 237, 192, 0 }, }, + { { 195, 242, 196, 1 }, + { 106, 21, 250, 0 }, + { 145, 167, 225, 1 }, + { 47, 212, 43, 0 }, }, + { { 199, 95, 223, 0 }, + { 113, 95, 253, 1 }, + { 125, 253, 113, 1 }, + { 223, 253, 71, 0 }, }, + { { 203, 101, 189, 0 }, + { 209, 209, 187, 1 }, + { 94, 211, 105, 1 }, + { 238, 197, 197, 1 }, }, + { { 203, 199, 15, 1 }, + { 235, 83, 153, 1 }, + { 248, 113, 233, 1 }, + { 204, 229, 107, 1 }, }, + { { 207, 246, 185, 0 }, + { 243, 209, 238, 1 }, + { 78, 183, 249, 1 }, + { 187, 197, 231, 1 }, }, + { { 212, 84, 239, 0 }, + { 69, 199, 124, 1 }, + { 123, 149, 21, 1 }, + { 159, 113, 209, 0 }, }, + { { 212, 251, 220, 0 }, + { 118, 13, 127, 1 }, + { 29, 239, 149, 1 }, + { 255, 88, 55, 0 }, }, + { { 220, 232, 84, 0 }, + { 214, 13, 30, 0 }, + { 21, 11, 157, 1 }, + { 60, 88, 53, 1 }, }, + { { 213, 177, 150, 0 }, + { 22, 19, 127, 0 }, + { 52, 198, 213, 1 }, + { 127, 100, 52, 0 }, }, + { { 209, 159, 7, 1 }, + { 47, 91, 89, 0 }, + { 240, 124, 197, 1 }, + { 77, 109, 122, 0 }, }, + { { 209, 93, 126, 1 }, + { 93, 159, 89, 1 }, + { 191, 93, 69, 1 }, + { 205, 124, 221, 0 }, }, + { { 217, 164, 134, 1 }, + { 143, 19, 58, 0 }, + { 176, 146, 205, 1 }, + { 46, 100, 120, 1 }, }, + { { 221, 9, 48, 1 }, + { 156, 153, 13, 0 }, + { 134, 72, 93, 1 }, + { 88, 76, 156, 1 }, }, + { { 217, 180, 57, 1 }, + { 159, 209, 74, 1 }, + { 206, 22, 205, 1 }, + { 169, 69, 252, 1 }, }, + { { 221, 171, 105, 1 }, + { 174, 221, 15, 1 }, + { 203, 106, 221, 1 }, + { 248, 93, 186, 1 }, }, + { { 217, 203, 66, 0 }, + { 230, 31, 9, 0 }, + { 33, 105, 205, 1 }, + { 72, 124, 51, 1 }, }, + { { 210, 33, 13, 0 }, + { 4, 65, 155, 1 }, + { 88, 66, 37, 1 }, + { 236, 193, 16, 0 }, }, + { { 214, 73, 4, 0 }, + { 68, 9, 157, 0 }, + { 16, 73, 53, 1 }, + { 92, 200, 17, 0 }, }, + { { 210, 216, 219, 0 }, + { 86, 79, 232, 1 }, + { 109, 141, 165, 1 }, + { 139, 249, 53, 0 }, }, + { { 218, 63, 69, 1 }, + { 173, 77, 219, 0 }, + { 209, 126, 45, 1 }, + { 109, 217, 90, 1 }, }, + { { 218, 125, 218, 0 }, + { 213, 15, 235, 1 }, + { 45, 223, 45, 1 }, + { 235, 248, 85, 1 }, }, + { { 218, 211, 230, 0 }, + { 230, 135, 249, 0 }, + { 51, 229, 173, 1 }, + { 79, 240, 179, 1 }, }, + { { 211, 51, 189, 0 }, + { 52, 209, 251, 1 }, + { 94, 230, 101, 1 }, + { 239, 197, 150, 0 }, }, + { { 215, 225, 237, 1 }, + { 78, 213, 191, 1 }, + { 219, 195, 245, 1 }, + { 254, 213, 185, 0 }, }, + { { 219, 197, 122, 0 }, + { 215, 151, 137, 1 }, + { 47, 81, 237, 1 }, + { 200, 244, 245, 1 }, }, + { { 224, 128, 59, 1 }, + { 26, 227, 8, 1 }, + { 238, 0, 131, 1 }, + { 136, 99, 172, 0 }, }, + { { 224, 207, 105, 1 }, + { 107, 237, 9, 1 }, + { 203, 121, 131, 1 }, + { 200, 91, 235, 0 }, }, + { { 228, 71, 48, 1 }, + { 121, 161, 13, 0 }, + { 134, 113, 19, 1 }, + { 88, 66, 207, 0 }, }, + { { 232, 29, 86, 1 }, + { 153, 47, 89, 0 }, + { 181, 92, 11, 1 }, + { 77, 122, 76, 1 }, }, + { { 232, 243, 72, 1 }, + { 234, 37, 75, 1 }, + { 137, 103, 139, 1 }, + { 233, 82, 43, 1 }, }, + { { 232, 254, 175, 0 }, + { 227, 235, 122, 1 }, + { 122, 191, 139, 1 }, + { 175, 107, 227, 1 }, }, + { { 236, 231, 159, 0 }, + { 243, 99, 63, 1 }, + { 124, 243, 155, 1 }, + { 254, 99, 103, 1 }, }, + { { 225, 60, 212, 1 }, + { 25, 61, 122, 0 }, + { 149, 158, 67, 1 }, + { 47, 94, 76, 0 }, }, + { { 229, 204, 106, 1 }, + { 75, 191, 12, 1 }, + { 171, 25, 211, 1 }, + { 152, 126, 233, 0 }, }, + { { 233, 175, 154, 0 }, + { 179, 59, 43, 1 }, + { 44, 250, 203, 1 }, + { 234, 110, 102, 1 }, }, + { { 227, 0, 237, 0 }, + { 0, 245, 184, 1 }, + { 91, 128, 99, 1 }, + { 142, 215, 128, 0 }, }, + { { 231, 177, 14, 0 }, + { 2, 51, 223, 1 }, + { 56, 70, 243, 1 }, + { 253, 230, 32, 0 }, }, + { { 227, 192, 126, 0 }, + { 82, 183, 152, 1 }, + { 63, 1, 227, 1 }, + { 140, 246, 165, 0 }, }, + { { 235, 140, 163, 0 }, + { 131, 251, 168, 0 }, + { 98, 152, 235, 1 }, + { 10, 239, 224, 1 }, }, + { { 235, 159, 109, 1 }, + { 171, 253, 217, 1 }, + { 219, 124, 235, 1 }, + { 205, 223, 234, 1 }, }, + { { 239, 65, 11, 0 }, + { 192, 115, 141, 1 }, + { 104, 65, 123, 1 }, + { 216, 231, 1, 1 }, }, + { { 239, 194, 195, 0 }, + { 226, 119, 172, 0 }, + { 97, 161, 251, 1 }, + { 26, 247, 35, 1 }, }, + { { 240, 168, 32, 0 }, + { 6, 169, 10, 0 }, + { 2, 10, 135, 1 }, + { 40, 74, 176, 0 }, }, + { { 244, 25, 211, 0 }, + { 20, 111, 109, 0 }, + { 101, 204, 23, 1 }, + { 91, 123, 20, 0 }, }, + { { 244, 133, 191, 1 }, + { 31, 227, 61, 1 }, + { 254, 208, 151, 1 }, + { 222, 99, 252, 0 }, }, + { { 240, 106, 204, 0 }, + { 100, 45, 58, 1 }, + { 25, 171, 7, 1 }, + { 174, 90, 19, 0 }, }, + { { 240, 123, 107, 1 }, + { 108, 239, 75, 1 }, + { 235, 111, 7, 1 }, + { 233, 123, 155, 0 }, }, + { { 244, 251, 170, 0 }, + { 102, 171, 111, 1 }, + { 42, 239, 151, 1 }, + { 251, 106, 179, 0 }, }, + { { 252, 198, 47, 0 }, + { 231, 227, 28, 1 }, + { 122, 49, 159, 1 }, + { 156, 99, 243, 1 }, }, + { { 248, 111, 16, 0 }, + { 245, 41, 11, 0 }, + { 4, 123, 15, 1 }, + { 104, 74, 87, 1 }, }, + { { 252, 127, 120, 1 }, + { 253, 173, 79, 1 }, + { 143, 127, 31, 1 }, + { 249, 90, 223, 1 }, }, + { { 241, 171, 217, 0 }, + { 54, 125, 43, 1 }, + { 77, 234, 199, 1 }, + { 234, 95, 54, 0 }, }, + { { 245, 81, 143, 1 }, + { 76, 115, 125, 1 }, + { 248, 197, 87, 1 }, + { 223, 103, 25, 0 }, }, + { { 241, 248, 166, 1 }, + { 78, 187, 122, 0 }, + { 178, 143, 199, 1 }, + { 47, 110, 185, 0 }, }, + { { 241, 86, 24, 0 }, + { 117, 49, 72, 1 }, + { 12, 53, 71, 1 }, + { 137, 70, 87, 0 }, }, + { { 253, 132, 196, 0 }, + { 135, 53, 60, 0 }, + { 17, 144, 223, 1 }, + { 30, 86, 112, 1 }, }, + { { 249, 141, 114, 1 }, + { 159, 191, 9, 0 }, + { 167, 88, 207, 1 }, + { 72, 126, 252, 1 }, }, + { { 253, 15, 85, 1 }, + { 189, 125, 29, 0 }, + { 213, 120, 95, 1 }, + { 92, 95, 94, 1 }, }, + { { 242, 156, 111, 1 }, + { 15, 239, 216, 1 }, + { 251, 28, 167, 1 }, + { 141, 251, 248, 0 }, }, + { { 246, 136, 24, 1 }, + { 30, 41, 140, 1 }, + { 140, 8, 183, 1 }, + { 152, 202, 60, 0 }, }, + { { 246, 7, 197, 0 }, + { 37, 101, 189, 0 }, + { 81, 240, 55, 1 }, + { 94, 211, 82, 0 }, }, + { { 242, 65, 215, 0 }, + { 84, 103, 185, 0 }, + { 117, 193, 39, 1 }, + { 78, 243, 21, 0 }, }, + { { 246, 241, 27, 0 }, + { 86, 99, 207, 1 }, + { 108, 71, 183, 1 }, + { 249, 227, 53, 0 }, }, + { { 242, 230, 203, 1 }, + { 111, 103, 170, 1 }, + { 233, 179, 167, 1 }, + { 170, 243, 123, 0 }, }, + { { 250, 213, 32, 1 }, + { 207, 161, 201, 0 }, + { 130, 85, 175, 1 }, + { 73, 194, 249, 1 }, }, + { { 254, 67, 233, 0 }, + { 228, 229, 173, 1 }, + { 75, 225, 63, 1 }, + { 218, 211, 147, 1 }, }, + { { 243, 251, 142, 0 }, + { 102, 59, 251, 1 }, + { 56, 239, 231, 1 }, + { 239, 238, 51, 0 }, }, + { { 255, 187, 218, 0 }, + { 182, 63, 239, 1 }, + { 45, 238, 255, 1 }, + { 251, 254, 54, 1 }, }, + { { 251, 201, 14, 1 }, + { 206, 59, 153, 1 }, + { 184, 73, 239, 1 }, + { 204, 238, 57, 1 }, }, }; + +static unsigned char DICT_6X6_1000_BYTES[][4][5] = + { { { 30, 61, 216, 42, 6 }, + { 227, 186, 70, 49, 9 }, + { 101, 65, 187, 199, 8 }, + { 152, 198, 37, 220, 7 }, }, + { { 14, 251, 163, 137, 1 }, + { 215, 230, 24, 5, 14 }, + { 137, 28, 93, 247, 0 }, + { 122, 1, 134, 126, 11 }, }, + { { 21, 144, 126, 172, 13 }, + { 236, 105, 87, 80, 6 }, + { 179, 87, 224, 154, 8 }, + { 96, 174, 169, 99, 7 }, }, + { { 201, 27, 48, 105, 14 }, + { 66, 50, 75, 222, 12 }, + { 121, 96, 205, 137, 3 }, + { 55, 189, 36, 196, 2 }, }, + { { 214, 7, 214, 225, 5 }, + { 164, 203, 74, 191, 2 }, + { 168, 118, 190, 6, 11 }, + { 79, 213, 45, 50, 5 }, }, + { { 216, 232, 224, 230, 8 }, + { 43, 140, 19, 138, 15 }, + { 22, 112, 113, 113, 11 }, + { 245, 28, 131, 29, 4 }, }, + { { 66, 104, 180, 31, 5 }, + { 13, 165, 192, 149, 13 }, + { 175, 130, 209, 100, 2 }, + { 186, 144, 58, 91, 0 }, }, + { { 136, 165, 15, 41, 10 }, + { 19, 115, 23, 38, 0 }, + { 89, 79, 10, 81, 1 }, + { 6, 78, 140, 236, 8 }, }, + { { 48, 125, 82, 79, 13 }, + { 109, 110, 97, 60, 9 }, + { 191, 36, 171, 224, 12 }, + { 147, 200, 103, 107, 6 }, }, + { { 60, 47, 52, 179, 12 }, + { 131, 11, 235, 52, 15 }, + { 60, 210, 207, 67, 12 }, + { 242, 205, 125, 12, 1 }, }, + { { 69, 223, 199, 78, 3 }, + { 252, 247, 24, 232, 9 }, + { 199, 46, 63, 186, 2 }, + { 145, 113, 142, 243, 15 }, }, + { { 72, 216, 91, 37, 7 }, + { 126, 84, 86, 148, 8 }, + { 234, 77, 161, 177, 2 }, + { 18, 150, 162, 167, 14 }, }, + { { 113, 5, 88, 252, 6 }, + { 40, 58, 230, 248, 2 }, + { 99, 241, 170, 8, 14 }, + { 65, 246, 117, 193, 4 }, }, + { { 134, 220, 250, 208, 7 }, + { 228, 212, 212, 59, 14 }, + { 224, 181, 243, 182, 1 }, + { 125, 194, 178, 178, 7 }, }, + { { 141, 114, 169, 63, 6 }, + { 219, 180, 206, 70, 5 }, + { 111, 201, 84, 235, 1 }, + { 166, 39, 50, 221, 11 }, }, + { { 162, 184, 157, 205, 14 }, + { 89, 177, 117, 31, 10 }, + { 123, 59, 145, 212, 5 }, + { 95, 138, 232, 217, 10 }, }, + { { 9, 253, 30, 156, 4 }, + { 75, 103, 212, 112, 10 }, + { 35, 151, 139, 249, 0 }, + { 80, 226, 190, 109, 2 }, }, + { { 21, 77, 189, 24, 15 }, + { 148, 191, 197, 112, 12 }, + { 241, 139, 219, 42, 8 }, + { 48, 234, 63, 210, 9 }, }, + { { 48, 10, 49, 14, 2 }, + { 24, 56, 40, 16, 13 }, + { 71, 8, 197, 0, 12 }, + { 176, 129, 65, 193, 8 }, }, + { { 72, 7, 239, 175, 13 }, + { 62, 227, 79, 164, 7 }, + { 191, 95, 126, 1, 2 }, + { 226, 95, 44, 119, 12 }, }, + { { 86, 223, 17, 219, 6 }, + { 208, 62, 216, 189, 11 }, + { 109, 184, 143, 182, 10 }, + { 219, 209, 183, 192, 11 }, }, + { { 102, 136, 50, 116, 12 }, + { 136, 64, 243, 153, 12 }, + { 50, 228, 193, 22, 6 }, + { 57, 156, 240, 33, 1 }, }, + { { 118, 232, 203, 120, 1 }, + { 181, 236, 182, 137, 8 }, + { 129, 237, 49, 118, 14 }, + { 25, 22, 211, 122, 13 }, }, + { { 154, 83, 217, 207, 3 }, + { 126, 190, 12, 31, 3 }, + { 207, 57, 188, 165, 9 }, + { 207, 131, 7, 215, 14 }, }, + { { 169, 203, 132, 2, 4 }, + { 2, 135, 120, 66, 9 }, + { 36, 2, 29, 57, 5 }, + { 148, 33, 238, 20, 0 }, }, + { { 198, 117, 73, 73, 0 }, + { 241, 38, 4, 175, 0 }, + { 9, 41, 42, 230, 3 }, + { 15, 82, 6, 72, 15 }, }, + { { 193, 210, 136, 148, 1 }, + { 76, 132, 156, 194, 2 }, + { 130, 145, 20, 184, 3 }, + { 68, 51, 146, 19, 2 }, }, + { { 231, 72, 8, 82, 11 }, + { 132, 20, 165, 203, 9 }, + { 212, 161, 1, 46, 7 }, + { 157, 58, 82, 130, 1 }, }, + { { 234, 47, 202, 132, 8 }, + { 43, 194, 45, 163, 10 }, + { 18, 21, 63, 69, 7 }, + { 92, 91, 68, 61, 4 }, }, + { { 233, 99, 183, 123, 1 }, + { 23, 231, 170, 222, 5 }, + { 141, 238, 220, 105, 7 }, + { 167, 181, 94, 126, 8 }, }, + { { 250, 54, 101, 42, 15 }, + { 119, 57, 107, 163, 5 }, + { 245, 74, 102, 197, 15 }, + { 172, 93, 105, 206, 14 }, }, + { { 6, 91, 255, 123, 13 }, + { 244, 231, 207, 29, 13 }, + { 189, 239, 253, 166, 0 }, + { 187, 143, 62, 114, 15 }, }, + { { 5, 65, 215, 45, 6 }, + { 184, 247, 66, 84, 0 }, + { 107, 78, 184, 42, 0 }, + { 2, 164, 46, 241, 13 }, }, + { { 12, 247, 36, 106, 2 }, + { 195, 55, 26, 40, 5 }, + { 69, 98, 78, 243, 0 }, + { 161, 69, 142, 204, 3 }, }, + { { 19, 56, 163, 158, 11 }, + { 93, 248, 129, 65, 15 }, + { 215, 156, 81, 204, 8 }, + { 248, 40, 17, 251, 10 }, }, + { { 21, 168, 147, 231, 4 }, + { 153, 200, 82, 92, 11 }, + { 46, 124, 145, 90, 8 }, + { 211, 164, 161, 57, 9 }, }, + { { 58, 65, 126, 233, 14 }, + { 34, 127, 103, 29, 6 }, + { 121, 119, 232, 37, 12 }, + { 107, 142, 111, 228, 4 }, }, + { { 79, 17, 226, 108, 0 }, + { 234, 226, 2, 201, 4 }, + { 3, 100, 120, 143, 2 }, + { 41, 52, 4, 117, 7 }, }, + { { 83, 13, 182, 210, 0 }, + { 0, 203, 128, 249, 15 }, + { 4, 182, 219, 12, 10 }, + { 249, 240, 29, 48, 0 }, }, + { { 88, 155, 250, 227, 4 }, + { 98, 202, 94, 156, 15 }, + { 44, 117, 253, 145, 10 }, + { 243, 151, 165, 52, 6 }, }, + { { 100, 9, 232, 160, 11 }, + { 164, 146, 39, 128, 14 }, + { 208, 81, 121, 2, 6 }, + { 112, 30, 68, 146, 5 }, }, + { { 96, 83, 122, 137, 1 }, + { 100, 102, 44, 148, 6 }, + { 137, 21, 236, 160, 6 }, + { 98, 147, 70, 98, 6 }, }, + { { 97, 89, 6, 155, 10 }, + { 64, 119, 161, 196, 11 }, + { 93, 150, 9, 168, 6 }, + { 210, 56, 94, 224, 2 }, }, + { { 107, 255, 120, 215, 11 }, + { 111, 22, 189, 253, 15 }, + { 222, 177, 239, 253, 6 }, + { 251, 251, 214, 143, 6 }, }, + { { 112, 173, 150, 164, 15 }, + { 13, 219, 115, 176, 10 }, + { 242, 86, 155, 80, 14 }, + { 80, 220, 237, 187, 0 }, }, + { { 117, 132, 111, 113, 10 }, + { 176, 89, 183, 236, 4 }, + { 88, 239, 98, 26, 14 }, + { 35, 126, 217, 160, 13 }, }, + { { 122, 149, 25, 47, 12 }, + { 90, 42, 119, 181, 1 }, + { 63, 73, 138, 149, 14 }, + { 138, 222, 229, 69, 10 }, }, + { { 134, 9, 118, 10, 10 }, + { 160, 115, 1, 19, 13 }, + { 85, 6, 233, 6, 1 }, + { 188, 136, 12, 224, 5 }, }, + { { 138, 45, 68, 195, 15 }, + { 39, 19, 65, 47, 11 }, + { 252, 50, 43, 69, 1 }, + { 223, 72, 44, 142, 4 }, }, + { { 147, 235, 120, 177, 4 }, + { 33, 14, 222, 87, 14 }, + { 40, 209, 237, 124, 9 }, + { 126, 167, 183, 8, 4 }, }, + { { 152, 141, 168, 77, 4 }, + { 10, 170, 84, 46, 12 }, + { 43, 33, 91, 17, 9 }, + { 55, 66, 165, 85, 0 }, }, + { { 158, 222, 43, 60, 8 }, + { 218, 108, 159, 35, 12 }, + { 19, 205, 71, 183, 9 }, + { 60, 79, 147, 101, 11 }, }, + { { 165, 41, 224, 123, 8 }, + { 161, 162, 163, 78, 13 }, + { 29, 224, 121, 74, 5 }, + { 183, 44, 84, 88, 5 }, }, + { { 181, 147, 184, 85, 15 }, + { 204, 154, 253, 94, 4 }, + { 250, 161, 220, 154, 13 }, + { 39, 171, 245, 147, 3 }, }, + { { 183, 248, 228, 38, 15 }, + { 237, 157, 115, 67, 13 }, + { 246, 66, 113, 254, 13 }, + { 188, 44, 235, 155, 7 }, }, + { { 188, 32, 82, 37, 14 }, + { 171, 88, 99, 22, 0 }, + { 122, 68, 160, 67, 13 }, + { 6, 140, 97, 173, 5 }, }, + { { 192, 68, 135, 118, 5 }, + { 28, 197, 194, 170, 1 }, + { 166, 238, 18, 32, 3 }, + { 133, 84, 58, 51, 8 }, }, + { { 196, 195, 36, 37, 9 }, + { 140, 7, 27, 134, 4 }, + { 154, 66, 76, 50, 3 }, + { 38, 29, 142, 3, 1 }, }, + { { 197, 169, 27, 216, 13 }, + { 149, 98, 213, 218, 10 }, + { 177, 189, 137, 90, 3 }, + { 85, 186, 180, 106, 9 }, }, + { { 206, 115, 230, 178, 12 }, + { 227, 199, 203, 131, 7 }, + { 52, 214, 124, 231, 3 }, + { 236, 29, 62, 60, 7 }, }, + { { 205, 12, 166, 39, 2 }, + { 138, 209, 2, 230, 13 }, + { 78, 70, 83, 11, 3 }, + { 182, 116, 8, 181, 1 }, }, + { { 201, 67, 93, 68, 13 }, + { 62, 7, 77, 218, 0 }, + { 178, 43, 172, 41, 3 }, + { 5, 187, 46, 7, 12 }, }, + { { 207, 190, 128, 243, 4 }, + { 195, 128, 218, 239, 11 }, + { 44, 240, 23, 223, 3 }, + { 223, 117, 176, 28, 3 }, }, + { { 229, 125, 21, 135, 7 }, + { 221, 23, 96, 246, 11 }, + { 238, 26, 139, 234, 7 }, + { 214, 240, 110, 139, 11 }, }, + { { 239, 198, 133, 142, 9 }, + { 158, 165, 57, 227, 3 }, + { 151, 26, 22, 63, 7 }, + { 204, 121, 202, 87, 9 }, }, + { { 247, 126, 243, 119, 2 }, + { 249, 220, 170, 255, 13 }, + { 78, 236, 247, 238, 15 }, + { 191, 245, 83, 185, 15 }, }, + { { 44, 228, 63, 37, 4 }, + { 155, 69, 118, 52, 4 }, + { 42, 79, 194, 115, 4 }, + { 34, 198, 234, 45, 9 }, }, + { { 43, 220, 255, 75, 3 }, + { 118, 245, 52, 125, 13 }, + { 205, 47, 243, 189, 4 }, + { 187, 226, 202, 246, 14 }, }, + { { 55, 199, 221, 189, 10 }, + { 184, 191, 191, 117, 2 }, + { 91, 219, 190, 62, 12 }, + { 74, 239, 223, 209, 13 }, }, + { { 161, 162, 84, 224, 15 }, + { 37, 17, 123, 90, 2 }, + { 240, 114, 164, 88, 5 }, + { 69, 173, 232, 138, 4 }, }, + { { 169, 130, 193, 187, 5 }, + { 54, 160, 250, 70, 3 }, + { 173, 216, 52, 25, 5 }, + { 198, 37, 240, 86, 12 }, }, + { { 216, 27, 73, 176, 8 }, + { 114, 10, 143, 130, 10 }, + { 16, 217, 45, 129, 11 }, + { 84, 31, 21, 4, 14 }, }, + { { 3, 88, 41, 248, 6 }, + { 80, 52, 198, 73, 14 }, + { 97, 249, 65, 172, 0 }, + { 121, 38, 50, 192, 10 }, }, + { { 7, 196, 9, 95, 12 }, + { 152, 36, 213, 109, 1 }, + { 63, 169, 2, 62, 0 }, + { 139, 106, 178, 65, 9 }, }, + { { 15, 226, 102, 23, 11 }, + { 175, 85, 153, 69, 5 }, + { 222, 134, 100, 127, 0 }, + { 170, 41, 154, 175, 5 }, }, + { { 20, 72, 54, 68, 1 }, + { 140, 77, 0, 24, 12 }, + { 130, 38, 193, 34, 8 }, + { 49, 128, 11, 35, 1 }, }, + { { 16, 173, 95, 251, 7 }, + { 53, 123, 214, 60, 11 }, + { 237, 255, 171, 80, 8 }, + { 211, 198, 189, 234, 12 }, }, + { { 18, 130, 149, 83, 15 }, + { 20, 153, 217, 29, 1 }, + { 252, 170, 148, 20, 8 }, + { 139, 137, 185, 146, 8 }, }, + { { 22, 225, 49, 132, 12 }, + { 153, 14, 81, 17, 6 }, + { 50, 24, 200, 118, 8 }, + { 104, 136, 167, 9, 9 }, }, + { { 24, 122, 73, 107, 0 }, + { 115, 44, 14, 12, 9 }, + { 13, 105, 37, 225, 8 }, + { 147, 7, 3, 76, 14 }, }, + { { 26, 232, 134, 17, 2 }, + { 3, 221, 144, 5, 8 }, + { 72, 134, 17, 117, 8 }, + { 26, 0, 155, 188, 0 }, }, + { { 25, 19, 174, 10, 1 }, + { 70, 235, 12, 64, 5 }, + { 133, 7, 92, 137, 8 }, + { 160, 35, 13, 118, 2 }, }, + { { 27, 103, 181, 161, 7 }, + { 23, 159, 74, 117, 6 }, + { 232, 90, 222, 109, 8 }, + { 106, 229, 47, 158, 8 }, }, + { { 37, 220, 149, 240, 11 }, + { 212, 149, 179, 120, 10 }, + { 208, 250, 147, 186, 4 }, + { 81, 236, 218, 146, 11 }, }, + { { 40, 137, 97, 247, 6 }, + { 58, 18, 242, 12, 15 }, + { 110, 248, 105, 17, 4 }, + { 243, 4, 244, 133, 12 }, }, + { { 51, 84, 20, 106, 10 }, + { 64, 61, 35, 121, 1 }, + { 85, 98, 130, 172, 12 }, + { 137, 236, 75, 192, 2 }, }, + { { 49, 193, 108, 31, 7 }, + { 44, 63, 244, 68, 5 }, + { 239, 131, 104, 56, 12 }, + { 162, 34, 255, 195, 4 }, }, + { { 51, 203, 24, 198, 6 }, + { 8, 30, 124, 89, 11 }, + { 102, 49, 141, 60, 12 }, + { 217, 163, 231, 129, 0 }, }, + { { 62, 207, 228, 144, 15 }, + { 166, 159, 249, 33, 14 }, + { 240, 146, 127, 55, 12 }, + { 120, 73, 255, 150, 5 }, }, + { { 70, 69, 24, 163, 15 }, + { 132, 22, 71, 181, 3 }, + { 252, 81, 138, 38, 2 }, + { 202, 222, 38, 130, 1 }, }, + { { 68, 186, 112, 182, 7 }, + { 237, 16, 218, 144, 15 }, + { 230, 208, 229, 210, 2 }, + { 240, 149, 176, 139, 7 }, }, + { { 65, 156, 98, 62, 8 }, + { 104, 96, 147, 224, 13 }, + { 23, 196, 99, 152, 2 }, + { 176, 124, 144, 97, 6 }, }, + { { 72, 209, 145, 74, 1 }, + { 86, 166, 16, 152, 1 }, + { 133, 40, 152, 177, 2 }, + { 129, 144, 134, 86, 10 }, }, + { { 84, 244, 153, 246, 13 }, + { 221, 140, 215, 184, 3 }, + { 182, 249, 146, 242, 10 }, + { 193, 222, 179, 27, 11 }, }, + { { 87, 90, 156, 129, 3 }, + { 196, 157, 12, 213, 10 }, + { 200, 19, 149, 174, 10 }, + { 90, 179, 11, 146, 3 }, }, + { { 85, 131, 85, 178, 12 }, + { 176, 11, 219, 208, 3 }, + { 52, 218, 172, 26, 10 }, + { 192, 189, 189, 0, 13 }, }, + { { 87, 183, 118, 16, 15 }, + { 229, 91, 217, 241, 4 }, + { 240, 134, 238, 222, 10 }, + { 40, 249, 189, 170, 7 }, }, + { { 92, 52, 54, 254, 4 }, + { 203, 105, 194, 184, 7 }, + { 39, 246, 194, 195, 10 }, + { 225, 212, 57, 109, 3 }, }, + { { 92, 72, 252, 119, 14 }, + { 170, 157, 199, 156, 13 }, + { 126, 227, 241, 35, 10 }, + { 179, 158, 59, 149, 5 }, }, + { { 94, 110, 239, 64, 2 }, + { 179, 221, 12, 169, 12 }, + { 64, 47, 119, 103, 10 }, + { 57, 83, 11, 188, 13 }, }, + { { 95, 35, 59, 111, 15 }, + { 159, 122, 79, 221, 5 }, + { 255, 109, 204, 79, 10 }, + { 171, 191, 37, 239, 9 }, }, + { { 91, 116, 42, 99, 2 }, + { 67, 92, 6, 237, 5 }, + { 76, 101, 66, 237, 10 }, + { 171, 118, 3, 172, 2 }, }, + { { 101, 15, 163, 58, 14 }, + { 144, 242, 235, 224, 13 }, + { 117, 204, 95, 10, 6 }, + { 176, 125, 116, 240, 9 }, }, + { { 101, 211, 23, 92, 12 }, + { 216, 103, 249, 216, 0 }, + { 51, 174, 140, 186, 6 }, + { 1, 185, 254, 97, 11 }, }, + { { 106, 156, 36, 90, 14 }, + { 66, 49, 241, 169, 13 }, + { 117, 162, 67, 149, 6 }, + { 185, 88, 248, 196, 2 }, }, + { { 105, 197, 243, 4, 2 }, + { 58, 214, 48, 240, 4 }, + { 66, 12, 250, 57, 6 }, + { 32, 240, 198, 181, 12 }, }, + { { 105, 210, 72, 78, 10 }, + { 106, 52, 61, 200, 1 }, + { 87, 33, 36, 185, 6 }, + { 129, 59, 194, 197, 6 }, }, + { { 116, 121, 226, 222, 6 }, + { 233, 254, 224, 136, 15 }, + { 103, 180, 121, 226, 14 }, + { 241, 16, 119, 249, 7 }, }, + { { 114, 207, 35, 234, 11 }, + { 20, 126, 59, 169, 15 }, + { 213, 124, 79, 52, 14 }, + { 249, 93, 199, 226, 8 }, }, + { { 119, 177, 220, 65, 4 }, + { 225, 139, 116, 221, 0 }, + { 40, 35, 184, 222, 14 }, + { 11, 178, 237, 24, 7 }, }, + { { 126, 12, 7, 33, 7 }, + { 150, 89, 98, 165, 8 }, + { 232, 78, 3, 7, 14 }, + { 26, 84, 105, 166, 9 }, }, + { { 122, 105, 112, 100, 7 }, + { 47, 30, 98, 153, 12 }, + { 226, 96, 233, 101, 14 }, + { 57, 148, 103, 143, 4 }, }, + { { 120, 178, 216, 112, 7 }, + { 103, 152, 254, 152, 0 }, + { 224, 225, 180, 209, 14 }, + { 1, 151, 241, 158, 6 }, }, + { { 121, 197, 133, 121, 4 }, + { 18, 175, 242, 236, 0 }, + { 41, 234, 26, 57, 14 }, + { 3, 116, 255, 84, 8 }, }, + { { 134, 111, 89, 252, 6 }, + { 185, 54, 206, 59, 10 }, + { 99, 249, 175, 102, 1 }, + { 93, 199, 54, 201, 13 }, }, + { { 130, 246, 114, 127, 5 }, + { 109, 100, 218, 63, 5 }, + { 175, 228, 230, 244, 1 }, + { 175, 197, 178, 107, 6 }, }, + { { 133, 78, 47, 65, 4 }, + { 144, 69, 76, 110, 12 }, + { 40, 47, 71, 42, 1 }, + { 55, 99, 42, 32, 9 }, }, + { { 154, 17, 133, 147, 4 }, + { 82, 139, 192, 7, 3 }, + { 44, 154, 24, 133, 9 }, + { 206, 0, 61, 20, 10 }, }, + { { 156, 113, 96, 201, 7 }, + { 231, 62, 64, 14, 6 }, + { 233, 48, 104, 227, 9 }, + { 103, 0, 39, 206, 7 }, }, + { { 157, 209, 148, 253, 8 }, + { 202, 175, 147, 94, 2 }, + { 27, 242, 152, 187, 9 }, + { 71, 172, 159, 85, 3 }, }, + { { 162, 30, 18, 227, 8 }, + { 64, 64, 43, 63, 11 }, + { 28, 116, 135, 132, 5 }, + { 223, 205, 64, 32, 2 }, }, + { { 174, 112, 28, 130, 12 }, + { 195, 5, 101, 19, 3 }, + { 52, 19, 128, 231, 5 }, + { 204, 138, 106, 12, 3 }, }, + { { 173, 1, 33, 156, 1 }, + { 158, 34, 160, 66, 6 }, + { 131, 152, 72, 11, 5 }, + { 100, 32, 84, 71, 9 }, }, + { { 176, 53, 31, 158, 14 }, + { 89, 123, 229, 50, 3 }, + { 119, 159, 138, 192, 13 }, + { 196, 202, 125, 233, 10 }, }, + { { 182, 74, 216, 13, 4 }, + { 168, 172, 108, 23, 8 }, + { 43, 1, 181, 38, 13 }, + { 30, 131, 99, 81, 5 }, }, + { { 181, 55, 49, 75, 4 }, + { 209, 42, 104, 126, 5 }, + { 45, 40, 206, 202, 13 }, + { 167, 225, 101, 72, 11 }, }, + { { 190, 170, 199, 227, 11 }, + { 183, 217, 59, 15, 11 }, + { 220, 126, 53, 87, 13 }, + { 223, 13, 201, 190, 13 }, }, + { { 187, 104, 61, 188, 15 }, + { 31, 61, 231, 83, 14 }, + { 243, 219, 193, 109, 13 }, + { 124, 174, 123, 207, 8 }, }, + { { 198, 114, 247, 44, 1 }, + { 253, 229, 10, 147, 4 }, + { 131, 78, 244, 230, 3 }, + { 44, 149, 10, 123, 15 }, }, + { { 193, 231, 77, 186, 11 }, + { 53, 55, 159, 226, 3 }, + { 213, 219, 46, 120, 3 }, + { 196, 127, 158, 202, 12 }, }, + { { 203, 85, 238, 89, 13 }, + { 102, 231, 197, 239, 4 }, + { 185, 167, 122, 173, 3 }, + { 47, 122, 62, 118, 6 }, }, + { { 203, 160, 83, 114, 4 }, + { 51, 64, 210, 219, 1 }, + { 36, 236, 160, 93, 3 }, + { 141, 180, 176, 44, 12 }, }, + { { 208, 9, 15, 207, 1 }, + { 28, 107, 4, 142, 11 }, + { 143, 63, 9, 0, 11 }, + { 215, 18, 13, 99, 8 }, }, + { { 208, 108, 58, 213, 4 }, + { 9, 76, 196, 190, 14 }, + { 42, 181, 195, 96, 11 }, + { 119, 210, 51, 41, 0 }, }, + { { 211, 241, 32, 87, 4 }, + { 73, 14, 208, 207, 5 }, + { 46, 160, 72, 252, 11 }, + { 175, 48, 183, 9, 2 }, }, + { { 230, 227, 59, 26, 7 }, + { 149, 118, 252, 147, 5 }, + { 229, 141, 204, 118, 7 }, + { 172, 147, 246, 234, 9 }, }, + { { 227, 83, 62, 164, 10 }, + { 72, 87, 47, 211, 6 }, + { 82, 87, 204, 172, 7 }, + { 108, 191, 78, 161, 2 }, }, + { { 232, 6, 142, 177, 4 }, + { 2, 193, 238, 166, 2 }, + { 40, 215, 22, 1, 7 }, + { 70, 87, 120, 52, 0 }, }, + { { 236, 7, 192, 89, 7 }, + { 166, 178, 232, 174, 0 }, + { 233, 160, 62, 3, 7 }, + { 7, 81, 116, 214, 5 }, }, + { { 234, 243, 128, 61, 10 }, + { 75, 182, 187, 135, 0 }, + { 91, 192, 28, 245, 7 }, + { 14, 29, 214, 221, 2 }, }, + { { 246, 59, 39, 216, 8 }, + { 209, 107, 169, 139, 14 }, + { 17, 190, 77, 198, 15 }, + { 125, 25, 93, 104, 11 }, }, + { { 243, 7, 152, 55, 9 }, + { 12, 138, 175, 247, 1 }, + { 158, 193, 158, 12, 15 }, + { 142, 255, 85, 19, 0 }, }, + { { 254, 75, 186, 155, 9 }, + { 134, 238, 173, 151, 15 }, + { 157, 149, 221, 39, 15 }, + { 254, 155, 87, 118, 1 }, }, + { { 171, 165, 125, 134, 11 }, + { 63, 19, 53, 115, 7 }, + { 214, 27, 234, 93, 5 }, + { 236, 234, 204, 143, 12 }, }, + { { 192, 209, 98, 90, 11 }, + { 100, 118, 145, 138, 5 }, + { 213, 164, 104, 176, 3 }, + { 165, 24, 150, 226, 6 }, }, + { { 19, 206, 123, 174, 7 }, + { 60, 124, 94, 113, 15 }, + { 231, 93, 231, 60, 8 }, + { 248, 231, 163, 227, 12 }, }, + { { 78, 129, 253, 97, 7 }, + { 182, 147, 86, 157, 4 }, + { 232, 107, 248, 23, 2 }, + { 43, 150, 172, 150, 13 }, }, + { { 86, 224, 118, 50, 0 }, + { 161, 77, 146, 145, 5 }, + { 4, 198, 224, 118, 10 }, + { 168, 148, 155, 40, 5 }, }, + { { 106, 112, 138, 84, 0 }, + { 75, 196, 164, 137, 0 }, + { 2, 165, 16, 229, 6 }, + { 9, 18, 82, 61, 2 }, }, + { { 114, 168, 152, 161, 8 }, + { 1, 136, 55, 149, 10 }, + { 24, 81, 145, 84, 14 }, + { 90, 158, 193, 24, 0 }, }, + { { 129, 93, 66, 248, 0 }, + { 96, 102, 130, 106, 10 }, + { 1, 244, 43, 168, 1 }, + { 85, 100, 22, 96, 6 }, }, + { { 207, 76, 195, 213, 15 }, + { 190, 212, 193, 239, 10 }, + { 250, 188, 51, 47, 3 }, + { 95, 120, 50, 183, 13 }, }, + { { 214, 187, 101, 134, 4 }, + { 249, 11, 88, 131, 15 }, + { 38, 26, 109, 214, 11 }, + { 252, 17, 173, 9, 15 }, }, + { { 236, 211, 19, 163, 1 }, + { 214, 70, 58, 150, 3 }, + { 140, 92, 140, 179, 7 }, + { 198, 149, 198, 38, 11 }, }, + { { 245, 33, 245, 32, 7 }, + { 181, 155, 98, 210, 4 }, + { 224, 74, 248, 74, 15 }, + { 36, 180, 109, 154, 13 }, }, + { { 249, 31, 165, 223, 7 }, + { 94, 187, 232, 238, 15 }, + { 239, 186, 95, 137, 15 }, + { 247, 113, 125, 215, 10 }, }, + { { 0, 36, 244, 122, 7 }, + { 37, 177, 194, 56, 5 }, + { 229, 226, 242, 64, 0 }, + { 161, 196, 56, 218, 4 }, }, + { { 0, 8, 77, 136, 2 }, + { 48, 49, 4, 0, 10 }, + { 65, 27, 33, 0, 0 }, + { 80, 2, 8, 192, 12 }, }, + { { 4, 60, 194, 242, 9 }, + { 229, 192, 131, 40, 11 }, + { 148, 244, 51, 194, 0 }, + { 209, 76, 16, 58, 7 }, }, + { { 4, 123, 80, 33, 1 }, + { 229, 6, 10, 20, 8 }, + { 136, 64, 173, 226, 0 }, + { 18, 133, 6, 10, 7 }, }, + { { 6, 122, 228, 193, 13 }, + { 229, 133, 73, 13, 14 }, + { 184, 50, 117, 230, 0 }, + { 123, 9, 42, 26, 7 }, }, + { { 0, 170, 150, 138, 3 }, + { 5, 241, 24, 16, 11 }, + { 197, 22, 149, 80, 0 }, + { 208, 129, 136, 250, 0 }, }, + { { 4, 209, 56, 233, 4 }, + { 192, 38, 86, 28, 6 }, + { 41, 113, 200, 178, 0 }, + { 99, 134, 166, 64, 3 }, }, + { { 5, 16, 168, 13, 10 }, + { 200, 176, 5, 68, 4 }, + { 91, 1, 80, 138, 0 }, + { 34, 42, 0, 209, 3 }, }, + { { 1, 64, 176, 0, 7 }, + { 4, 148, 64, 80, 4 }, + { 224, 0, 208, 40, 0 }, + { 32, 160, 34, 146, 0 }, }, + { { 1, 157, 156, 238, 1 }, + { 76, 163, 22, 120, 11 }, + { 135, 115, 155, 152, 0 }, + { 209, 230, 140, 83, 2 }, }, + { { 8, 16, 87, 227, 11 }, + { 118, 81, 3, 28, 3 }, + { 220, 126, 160, 129, 0 }, + { 195, 140, 8, 166, 14 }, }, + { { 8, 107, 151, 182, 6 }, + { 27, 215, 202, 16, 11 }, + { 102, 222, 157, 97, 0 }, + { 208, 133, 62, 189, 8 }, }, + { { 14, 232, 184, 96, 10 }, + { 131, 148, 23, 25, 12 }, + { 80, 97, 209, 119, 0 }, + { 57, 142, 130, 156, 1 }, }, + { { 11, 108, 118, 185, 11 }, + { 39, 117, 131, 117, 14 }, + { 217, 214, 227, 109, 0 }, + { 122, 236, 26, 238, 4 }, }, + { { 15, 220, 185, 140, 11 }, + { 222, 180, 21, 113, 14 }, + { 211, 25, 211, 191, 0 }, + { 120, 234, 130, 215, 11 }, }, + { { 15, 202, 207, 58, 0 }, + { 178, 229, 158, 65, 9 }, + { 5, 207, 53, 63, 0 }, + { 152, 39, 154, 116, 13 }, }, + { { 20, 36, 159, 217, 8 }, + { 145, 233, 133, 60, 2 }, + { 25, 191, 146, 66, 8 }, + { 67, 202, 25, 120, 9 }, }, + { { 20, 7, 32, 31, 13 }, + { 140, 42, 201, 36, 5 }, + { 191, 128, 78, 2, 8 }, + { 162, 73, 53, 67, 1 }, }, + { { 21, 9, 16, 213, 7 }, + { 140, 26, 192, 92, 10 }, + { 234, 176, 137, 10, 8 }, + { 83, 160, 53, 131, 1 }, }, + { { 19, 92, 215, 48, 7 }, + { 116, 221, 194, 113, 8 }, + { 224, 206, 179, 172, 8 }, + { 24, 228, 59, 178, 14 }, }, + { { 17, 71, 154, 187, 6 }, + { 0, 254, 206, 116, 3 }, + { 109, 213, 158, 40, 8 }, + { 194, 231, 55, 240, 0 }, }, + { { 28, 185, 169, 35, 8 }, + { 211, 138, 23, 4, 13 }, + { 28, 73, 89, 211, 8 }, + { 178, 14, 133, 28, 11 }, }, + { { 28, 221, 7, 118, 6 }, + { 218, 95, 210, 40, 9 }, + { 102, 238, 11, 179, 8 }, + { 145, 68, 191, 165, 11 }, }, + { { 31, 46, 124, 36, 11 }, + { 175, 25, 15, 113, 12 }, + { 210, 67, 231, 79, 8 }, + { 56, 239, 9, 143, 5 }, }, + { { 25, 102, 66, 71, 7 }, + { 47, 92, 72, 108, 1 }, + { 238, 36, 38, 105, 8 }, + { 131, 97, 35, 175, 4 }, }, + { { 25, 87, 212, 200, 4 }, + { 98, 175, 72, 120, 2 }, + { 33, 50, 190, 169, 8 }, + { 65, 225, 47, 84, 6 }, }, + { { 31, 168, 244, 240, 4 }, + { 163, 137, 210, 89, 14 }, + { 32, 242, 241, 95, 8 }, + { 121, 164, 185, 28, 5 }, }, + { { 27, 130, 70, 237, 8 }, + { 42, 105, 27, 77, 2 }, + { 27, 118, 36, 29, 8 }, + { 75, 45, 137, 101, 4 }, }, + { { 27, 174, 225, 15, 14 }, + { 59, 184, 89, 101, 13 }, + { 127, 8, 119, 93, 8 }, + { 186, 105, 161, 221, 12 }, }, + { { 34, 164, 182, 60, 10 }, + { 9, 241, 179, 49, 4 }, + { 83, 198, 210, 84, 4 }, + { 40, 204, 216, 249, 0 }, }, + { { 34, 191, 144, 18, 15 }, + { 69, 146, 249, 49, 9 }, + { 244, 128, 159, 212, 4 }, + { 152, 201, 244, 154, 2 }, }, + { { 35, 44, 21, 180, 0 }, + { 25, 1, 162, 113, 10 }, + { 2, 218, 131, 76, 4 }, + { 88, 228, 88, 9, 8 }, }, + { { 37, 90, 169, 102, 12 }, + { 216, 132, 111, 72, 13 }, + { 54, 105, 85, 170, 4 }, + { 177, 47, 98, 17, 11 }, }, + { { 39, 165, 175, 169, 7 }, + { 149, 243, 118, 101, 6 }, + { 233, 95, 90, 94, 4 }, + { 106, 102, 236, 250, 9 }, }, + { { 37, 244, 14, 66, 5 }, + { 197, 69, 116, 104, 1 }, + { 164, 39, 2, 250, 4 }, + { 129, 98, 234, 42, 3 }, }, + { { 40, 102, 85, 205, 14 }, + { 59, 53, 105, 60, 2 }, + { 123, 58, 166, 97, 4 }, + { 67, 201, 106, 205, 12 }, }, + { { 44, 66, 126, 14, 0 }, + { 170, 101, 44, 16, 5 }, + { 7, 7, 228, 35, 4 }, + { 160, 131, 74, 101, 5 }, }, + { { 42, 185, 124, 189, 0 }, + { 107, 35, 182, 21, 14 }, + { 11, 211, 233, 213, 4 }, + { 122, 134, 220, 77, 6 }, }, + { { 41, 70, 225, 210, 3 }, + { 54, 148, 168, 104, 7 }, + { 196, 184, 118, 41, 4 }, + { 225, 97, 82, 150, 12 }, }, + { { 45, 166, 40, 65, 0 }, + { 131, 0, 60, 108, 4 }, + { 8, 33, 70, 91, 4 }, + { 35, 99, 192, 12, 1 }, }, + { { 43, 251, 32, 154, 6 }, + { 67, 54, 248, 65, 15 }, + { 101, 144, 77, 253, 4 }, + { 248, 33, 246, 204, 2 }, }, + { { 54, 140, 214, 107, 12 }, + { 160, 233, 115, 61, 9 }, + { 61, 102, 179, 22, 12 }, + { 155, 204, 233, 112, 5 }, }, + { { 52, 135, 119, 124, 7 }, + { 188, 123, 250, 56, 4 }, + { 227, 238, 238, 18, 12 }, + { 33, 197, 253, 227, 13 }, }, + { { 52, 221, 235, 132, 0 }, + { 248, 206, 52, 32, 14 }, + { 2, 29, 123, 178, 12 }, + { 112, 66, 199, 49, 15 }, }, + { { 55, 145, 247, 111, 1 }, + { 252, 235, 50, 93, 5 }, + { 143, 110, 248, 158, 12 }, + { 171, 164, 205, 115, 15 }, }, + { { 58, 34, 142, 23, 5 }, + { 15, 201, 236, 5, 1 }, + { 174, 135, 20, 69, 12 }, + { 138, 3, 121, 63, 0 }, }, + { { 62, 19, 189, 64, 8 }, + { 210, 139, 45, 25, 4 }, + { 16, 43, 220, 135, 12 }, + { 41, 139, 77, 20, 11 }, }, + { { 60, 152, 67, 202, 2 }, + { 242, 120, 48, 8, 11 }, + { 69, 60, 33, 147, 12 }, + { 209, 0, 193, 228, 15 }, }, + { { 57, 88, 157, 23, 9 }, + { 94, 141, 165, 84, 9 }, + { 158, 139, 145, 169, 12 }, + { 146, 170, 91, 23, 10 }, }, + { { 57, 116, 218, 238, 11 }, + { 111, 252, 39, 120, 3 }, + { 215, 117, 178, 233, 12 }, + { 193, 238, 67, 255, 6 }, }, + { { 63, 109, 188, 115, 1 }, + { 135, 143, 166, 125, 13 }, + { 140, 227, 219, 111, 12 }, + { 187, 230, 95, 30, 1 }, }, + { { 61, 107, 192, 80, 12 }, + { 163, 142, 233, 72, 8 }, + { 48, 160, 61, 107, 12 }, + { 17, 41, 119, 28, 5 }, }, + { { 57, 171, 39, 73, 7 }, + { 23, 123, 120, 76, 12 }, + { 233, 46, 77, 89, 12 }, + { 51, 33, 237, 238, 8 }, }, + { { 70, 2, 78, 37, 14 }, + { 168, 81, 79, 133, 0 }, + { 122, 71, 36, 6, 2 }, + { 10, 31, 40, 161, 5 }, }, + { { 70, 130, 186, 11, 12 }, + { 128, 224, 93, 149, 5 }, + { 61, 5, 212, 22, 2 }, + { 170, 155, 160, 112, 1 }, }, + { { 66, 233, 205, 90, 14 }, + { 49, 183, 213, 137, 9 }, + { 117, 171, 57, 116, 2 }, + { 153, 26, 190, 216, 12 }, }, + { { 68, 201, 183, 179, 15 }, + { 148, 215, 211, 148, 15 }, + { 252, 222, 217, 50, 2 }, + { 242, 156, 190, 178, 9 }, }, + { { 64, 199, 212, 30, 9 }, + { 44, 167, 153, 176, 1 }, + { 151, 130, 190, 48, 2 }, + { 128, 217, 158, 83, 4 }, }, + { { 70, 210, 180, 204, 14 }, + { 200, 181, 89, 153, 6 }, + { 115, 50, 212, 182, 2 }, + { 105, 153, 170, 209, 3 }, }, + { { 67, 25, 83, 86, 11 }, + { 124, 82, 129, 217, 9 }, + { 214, 172, 169, 140, 2 }, + { 153, 184, 20, 163, 14 }, }, + { { 65, 34, 230, 221, 9 }, + { 45, 225, 137, 204, 6 }, + { 155, 182, 116, 72, 2 }, + { 99, 57, 24, 123, 4 }, }, + { { 71, 83, 165, 154, 11 }, + { 212, 183, 137, 193, 7 }, + { 213, 154, 92, 174, 2 }, + { 232, 57, 30, 210, 11 }, }, + { { 78, 30, 241, 224, 8 }, + { 242, 128, 11, 185, 14 }, + { 16, 120, 247, 135, 2 }, + { 121, 221, 0, 20, 15 }, }, + { { 78, 74, 192, 150, 0 }, + { 170, 132, 136, 129, 11 }, + { 6, 144, 53, 39, 2 }, + { 216, 17, 18, 21, 5 }, }, + { { 78, 95, 170, 6, 15 }, + { 206, 214, 77, 161, 13 }, + { 246, 5, 95, 167, 2 }, + { 184, 91, 38, 183, 3 }, }, + { { 74, 141, 50, 148, 3 }, + { 14, 82, 144, 177, 14 }, + { 194, 148, 203, 21, 2 }, + { 120, 208, 148, 167, 0 }, }, + { { 73, 21, 148, 179, 9 }, + { 70, 131, 131, 244, 3 }, + { 156, 210, 154, 137, 2 }, + { 194, 252, 28, 22, 2 }, }, + { { 77, 77, 219, 98, 1 }, + { 182, 198, 6, 248, 9 }, + { 132, 109, 187, 43, 2 }, + { 145, 246, 6, 54, 13 }, }, + { { 75, 167, 97, 232, 1 }, + { 55, 34, 26, 233, 6 }, + { 129, 120, 110, 93, 2 }, + { 105, 117, 132, 78, 12 }, }, + { { 73, 212, 131, 216, 14 }, + { 82, 244, 209, 232, 2 }, + { 113, 188, 18, 185, 2 }, + { 65, 120, 178, 244, 10 }, }, + { { 86, 41, 14, 246, 12 }, + { 137, 75, 199, 137, 11 }, + { 54, 247, 9, 70, 10 }, + { 217, 30, 61, 41, 1 }, }, + { { 83, 126, 213, 255, 12 }, + { 121, 173, 203, 253, 11 }, + { 63, 250, 183, 236, 10 }, + { 219, 253, 59, 89, 14 }, }, + { { 85, 245, 167, 175, 10 }, + { 217, 255, 19, 228, 7 }, + { 95, 94, 90, 250, 10 }, + { 226, 124, 143, 249, 11 }, }, + { { 85, 213, 234, 100, 15 }, + { 236, 222, 87, 232, 4 }, + { 242, 101, 122, 186, 10 }, + { 33, 126, 167, 179, 7 }, }, + { { 88, 27, 171, 29, 10 }, + { 90, 250, 141, 132, 12 }, + { 91, 141, 93, 129, 10 }, + { 50, 27, 21, 245, 10 }, }, + { { 94, 190, 146, 109, 13 }, + { 207, 232, 91, 189, 8 }, + { 187, 100, 151, 215, 10 }, + { 27, 221, 161, 127, 3 }, }, + { { 95, 16, 249, 155, 5 }, + { 246, 168, 196, 213, 7 }, + { 173, 153, 240, 143, 10 }, + { 234, 178, 49, 86, 15 }, }, + { { 93, 30, 223, 165, 12 }, + { 250, 201, 79, 244, 10 }, + { 58, 95, 183, 139, 10 }, + { 82, 255, 41, 53, 15 }, }, + { { 95, 113, 141, 240, 2 }, + { 211, 159, 134, 201, 2 }, + { 64, 251, 24, 239, 10 }, + { 73, 54, 31, 156, 11 }, }, + { { 93, 225, 30, 70, 8 }, + { 139, 79, 21, 216, 1 }, + { 22, 39, 136, 123, 10 }, + { 129, 186, 143, 45, 1 }, }, + { { 96, 51, 187, 36, 7 }, + { 93, 210, 110, 144, 4 }, + { 226, 77, 220, 192, 6 }, + { 32, 151, 100, 187, 10 }, }, + { { 100, 88, 26, 254, 1 }, + { 204, 100, 166, 152, 11 }, + { 135, 245, 129, 162, 6 }, + { 209, 150, 82, 99, 3 }, }, + { { 99, 200, 221, 167, 6 }, + { 56, 149, 118, 213, 11 }, + { 110, 91, 177, 60, 6 }, + { 218, 182, 234, 145, 12 }, }, + { { 97, 218, 61, 143, 13 }, + { 92, 37, 125, 212, 15 }, + { 191, 27, 197, 184, 6 }, + { 242, 187, 234, 67, 10 }, }, + { { 110, 58, 34, 175, 10 }, + { 203, 112, 43, 133, 15 }, + { 95, 84, 69, 199, 6 }, + { 250, 29, 64, 237, 3 }, }, + { { 110, 97, 5, 183, 1 }, + { 159, 7, 162, 133, 3 }, + { 142, 218, 8, 103, 6 }, + { 202, 20, 94, 15, 9 }, }, + { { 106, 137, 169, 232, 12 }, + { 18, 162, 119, 137, 14 }, + { 49, 121, 89, 21, 6 }, + { 121, 30, 228, 84, 8 }, }, + { { 106, 151, 34, 79, 5 }, + { 78, 98, 120, 173, 5 }, + { 175, 36, 78, 149, 6 }, + { 171, 81, 228, 103, 2 }, }, + { { 107, 18, 195, 128, 1 }, + { 118, 192, 40, 193, 2 }, + { 128, 28, 52, 141, 6 }, + { 72, 49, 64, 54, 14 }, }, + { { 107, 104, 75, 34, 10 }, + { 51, 84, 39, 193, 9 }, + { 84, 77, 33, 109, 6 }, + { 152, 62, 66, 172, 12 }, }, + { { 111, 148, 193, 87, 9 }, + { 254, 128, 177, 237, 1 }, + { 158, 168, 50, 159, 6 }, + { 139, 120, 208, 23, 15 }, }, + { { 109, 166, 254, 160, 13 }, + { 167, 193, 127, 240, 6 }, + { 176, 87, 246, 91, 6 }, + { 96, 255, 232, 62, 5 }, }, + { { 111, 234, 202, 69, 7 }, + { 175, 212, 124, 205, 8 }, + { 234, 37, 53, 127, 6 }, + { 27, 51, 226, 191, 5 }, }, + { { 112, 61, 56, 166, 0 }, + { 73, 10, 38, 176, 15 }, + { 6, 81, 203, 192, 14 }, + { 240, 214, 69, 9, 2 }, }, + { { 118, 108, 53, 231, 8 }, + { 153, 13, 35, 189, 15 }, + { 30, 122, 195, 102, 14 }, + { 251, 220, 75, 9, 9 }, }, + { { 112, 74, 13, 255, 6 }, + { 24, 61, 238, 140, 11 }, + { 111, 251, 5, 32, 14 }, + { 211, 23, 123, 193, 8 }, }, + { { 117, 120, 169, 200, 0 }, + { 209, 172, 36, 200, 14 }, + { 1, 57, 81, 234, 14 }, + { 113, 50, 67, 88, 11 }, }, + { { 113, 74, 112, 19, 8 }, + { 32, 12, 169, 212, 13 }, + { 28, 128, 229, 40, 14 }, + { 178, 185, 83, 0, 4 }, }, + { { 117, 127, 140, 187, 9 }, + { 197, 175, 175, 228, 11 }, + { 157, 211, 31, 234, 14 }, + { 210, 127, 95, 90, 3 }, }, + { { 124, 35, 104, 51, 1 }, + { 167, 10, 174, 132, 5 }, + { 140, 193, 108, 67, 14 }, + { 162, 23, 85, 14, 5 }, }, + { { 124, 181, 167, 211, 1 }, + { 215, 203, 176, 172, 7 }, + { 140, 190, 90, 211, 14 }, + { 227, 80, 221, 62, 11 }, }, + { { 124, 248, 44, 237, 14 }, + { 203, 61, 119, 140, 14 }, + { 123, 115, 65, 243, 14 }, + { 115, 30, 235, 205, 3 }, }, + { { 127, 36, 226, 52, 15 }, + { 175, 216, 227, 225, 4 }, + { 242, 196, 114, 79, 14 }, + { 40, 124, 113, 191, 5 }, }, + { { 127, 71, 41, 141, 8 }, + { 154, 46, 45, 229, 6 }, + { 27, 25, 78, 47, 14 }, + { 106, 123, 71, 69, 9 }, }, + { { 134, 216, 3, 209, 9 }, + { 212, 68, 145, 15, 10 }, + { 152, 188, 1, 182, 1 }, + { 95, 8, 146, 34, 11 }, }, + { { 131, 139, 27, 161, 3 }, + { 20, 82, 30, 87, 10 }, + { 200, 93, 141, 28, 1 }, + { 94, 167, 132, 162, 8 }, }, + { { 135, 162, 121, 197, 9 }, + { 189, 0, 29, 95, 6 }, + { 154, 57, 228, 94, 1 }, + { 111, 171, 128, 11, 13 }, }, + { { 138, 67, 100, 140, 14 }, + { 42, 55, 73, 3, 6 }, + { 115, 18, 108, 37, 1 }, + { 108, 9, 46, 197, 4 }, }, + { { 136, 147, 59, 76, 8 }, + { 90, 98, 29, 26, 4 }, + { 19, 45, 204, 145, 1 }, + { 37, 139, 132, 101, 10 }, }, + { { 143, 33, 223, 78, 3 }, + { 191, 243, 4, 91, 1 }, + { 199, 47, 184, 79, 1 }, + { 141, 162, 12, 255, 13 }, }, + { { 141, 132, 53, 114, 9 }, + { 150, 1, 147, 122, 5 }, + { 148, 234, 194, 27, 1 }, + { 165, 236, 152, 6, 9 }, }, + { { 141, 136, 215, 31, 13 }, + { 190, 225, 209, 86, 9 }, + { 191, 142, 177, 27, 1 }, + { 150, 168, 184, 119, 13 }, }, + { { 137, 159, 120, 252, 13 }, + { 110, 34, 223, 122, 14 }, + { 179, 241, 239, 153, 1 }, + { 117, 239, 180, 71, 6 }, }, + { { 146, 107, 22, 121, 12 }, + { 1, 111, 203, 31, 8 }, + { 57, 230, 141, 100, 9 }, + { 31, 141, 63, 104, 0 }, }, + { { 148, 142, 34, 241, 2 }, + { 128, 88, 154, 46, 14 }, + { 72, 244, 71, 18, 9 }, + { 119, 69, 145, 160, 1 }, }, + { { 144, 229, 230, 49, 7 }, + { 37, 223, 210, 38, 4 }, + { 232, 198, 122, 112, 9 }, + { 38, 68, 191, 186, 4 }, }, + { { 150, 216, 133, 42, 1 }, + { 212, 173, 18, 3, 9 }, + { 133, 74, 17, 182, 9 }, + { 156, 4, 139, 82, 11 }, }, + { { 149, 57, 59, 164, 6 }, + { 217, 90, 70, 82, 14 }, + { 98, 93, 201, 202, 9 }, + { 116, 166, 37, 169, 11 }, }, + { { 149, 60, 251, 77, 13 }, + { 253, 232, 69, 126, 12 }, + { 187, 45, 243, 202, 9 }, + { 55, 234, 33, 123, 15 }, }, + { { 145, 62, 170, 18, 6 }, + { 65, 216, 204, 98, 13 }, + { 100, 133, 87, 200, 9 }, + { 180, 99, 49, 184, 2 }, }, + { { 151, 111, 90, 175, 9 }, + { 173, 110, 15, 119, 11 }, + { 159, 85, 175, 110, 9 }, + { 222, 239, 7, 107, 5 }, }, + { { 145, 178, 41, 253, 10 }, + { 89, 56, 159, 78, 6 }, + { 91, 249, 68, 216, 9 }, + { 103, 47, 145, 201, 10 }, }, + { { 145, 211, 250, 118, 1 }, + { 108, 206, 158, 90, 5 }, + { 134, 229, 252, 184, 9 }, + { 165, 167, 151, 51, 6 }, }, + { { 154, 112, 134, 200, 8 }, + { 67, 237, 1, 11, 2 }, + { 17, 54, 16, 229, 9 }, + { 77, 8, 11, 124, 2 }, }, + { { 152, 142, 205, 3, 1 }, + { 54, 137, 28, 38, 9 }, + { 140, 11, 55, 17, 9 }, + { 150, 67, 137, 22, 12 }, }, + { { 152, 199, 16, 151, 10 }, + { 10, 30, 153, 54, 3 }, + { 94, 144, 142, 49, 9 }, + { 198, 201, 151, 133, 0 }, }, + { { 157, 203, 235, 70, 6 }, + { 186, 222, 92, 74, 13 }, + { 102, 45, 125, 59, 9 }, + { 181, 35, 167, 181, 13 }, }, + { { 164, 40, 245, 182, 14 }, + { 185, 145, 227, 18, 15 }, + { 118, 218, 241, 66, 5 }, + { 244, 140, 120, 153, 13 }, }, + { { 163, 55, 241, 121, 3 }, + { 117, 178, 170, 127, 4 }, + { 201, 232, 254, 204, 5 }, + { 47, 229, 84, 218, 14 }, }, + { { 163, 68, 64, 245, 10 }, + { 40, 20, 163, 111, 2 }, + { 90, 240, 34, 44, 5 }, + { 79, 108, 82, 129, 4 }, }, + { { 161, 127, 173, 133, 8 }, + { 89, 135, 45, 102, 14 }, + { 26, 27, 95, 232, 5 }, + { 118, 107, 78, 25, 10 }, }, + { { 167, 210, 150, 35, 13 }, + { 196, 197, 123, 87, 1 }, + { 188, 70, 148, 190, 5 }, + { 142, 173, 234, 50, 3 }, }, + { { 168, 69, 112, 43, 11 }, + { 38, 54, 35, 54, 5 }, + { 221, 64, 234, 33, 5 }, + { 166, 204, 70, 198, 4 }, }, + { { 174, 72, 127, 160, 9 }, + { 182, 69, 39, 19, 14 }, + { 144, 95, 225, 39, 5 }, + { 124, 142, 74, 38, 13 }, }, + { { 172, 79, 182, 214, 8 }, + { 138, 199, 169, 58, 15 }, + { 22, 182, 223, 35, 5 }, + { 245, 201, 94, 53, 1 }, }, + { { 168, 168, 211, 133, 3 }, + { 63, 208, 48, 22, 10 }, + { 202, 28, 177, 81, 5 }, + { 86, 128, 192, 191, 12 }, }, + { { 169, 139, 10, 203, 8 }, + { 2, 98, 61, 78, 11 }, + { 29, 53, 13, 25, 5 }, + { 215, 43, 196, 100, 0 }, }, + { { 173, 254, 140, 222, 2 }, + { 203, 181, 188, 106, 11 }, + { 71, 179, 23, 251, 5 }, + { 213, 99, 218, 221, 3 }, }, + { { 180, 239, 46, 46, 14 }, + { 137, 127, 127, 34, 13 }, + { 119, 71, 79, 114, 13 }, + { 180, 79, 239, 233, 1 }, }, + { { 183, 153, 137, 199, 0 }, + { 216, 138, 52, 79, 11 }, + { 14, 57, 25, 158, 13 }, + { 223, 34, 197, 17, 11 }, }, + { { 190, 12, 162, 14, 12 }, + { 138, 232, 97, 35, 13 }, + { 55, 4, 83, 7, 13 }, + { 188, 72, 97, 117, 1 }, }, + { { 188, 112, 34, 122, 9 }, + { 199, 108, 163, 10, 5 }, + { 149, 228, 64, 227, 13 }, + { 165, 12, 83, 110, 3 }, }, + { { 190, 188, 47, 145, 10 }, + { 211, 89, 181, 39, 14 }, + { 88, 159, 67, 215, 13 }, + { 126, 74, 217, 172, 11 }, }, + { { 184, 233, 10, 152, 3 }, + { 7, 126, 180, 2, 10 }, + { 193, 149, 9, 113, 13 }, + { 84, 2, 215, 238, 0 }, }, + { { 189, 10, 48, 236, 8 }, + { 138, 40, 43, 90, 14 }, + { 19, 112, 197, 11, 13 }, + { 117, 173, 65, 69, 1 }, }, + { { 194, 2, 224, 243, 1 }, + { 36, 128, 138, 143, 7 }, + { 140, 240, 116, 4, 3 }, + { 239, 21, 16, 18, 4 }, }, + { { 194, 107, 50, 227, 7 }, + { 5, 86, 74, 159, 15 }, + { 236, 116, 205, 100, 3 }, + { 255, 149, 38, 170, 0 }, }, + { { 198, 202, 66, 106, 8 }, + { 160, 100, 27, 139, 9 }, + { 21, 100, 37, 54, 3 }, + { 157, 29, 130, 96, 5 }, }, + { { 199, 30, 238, 104, 14 }, + { 224, 241, 79, 235, 12 }, + { 113, 103, 119, 142, 3 }, + { 61, 127, 40, 240, 7 }, }, + { { 199, 125, 46, 145, 3 }, + { 197, 87, 132, 231, 14 }, + { 200, 151, 75, 238, 3 }, + { 126, 114, 30, 170, 3 }, }, + { { 206, 60, 32, 116, 2 }, + { 203, 16, 130, 171, 12 }, + { 66, 224, 67, 199, 3 }, + { 61, 84, 16, 141, 3 }, }, + { { 204, 74, 185, 197, 7 }, + { 158, 148, 76, 158, 14 }, + { 234, 57, 213, 35, 3 }, + { 119, 147, 34, 151, 9 }, }, + { { 206, 247, 99, 220, 13 }, + { 255, 102, 217, 171, 6 }, + { 179, 188, 110, 247, 3 }, + { 109, 89, 182, 111, 15 }, }, + { { 205, 67, 34, 202, 2 }, + { 130, 118, 8, 202, 7 }, + { 69, 52, 76, 43, 3 }, + { 229, 49, 6, 228, 1 }, }, + { { 207, 183, 204, 29, 0 }, + { 235, 163, 156, 231, 0 }, + { 11, 131, 62, 223, 3 }, + { 14, 115, 156, 93, 7 }, }, + { { 201, 206, 200, 53, 10 }, + { 42, 148, 159, 230, 8 }, + { 90, 193, 55, 57, 3 }, + { 22, 127, 146, 149, 4 }, }, + { { 207, 243, 75, 113, 3 }, + { 247, 86, 158, 207, 0 }, + { 200, 237, 44, 255, 3 }, + { 15, 55, 150, 174, 15 }, }, + { { 214, 46, 123, 112, 13 }, + { 181, 72, 207, 187, 12 }, + { 176, 237, 231, 70, 11 }, + { 61, 223, 49, 42, 13 }, }, + { { 212, 23, 75, 59, 4 }, + { 240, 106, 206, 166, 1 }, + { 45, 205, 46, 130, 11 }, + { 134, 87, 53, 96, 15 }, }, + { { 215, 141, 250, 151, 14 }, + { 168, 218, 213, 247, 15 }, + { 126, 149, 251, 30, 11 }, + { 254, 250, 181, 177, 5 }, }, + { { 209, 216, 245, 85, 1 }, + { 124, 141, 144, 222, 12 }, + { 138, 170, 241, 184, 11 }, + { 55, 176, 155, 19, 14 }, }, + { { 213, 207, 225, 211, 9 }, + { 180, 142, 153, 238, 15 }, + { 156, 184, 127, 58, 11 }, + { 247, 121, 151, 18, 13 }, }, + { { 218, 22, 168, 204, 9 }, + { 78, 168, 13, 171, 6 }, + { 147, 49, 86, 133, 11 }, + { 109, 91, 1, 87, 2 }, }, + { { 216, 76, 68, 133, 9 }, + { 46, 13, 1, 166, 10 }, + { 154, 18, 35, 33, 11 }, + { 86, 88, 11, 7, 4 }, }, + { { 220, 217, 114, 142, 13 }, + { 238, 110, 81, 146, 15 }, + { 183, 20, 233, 179, 11 }, + { 244, 152, 167, 103, 7 }, }, + { { 223, 103, 17, 126, 8 }, + { 155, 46, 139, 251, 1 }, + { 23, 232, 142, 111, 11 }, + { 141, 253, 23, 77, 9 }, }, + { { 219, 153, 125, 230, 7 }, + { 126, 27, 86, 219, 15 }, + { 230, 123, 233, 157, 11 }, + { 253, 182, 173, 135, 14 }, }, + { { 221, 171, 142, 49, 14 }, + { 131, 219, 223, 198, 8 }, + { 120, 199, 29, 91, 11 }, + { 22, 63, 189, 188, 1 }, }, + { { 224, 25, 8, 76, 13 }, + { 76, 34, 101, 138, 8 }, + { 179, 33, 9, 128, 7 }, + { 21, 26, 100, 67, 2 }, }, + { { 230, 54, 218, 82, 1 }, + { 229, 192, 172, 187, 1 }, + { 132, 165, 182, 198, 7 }, + { 141, 211, 80, 58, 7 }, }, + { { 226, 172, 199, 155, 0 }, + { 49, 225, 176, 167, 11 }, + { 13, 158, 51, 84, 7 }, + { 222, 80, 216, 120, 12 }, }, + { { 228, 141, 33, 98, 0 }, + { 144, 2, 50, 170, 13 }, + { 4, 104, 75, 18, 7 }, + { 181, 84, 196, 0, 9 }, }, + { { 226, 254, 208, 197, 1 }, + { 109, 132, 56, 191, 10 }, + { 138, 48, 183, 244, 7 }, + { 95, 209, 194, 27, 6 }, }, + { { 225, 58, 125, 2, 10 }, + { 113, 17, 45, 210, 13 }, + { 84, 11, 229, 200, 7 }, + { 180, 187, 72, 136, 14 }, }, + { { 231, 208, 91, 142, 5 }, + { 252, 100, 116, 211, 3 }, + { 167, 29, 160, 190, 7 }, + { 204, 178, 226, 99, 15 }, }, + { { 236, 48, 156, 107, 5 }, + { 199, 161, 102, 158, 1 }, + { 173, 99, 144, 195, 7 }, + { 135, 150, 104, 94, 3 }, }, + { { 236, 170, 73, 210, 0 }, + { 179, 0, 188, 138, 11 }, + { 4, 185, 37, 83, 7 }, + { 213, 19, 208, 12, 13 }, }, + { { 238, 179, 122, 196, 6 }, + { 235, 82, 124, 155, 6 }, + { 98, 53, 236, 215, 7 }, + { 109, 147, 228, 173, 7 }, }, + { { 232, 224, 103, 46, 2 }, + { 59, 117, 50, 130, 5 }, + { 71, 78, 96, 113, 7 }, + { 164, 20, 202, 237, 12 }, }, + { { 234, 229, 213, 36, 12 }, + { 59, 135, 115, 179, 0 }, + { 50, 74, 186, 117, 7 }, + { 12, 220, 238, 29, 12 }, }, + { { 237, 107, 28, 44, 2 }, + { 139, 55, 46, 210, 8 }, + { 67, 67, 141, 107, 7 }, + { 20, 183, 78, 205, 1 }, }, + { { 235, 200, 175, 29, 6 }, + { 26, 245, 244, 199, 12 }, + { 107, 143, 81, 61, 7 }, + { 62, 50, 250, 245, 8 }, }, + { { 242, 5, 98, 212, 5 }, + { 44, 74, 224, 171, 6 }, + { 162, 180, 106, 4, 15 }, + { 109, 80, 117, 35, 4 }, }, + { { 246, 25, 188, 251, 2 }, + { 192, 187, 166, 159, 15 }, + { 77, 243, 217, 134, 15 }, + { 255, 150, 93, 208, 3 }, }, + { { 246, 163, 92, 109, 11 }, + { 173, 59, 63, 159, 0 }, + { 219, 99, 172, 86, 15 }, + { 15, 159, 205, 203, 5 }, }, + { { 244, 241, 189, 15, 0 }, + { 217, 175, 52, 150, 5 }, + { 15, 11, 216, 242, 15 }, + { 166, 146, 207, 89, 11 }, }, + { { 241, 106, 155, 67, 5 }, + { 21, 204, 108, 222, 9 }, + { 172, 45, 149, 104, 15 }, + { 151, 179, 99, 58, 8 }, }, + { { 241, 178, 145, 41, 14 }, + { 81, 184, 123, 214, 0 }, + { 121, 72, 148, 216, 15 }, + { 6, 189, 225, 216, 10 }, }, + { { 250, 84, 91, 243, 5 }, + { 118, 76, 230, 191, 3 }, + { 172, 253, 162, 165, 15 }, + { 207, 214, 115, 38, 14 }, }, + { { 254, 110, 134, 124, 6 }, + { 139, 253, 234, 171, 8 }, + { 99, 230, 23, 103, 15 }, + { 29, 85, 123, 253, 1 }, }, + { { 249, 13, 185, 67, 2 }, + { 18, 154, 36, 254, 13 }, + { 76, 41, 219, 9, 15 }, + { 183, 242, 69, 148, 8 }, }, + { { 249, 105, 102, 43, 13 }, + { 39, 111, 99, 198, 13 }, + { 189, 70, 105, 105, 15 }, + { 182, 60, 111, 110, 4 }, }, + { { 251, 65, 203, 72, 4 }, + { 50, 238, 100, 203, 0 }, + { 33, 45, 56, 45, 15 }, + { 13, 50, 103, 116, 12 }, }, + { { 253, 87, 191, 152, 5 }, + { 214, 239, 236, 242, 6 }, + { 161, 159, 222, 171, 15 }, + { 100, 243, 127, 118, 11 }, }, + { { 251, 152, 144, 126, 6 }, + { 74, 184, 242, 219, 9 }, + { 103, 224, 145, 157, 15 }, + { 157, 180, 241, 213, 2 }, }, + { { 255, 234, 33, 198, 3 }, + { 159, 28, 56, 203, 15 }, + { 198, 56, 69, 127, 15 }, + { 253, 49, 195, 143, 9 }, }, + { { 163, 165, 111, 69, 0 }, + { 57, 67, 52, 111, 4 }, + { 10, 47, 106, 92, 5 }, + { 47, 98, 204, 41, 12 }, }, + { { 161, 152, 104, 48, 2 }, + { 96, 16, 182, 66, 12 }, + { 64, 193, 97, 152, 5 }, + { 52, 38, 208, 128, 6 }, }, + { { 15, 55, 131, 43, 0 }, + { 211, 226, 10, 101, 1 }, + { 13, 76, 30, 207, 0 }, + { 138, 101, 4, 124, 11 }, }, + { { 38, 236, 72, 39, 2 }, + { 169, 20, 54, 37, 9 }, + { 78, 65, 35, 118, 4 }, + { 154, 70, 194, 137, 5 }, }, + { { 65, 152, 184, 168, 15 }, + { 68, 176, 87, 208, 14 }, + { 241, 81, 209, 152, 2 }, + { 112, 190, 160, 210, 2 }, }, + { { 78, 181, 67, 138, 4 }, + { 243, 98, 80, 161, 3 }, + { 37, 28, 42, 215, 2 }, + { 200, 80, 164, 108, 15 }, }, + { { 99, 197, 227, 123, 10 }, + { 48, 246, 179, 237, 5 }, + { 93, 236, 122, 60, 6 }, + { 171, 124, 214, 240, 12 }, }, + { { 110, 89, 221, 230, 12 }, + { 250, 135, 103, 153, 11 }, + { 54, 123, 185, 167, 6 }, + { 217, 158, 110, 21, 15 }, }, + { { 128, 212, 89, 240, 8 }, + { 112, 4, 151, 58, 2 }, + { 16, 249, 162, 176, 1 }, + { 69, 206, 146, 0, 14 }, }, + { { 152, 8, 136, 159, 13 }, + { 14, 168, 197, 6, 11 }, + { 191, 145, 17, 1, 9 }, + { 214, 10, 49, 87, 0 }, }, + { { 163, 6, 103, 166, 15 }, + { 60, 81, 107, 99, 7 }, + { 246, 94, 102, 12, 5 }, + { 236, 109, 104, 163, 12 }, }, + { { 2, 25, 166, 20, 7 }, + { 76, 211, 192, 1, 12 }, + { 226, 134, 89, 132, 0 }, + { 56, 0, 60, 179, 2 }, }, + { { 2, 21, 202, 78, 2 }, + { 104, 242, 4, 41, 1 }, + { 71, 37, 58, 132, 0 }, + { 137, 66, 4, 241, 6 }, }, + { { 0, 104, 204, 57, 9 }, + { 37, 165, 135, 4, 8 }, + { 153, 195, 49, 96, 0 }, + { 18, 14, 26, 90, 4 }, }, + { { 0, 161, 19, 254, 8 }, + { 25, 98, 147, 24, 3 }, + { 23, 252, 136, 80, 0 }, + { 193, 140, 148, 105, 8 }, }, + { { 2, 185, 86, 117, 5 }, + { 109, 67, 210, 29, 8 }, + { 170, 230, 169, 212, 0 }, + { 27, 132, 188, 43, 6 }, }, + { { 2, 198, 187, 83, 2 }, + { 16, 212, 156, 61, 5 }, + { 76, 173, 214, 52, 0 }, + { 171, 195, 146, 176, 8 }, }, + { { 2, 243, 31, 29, 9 }, + { 93, 103, 157, 21, 0 }, + { 155, 143, 140, 244, 0 }, + { 10, 139, 158, 107, 10 }, }, + { { 7, 42, 193, 126, 3 }, + { 189, 176, 138, 73, 9 }, + { 199, 232, 53, 78, 0 }, + { 153, 37, 16, 219, 13 }, }, + { { 7, 55, 141, 151, 2 }, + { 217, 147, 140, 101, 3 }, + { 78, 155, 30, 206, 0 }, + { 202, 99, 28, 153, 11 }, }, + { { 1, 111, 31, 231, 5 }, + { 29, 71, 78, 124, 11 }, + { 174, 127, 143, 104, 0 }, + { 211, 231, 46, 43, 8 }, }, + { { 1, 119, 48, 21, 5 }, + { 77, 6, 200, 116, 4 }, + { 170, 128, 206, 232, 0 }, + { 34, 225, 54, 11, 2 }, }, + { { 7, 149, 114, 65, 10 }, + { 224, 82, 17, 125, 4 }, + { 88, 36, 234, 158, 0 }, + { 43, 232, 132, 160, 7 }, }, + { { 7, 200, 163, 134, 14 }, + { 152, 212, 81, 65, 15 }, + { 118, 28, 81, 62, 0 }, + { 248, 40, 162, 177, 9 }, }, + { { 5, 254, 251, 247, 9 }, + { 253, 196, 159, 124, 15 }, + { 158, 253, 247, 250, 0 }, + { 243, 239, 146, 59, 15 }, }, + { { 12, 21, 243, 16, 1 }, + { 246, 194, 128, 48, 4 }, + { 128, 140, 250, 131, 0 }, + { 32, 192, 20, 54, 15 }, }, + { { 10, 68, 98, 226, 14 }, + { 34, 84, 67, 41, 7 }, + { 116, 116, 98, 37, 0 }, + { 233, 76, 34, 164, 4 }, }, + { { 8, 94, 55, 238, 9 }, + { 94, 101, 11, 56, 15 }, + { 151, 126, 199, 161, 0 }, + { 241, 205, 10, 103, 10 }, }, + { { 8, 82, 222, 18, 13 }, + { 102, 197, 205, 16, 1 }, + { 180, 135, 180, 161, 0 }, + { 128, 139, 58, 54, 6 }, }, + { { 12, 102, 136, 3, 9 }, + { 135, 132, 13, 36, 1 }, + { 156, 1, 22, 99, 0 }, + { 130, 75, 2, 30, 1 }, }, + { { 8, 140, 186, 71, 15 }, + { 14, 208, 85, 60, 13 }, + { 254, 37, 211, 17, 0 }, + { 179, 202, 160, 183, 0 }, }, + { { 12, 172, 39, 30, 5 }, + { 159, 97, 208, 32, 13 }, + { 167, 142, 67, 83, 0 }, + { 176, 64, 184, 111, 9 }, }, + { { 8, 179, 56, 11, 15 }, + { 71, 50, 93, 20, 5 }, + { 253, 1, 204, 209, 0 }, + { 162, 139, 164, 206, 2 }, }, + { { 12, 162, 165, 217, 4 }, + { 147, 161, 216, 12, 6 }, + { 41, 186, 84, 83, 0 }, + { 99, 1, 184, 92, 9 }, }, + { { 8, 227, 82, 192, 1 }, + { 39, 70, 24, 24, 2 }, + { 128, 52, 172, 113, 0 }, + { 65, 129, 134, 46, 4 }, }, + { { 14, 255, 68, 245, 14 }, + { 235, 23, 219, 45, 10 }, + { 122, 242, 47, 247, 0 }, + { 91, 77, 190, 141, 7 }, }, + { { 14, 255, 93, 234, 11 }, + { 247, 55, 31, 57, 11 }, + { 213, 123, 175, 247, 0 }, + { 217, 207, 142, 206, 15 }, }, + { { 11, 52, 109, 201, 14 }, + { 115, 49, 69, 109, 6 }, + { 121, 59, 98, 205, 0 }, + { 107, 106, 40, 204, 14 }, }, + { { 13, 9, 254, 187, 3 }, + { 166, 243, 134, 84, 15 }, + { 205, 215, 249, 11, 0 }, + { 242, 166, 28, 246, 5 }, }, + { { 15, 16, 170, 146, 6 }, + { 194, 208, 196, 65, 7 }, + { 100, 149, 80, 143, 0 }, + { 232, 34, 48, 180, 3 }, }, + { { 13, 31, 101, 167, 14 }, + { 250, 19, 75, 100, 15 }, + { 126, 90, 111, 139, 0 }, + { 242, 109, 44, 133, 15 }, }, + { { 15, 177, 95, 160, 0 }, + { 243, 67, 22, 81, 2 }, + { 0, 95, 168, 223, 0 }, + { 72, 166, 140, 44, 15 }, }, + { { 13, 162, 5, 35, 3 }, + { 151, 17, 26, 68, 1 }, + { 204, 74, 4, 91, 0 }, + { 130, 37, 136, 142, 9 }, }, + { { 13, 175, 35, 219, 11 }, + { 151, 114, 153, 108, 15 }, + { 221, 188, 79, 91, 0 }, + { 243, 105, 148, 238, 9 }, }, + { { 11, 240, 165, 238, 4 }, + { 91, 165, 82, 73, 7 }, + { 39, 122, 80, 253, 0 }, + { 233, 36, 170, 93, 10 }, }, + { { 13, 200, 153, 251, 14 }, + { 146, 180, 215, 92, 11 }, + { 125, 249, 145, 59, 0 }, + { 211, 174, 178, 212, 9 }, }, + { { 15, 253, 48, 39, 7 }, + { 207, 22, 82, 117, 13 }, + { 238, 64, 203, 255, 0 }, + { 186, 228, 166, 143, 3 }, }, + { { 11, 199, 93, 86, 2 }, + { 58, 23, 156, 121, 1 }, + { 70, 171, 174, 61, 0 }, + { 137, 227, 158, 133, 12 }, }, + { { 18, 47, 163, 1, 13 }, + { 21, 202, 73, 37, 12 }, + { 184, 12, 95, 68, 8 }, + { 58, 73, 37, 58, 8 }, }, + { { 18, 117, 72, 114, 5 }, + { 101, 14, 198, 41, 1 }, + { 164, 225, 42, 228, 8 }, + { 137, 70, 55, 10, 6 }, }, + { { 16, 79, 174, 98, 7 }, + { 4, 223, 78, 40, 13 }, + { 228, 103, 95, 32, 8 }, + { 177, 71, 47, 178, 0 }, }, + { { 16, 140, 138, 232, 11 }, + { 4, 248, 23, 40, 10 }, + { 209, 117, 19, 16, 8 }, + { 81, 78, 129, 242, 0 }, }, + { { 16, 243, 244, 46, 14 }, + { 105, 191, 91, 16, 5 }, + { 119, 66, 252, 240, 8 }, + { 160, 141, 175, 217, 6 }, }, + { { 19, 55, 238, 112, 2 }, + { 97, 219, 142, 105, 4 }, + { 64, 231, 126, 204, 8 }, + { 41, 103, 29, 184, 6 }, }, + { { 23, 11, 35, 235, 0 }, + { 144, 106, 10, 77, 15 }, + { 13, 124, 77, 14, 8 }, + { 251, 37, 5, 96, 9 }, }, + { { 21, 2, 111, 27, 11 }, + { 180, 121, 141, 68, 5 }, + { 221, 143, 100, 10, 8 }, + { 162, 43, 25, 226, 13 }, }, + { { 17, 161, 171, 203, 2 }, + { 17, 250, 20, 76, 7 }, + { 77, 61, 88, 88, 8 }, + { 227, 34, 133, 248, 8 }, }, + { { 19, 166, 74, 200, 13 }, + { 37, 104, 93, 105, 2 }, + { 177, 53, 38, 92, 8 }, + { 73, 107, 161, 106, 4 }, }, + { { 23, 142, 53, 205, 3 }, + { 156, 57, 24, 125, 14 }, + { 203, 58, 199, 30, 8 }, + { 123, 225, 137, 195, 9 }, }, + { { 21, 139, 229, 157, 12 }, + { 184, 171, 217, 68, 14 }, + { 59, 154, 125, 26, 8 }, + { 114, 41, 189, 81, 13 }, }, + { { 23, 166, 249, 125, 4 }, + { 185, 168, 222, 125, 4 }, + { 43, 233, 246, 94, 8 }, + { 43, 231, 177, 89, 13 }, }, + { { 21, 220, 164, 180, 4 }, + { 200, 141, 210, 96, 14 }, + { 34, 210, 83, 186, 8 }, + { 112, 100, 187, 17, 3 }, }, + { { 17, 223, 5, 67, 12 }, + { 80, 15, 89, 108, 9 }, + { 60, 42, 15, 184, 8 }, + { 147, 105, 175, 0, 10 }, }, + { { 21, 210, 1, 147, 5 }, + { 212, 12, 216, 68, 3 }, + { 172, 152, 4, 186, 8 }, + { 194, 33, 179, 2, 11 }, }, + { { 24, 31, 173, 250, 10 }, + { 82, 187, 143, 40, 15 }, + { 85, 251, 95, 129, 8 }, + { 241, 79, 29, 212, 10 }, }, + { { 28, 64, 14, 171, 13 }, + { 134, 109, 71, 4, 3 }, + { 189, 87, 0, 35, 8 }, + { 194, 14, 43, 102, 1 }, }, + { { 30, 76, 93, 61, 2 }, + { 186, 61, 134, 53, 8 }, + { 75, 203, 163, 39, 8 }, + { 26, 198, 27, 197, 13 }, }, + { { 24, 111, 246, 127, 9 }, + { 47, 239, 139, 60, 13 }, + { 159, 230, 255, 97, 8 }, + { 179, 205, 31, 127, 4 }, }, + { { 24, 87, 52, 184, 15 }, + { 70, 63, 203, 48, 6 }, + { 241, 210, 206, 161, 8 }, + { 96, 205, 63, 198, 2 }, }, + { { 30, 86, 137, 227, 4 }, + { 210, 140, 78, 45, 3 }, + { 44, 121, 22, 167, 8 }, + { 203, 71, 35, 20, 11 }, }, + { { 26, 149, 209, 132, 5 }, + { 126, 138, 80, 49, 2 }, + { 162, 24, 186, 149, 8 }, + { 72, 192, 165, 23, 14 }, }, + { { 24, 167, 255, 2, 10 }, + { 51, 219, 29, 48, 5 }, + { 84, 15, 254, 81, 8 }, + { 160, 203, 141, 188, 12 }, }, + { { 24, 158, 177, 201, 14 }, + { 82, 184, 89, 60, 14 }, + { 121, 56, 215, 145, 8 }, + { 115, 201, 161, 212, 10 }, }, + { { 28, 178, 10, 96, 10 }, + { 195, 88, 31, 8, 0 }, + { 80, 101, 4, 211, 8 }, + { 1, 15, 129, 172, 3 }, }, + { { 28, 146, 53, 136, 1 }, + { 214, 41, 24, 16, 6 }, + { 129, 26, 196, 147, 8 }, + { 96, 129, 137, 70, 11 }, }, + { { 28, 147, 183, 214, 13 }, + { 222, 203, 217, 24, 7 }, + { 182, 190, 220, 147, 8 }, + { 225, 137, 189, 55, 11 }, }, + { { 26, 202, 188, 88, 1 }, + { 6, 173, 156, 25, 12 }, + { 129, 163, 213, 53, 8 }, + { 57, 131, 155, 86, 0 }, }, + { { 28, 226, 172, 181, 8 }, + { 139, 141, 159, 4, 6 }, + { 26, 211, 84, 115, 8 }, + { 98, 15, 155, 29, 1 }, }, + { { 27, 121, 237, 6, 4 }, + { 123, 143, 68, 65, 13 }, + { 38, 11, 121, 237, 8 }, + { 184, 34, 47, 29, 14 }, }, + { { 31, 106, 57, 19, 2 }, + { 147, 28, 140, 85, 13 }, + { 76, 137, 197, 111, 8 }, + { 186, 163, 19, 140, 9 }, }, + { { 29, 152, 56, 117, 0 }, + { 202, 8, 150, 92, 12 }, + { 10, 225, 193, 155, 8 }, + { 51, 166, 145, 5, 3 }, }, + { { 32, 13, 193, 3, 15 }, + { 52, 146, 97, 36, 9 }, + { 252, 8, 59, 0, 4 }, + { 146, 72, 100, 146, 12 }, }, + { { 38, 53, 212, 232, 0 }, + { 225, 163, 34, 57, 2 }, + { 1, 114, 186, 198, 4 }, + { 73, 196, 76, 88, 7 }, }, + { { 38, 117, 44, 47, 8 }, + { 201, 39, 39, 37, 5 }, + { 31, 67, 74, 230, 4 }, + { 170, 78, 78, 73, 3 }, }, + { { 32, 83, 133, 143, 6 }, + { 88, 183, 104, 4, 3 }, + { 111, 26, 28, 160, 4 }, + { 194, 1, 110, 209, 10 }, }, + { { 34, 136, 31, 122, 3 }, + { 20, 113, 182, 25, 9 }, + { 197, 239, 129, 20, 4 }, + { 153, 134, 216, 226, 8 }, }, + { { 39, 44, 250, 229, 5 }, + { 173, 192, 102, 125, 14 }, + { 170, 117, 243, 78, 4 }, + { 123, 230, 96, 59, 5 }, }, + { { 33, 107, 76, 67, 11 }, + { 37, 23, 45, 76, 9 }, + { 220, 35, 45, 104, 4 }, + { 147, 43, 78, 138, 4 }, }, + { { 37, 188, 103, 11, 10 }, + { 241, 113, 49, 100, 13 }, + { 93, 14, 99, 218, 4 }, + { 178, 104, 200, 232, 15 }, }, + { { 33, 163, 233, 176, 14 }, + { 49, 146, 255, 64, 6 }, + { 112, 217, 124, 88, 4 }, + { 96, 47, 244, 152, 12 }, }, + { { 33, 150, 242, 145, 15 }, + { 100, 208, 249, 116, 6 }, + { 248, 148, 246, 152, 4 }, + { 98, 233, 240, 178, 6 }, }, + { { 39, 162, 43, 140, 10 }, + { 153, 112, 61, 65, 6 }, + { 83, 29, 68, 94, 4 }, + { 104, 43, 192, 233, 9 }, }, + { { 39, 159, 72, 50, 8 }, + { 224, 2, 191, 97, 9 }, + { 20, 193, 47, 158, 4 }, + { 152, 111, 212, 0, 7 }, }, + { { 35, 226, 188, 201, 7 }, + { 5, 181, 124, 93, 6 }, + { 233, 51, 212, 124, 4 }, + { 107, 163, 234, 218, 0 }, }, + { { 33, 194, 87, 244, 1 }, + { 60, 69, 186, 88, 2 }, + { 130, 254, 164, 56, 4 }, + { 65, 165, 218, 35, 12 }, }, + { { 39, 207, 186, 248, 11 }, + { 132, 246, 191, 121, 14 }, + { 209, 245, 223, 62, 4 }, + { 121, 239, 214, 242, 1 }, }, + { { 42, 47, 189, 228, 11 }, + { 31, 147, 47, 57, 14 }, + { 210, 123, 223, 69, 4 }, + { 121, 207, 76, 159, 8 }, }, + { { 42, 84, 58, 140, 12 }, + { 74, 100, 101, 49, 6 }, + { 51, 21, 194, 165, 4 }, + { 104, 202, 98, 101, 2 }, }, + { { 42, 209, 187, 151, 15 }, + { 94, 214, 245, 21, 7 }, + { 254, 157, 216, 181, 4 }, + { 234, 138, 246, 183, 10 }, }, + { { 40, 255, 58, 99, 1 }, + { 71, 70, 62, 60, 13 }, + { 140, 101, 207, 241, 4 }, + { 179, 199, 198, 46, 2 }, }, + { { 43, 84, 197, 185, 8 }, + { 114, 165, 163, 101, 2 }, + { 25, 218, 50, 173, 4 }, + { 74, 108, 90, 84, 14 }, }, + { { 45, 97, 175, 26, 10 }, + { 147, 247, 165, 64, 5 }, + { 85, 143, 88, 107, 4 }, + { 160, 42, 94, 252, 9 }, }, + { { 43, 188, 219, 62, 6 }, + { 123, 240, 246, 113, 9 }, + { 103, 205, 179, 221, 4 }, + { 152, 230, 240, 253, 14 }, }, + { { 45, 159, 154, 13, 2 }, + { 202, 242, 60, 116, 8 }, + { 75, 5, 159, 155, 4 }, + { 18, 227, 196, 245, 3 }, }, + { { 47, 187, 114, 106, 3 }, + { 231, 114, 58, 89, 13 }, + { 197, 100, 237, 223, 4 }, + { 185, 165, 196, 238, 7 }, }, + { { 43, 217, 204, 255, 7 }, + { 110, 183, 246, 77, 11 }, + { 239, 243, 57, 189, 4 }, + { 219, 38, 254, 215, 6 }, }, + { { 41, 198, 223, 142, 12 }, + { 58, 229, 125, 112, 3 }, + { 55, 31, 182, 57, 4 }, + { 192, 235, 234, 117, 12 }, }, + { { 52, 12, 63, 195, 5 }, + { 148, 73, 100, 60, 15 }, + { 172, 63, 195, 2, 12 }, + { 243, 194, 105, 34, 9 }, }, + { { 48, 3, 27, 40, 14 }, + { 16, 122, 111, 16, 0 }, + { 113, 77, 140, 0, 12 }, + { 0, 143, 101, 224, 8 }, }, + { { 48, 113, 29, 236, 3 }, + { 93, 63, 38, 24, 2 }, + { 195, 123, 136, 224, 12 }, + { 65, 134, 79, 203, 10 }, }, + { { 52, 76, 222, 162, 10 }, + { 160, 221, 39, 48, 11 }, + { 84, 87, 179, 34, 12 }, + { 208, 206, 75, 176, 5 }, }, + { { 50, 122, 138, 139, 12 }, + { 65, 236, 109, 5, 11 }, + { 61, 21, 21, 228, 12 }, + { 218, 11, 99, 120, 2 }, }, + { { 54, 87, 99, 215, 2 }, + { 248, 94, 168, 45, 7 }, + { 78, 188, 110, 166, 12 }, + { 235, 65, 87, 161, 15 }, }, + { { 52, 141, 50, 170, 6 }, + { 128, 122, 114, 48, 15 }, + { 101, 84, 203, 18, 12 }, + { 240, 196, 229, 224, 1 }, }, + { { 54, 129, 250, 177, 1 }, + { 164, 202, 182, 21, 6 }, + { 136, 213, 248, 22, 12 }, + { 106, 134, 213, 50, 5 }, }, + { { 54, 153, 111, 21, 13 }, + { 252, 75, 245, 5, 12 }, + { 186, 143, 105, 150, 12 }, + { 58, 10, 253, 35, 15 }, }, + { { 50, 204, 108, 49, 14 }, + { 32, 29, 247, 37, 12 }, + { 120, 195, 99, 52, 12 }, + { 58, 78, 251, 128, 4 }, }, + { { 54, 224, 7, 77, 4 }, + { 153, 109, 112, 13, 0 }, + { 43, 46, 0, 118, 12 }, + { 11, 0, 235, 105, 9 }, }, + { { 51, 64, 141, 156, 5 }, + { 28, 173, 228, 65, 2 }, + { 163, 155, 16, 44, 12 }, + { 72, 34, 123, 83, 8 }, }, + { { 51, 125, 174, 182, 10 }, + { 73, 223, 167, 97, 15 }, + { 86, 215, 91, 236, 12 }, + { 248, 110, 95, 185, 2 }, }, + { { 49, 94, 255, 61, 1 }, + { 124, 237, 174, 116, 12 }, + { 139, 207, 247, 168, 12 }, + { 50, 231, 91, 115, 14 }, }, + { { 53, 164, 249, 40, 11 }, + { 181, 184, 55, 112, 4 }, + { 209, 73, 242, 90, 12 }, + { 32, 238, 193, 218, 13 }, }, + { { 51, 134, 161, 198, 4 }, + { 24, 136, 120, 105, 7 }, + { 38, 56, 86, 28, 12 }, + { 233, 97, 225, 17, 8 }, }, + { { 53, 232, 26, 158, 12 }, + { 137, 108, 245, 80, 11 }, + { 55, 149, 129, 122, 12 }, + { 208, 170, 243, 105, 1 }, }, + { { 55, 216, 88, 186, 3 }, + { 228, 60, 182, 81, 11 }, + { 197, 209, 161, 190, 12 }, + { 216, 166, 211, 194, 7 }, }, + { { 56, 21, 51, 89, 12 }, + { 82, 106, 225, 60, 4 }, + { 57, 172, 202, 129, 12 }, + { 35, 200, 117, 100, 10 }, }, + { { 56, 27, 98, 106, 12 }, + { 98, 106, 107, 8, 13 }, + { 53, 100, 109, 129, 12 }, + { 177, 13, 101, 100, 6 }, }, + { { 58, 54, 80, 219, 10 }, + { 99, 56, 169, 61, 3 }, + { 93, 176, 166, 197, 12 }, + { 203, 201, 81, 204, 6 }, }, + { { 56, 136, 209, 242, 9 }, + { 54, 136, 179, 24, 11 }, + { 148, 248, 177, 17, 12 }, + { 209, 140, 209, 22, 12 }, }, + { { 58, 204, 141, 200, 2 }, + { 18, 189, 52, 41, 10 }, + { 65, 59, 19, 53, 12 }, + { 89, 66, 203, 212, 8 }, }, + { { 60, 252, 249, 7, 11 }, + { 255, 156, 53, 52, 13 }, + { 222, 9, 243, 243, 12 }, + { 178, 202, 195, 159, 15 }, }, + { { 60, 246, 137, 57, 12 }, + { 211, 172, 255, 36, 0 }, + { 57, 201, 22, 243, 12 }, + { 2, 79, 243, 92, 11 }, }, + { { 59, 15, 174, 199, 9 }, + { 14, 203, 45, 109, 15 }, + { 158, 55, 95, 13, 12 }, + { 251, 107, 77, 55, 0 }, }, + { { 61, 11, 95, 245, 6 }, + { 186, 91, 238, 92, 10 }, + { 106, 255, 173, 11, 12 }, + { 83, 167, 125, 165, 13 }, }, + { { 57, 73, 131, 170, 6 }, + { 18, 254, 98, 64, 11 }, + { 101, 92, 25, 41, 12 }, + { 208, 36, 103, 244, 8 }, }, + { { 61, 92, 175, 229, 6 }, + { 218, 221, 102, 108, 14 }, + { 106, 127, 83, 171, 12 }, + { 115, 102, 107, 181, 11 }, }, + { { 59, 111, 209, 254, 2 }, + { 59, 190, 170, 121, 11 }, + { 71, 248, 191, 109, 12 }, + { 217, 229, 87, 221, 12 }, }, + { { 61, 114, 229, 206, 7 }, + { 255, 189, 104, 72, 7 }, + { 231, 58, 116, 235, 12 }, + { 225, 33, 107, 223, 15 }, }, + { { 59, 165, 24, 48, 4 }, + { 3, 10, 246, 113, 0 }, + { 32, 193, 138, 93, 12 }, + { 8, 230, 245, 12, 0 }, }, + { { 59, 215, 215, 116, 14 }, + { 122, 223, 251, 121, 0 }, + { 114, 238, 190, 189, 12 }, + { 9, 237, 255, 181, 14 }, }, + { { 66, 21, 7, 134, 1 }, + { 92, 67, 0, 161, 3 }, + { 134, 30, 10, 132, 2 }, + { 200, 80, 12, 35, 10 }, }, + { { 68, 42, 51, 204, 5 }, + { 157, 96, 72, 152, 14 }, + { 163, 60, 197, 66, 2 }, + { 113, 145, 32, 107, 9 }, }, + { { 64, 105, 62, 32, 12 }, + { 1, 71, 71, 144, 12 }, + { 48, 71, 201, 96, 2 }, + { 48, 158, 46, 40, 0 }, }, + { { 68, 76, 84, 241, 12 }, + { 160, 5, 195, 188, 10 }, + { 56, 242, 163, 34, 2 }, + { 83, 220, 58, 0, 5 }, }, + { { 64, 78, 207, 5, 5 }, + { 60, 197, 76, 164, 8 }, + { 170, 15, 55, 32, 2 }, + { 18, 83, 42, 51, 12 }, }, + { { 64, 223, 74, 196, 6 }, + { 104, 86, 92, 168, 10 }, + { 98, 53, 47, 176, 2 }, + { 81, 83, 166, 161, 6 }, }, + { { 70, 218, 103, 26, 14 }, + { 240, 117, 217, 129, 13 }, + { 117, 142, 101, 182, 2 }, + { 184, 25, 186, 224, 15 }, }, + { { 67, 32, 239, 178, 13 }, + { 53, 193, 199, 193, 7 }, + { 180, 223, 112, 76, 2 }, + { 232, 62, 56, 58, 12 }, }, + { { 65, 99, 121, 242, 7 }, + { 53, 22, 206, 216, 7 }, + { 228, 249, 236, 104, 2 }, + { 225, 183, 54, 138, 12 }, }, + { { 71, 127, 169, 44, 10 }, + { 217, 182, 15, 225, 12 }, + { 83, 73, 95, 238, 2 }, + { 56, 127, 6, 217, 11 }, }, + { { 67, 168, 36, 115, 10 }, + { 1, 17, 147, 205, 13 }, + { 92, 226, 65, 92, 2 }, + { 187, 60, 152, 136, 0 }, }, + { { 69, 174, 2, 166, 1 }, + { 141, 64, 26, 224, 11 }, + { 134, 84, 7, 90, 2 }, + { 208, 117, 128, 43, 1 }, }, + { { 72, 29, 125, 107, 6 }, + { 114, 51, 70, 188, 13 }, + { 109, 107, 235, 129, 2 }, + { 179, 214, 44, 196, 14 }, }, + { { 72, 68, 41, 14, 4 }, + { 26, 36, 68, 160, 5 }, + { 39, 9, 66, 33, 2 }, + { 160, 82, 34, 69, 8 }, }, + { { 76, 93, 176, 242, 6 }, + { 194, 150, 194, 184, 15 }, + { 100, 240, 219, 163, 2 }, + { 241, 212, 54, 148, 3 }, }, + { { 72, 79, 166, 76, 0 }, + { 10, 231, 8, 168, 12 }, + { 3, 38, 95, 33, 2 }, + { 49, 81, 14, 117, 0 }, }, + { { 72, 129, 242, 45, 11 }, + { 46, 242, 19, 148, 4 }, + { 219, 68, 248, 17, 2 }, + { 34, 156, 132, 247, 4 }, }, + { { 72, 140, 105, 154, 11 }, + { 54, 48, 149, 160, 15 }, + { 213, 153, 99, 17, 2 }, + { 240, 90, 144, 198, 12 }, }, + { { 76, 230, 39, 197, 2 }, + { 155, 85, 24, 172, 6 }, + { 74, 62, 70, 115, 2 }, + { 99, 81, 138, 173, 9 }, }, + { { 79, 61, 163, 205, 7 }, + { 223, 242, 64, 237, 14 }, + { 235, 60, 91, 207, 2 }, + { 123, 112, 36, 255, 11 }, }, + { { 73, 73, 253, 184, 8 }, + { 50, 167, 135, 208, 14 }, + { 17, 219, 249, 41, 2 }, + { 112, 190, 30, 84, 12 }, }, + { { 77, 89, 111, 25, 7 }, + { 246, 119, 196, 196, 12 }, + { 233, 143, 105, 171, 2 }, + { 50, 50, 62, 230, 15 }, }, + { { 79, 79, 213, 39, 11 }, + { 190, 151, 11, 245, 9 }, + { 222, 74, 191, 47, 2 }, + { 154, 253, 14, 151, 13 }, }, + { { 75, 148, 100, 228, 11 }, + { 110, 17, 19, 233, 6 }, + { 210, 114, 98, 157, 2 }, + { 105, 124, 136, 135, 6 }, }, + { { 77, 160, 106, 169, 14 }, + { 163, 112, 87, 196, 6 }, + { 121, 85, 96, 91, 2 }, + { 98, 62, 160, 236, 5 }, }, + { { 75, 146, 209, 251, 2 }, + { 114, 176, 154, 221, 3 }, + { 77, 248, 180, 157, 2 }, + { 203, 181, 144, 212, 14 }, }, + { { 79, 237, 128, 190, 11 }, + { 143, 182, 147, 225, 11 }, + { 215, 208, 27, 127, 2 }, + { 216, 124, 150, 223, 1 }, }, + { { 73, 250, 110, 175, 3 }, + { 111, 117, 30, 196, 15 }, + { 207, 87, 101, 249, 2 }, + { 242, 55, 138, 239, 6 }, }, + { { 84, 32, 172, 167, 7 }, + { 141, 153, 70, 132, 7 }, + { 238, 83, 80, 66, 10 }, + { 226, 22, 41, 155, 1 }, }, + { { 84, 25, 164, 142, 8 }, + { 200, 171, 1, 128, 15 }, + { 23, 18, 89, 130, 10 }, + { 240, 24, 13, 81, 3 }, }, + { { 86, 116, 218, 31, 8 }, + { 233, 236, 133, 181, 1 }, + { 31, 133, 178, 230, 10 }, + { 138, 218, 19, 121, 7 }, }, + { { 82, 70, 69, 212, 11 }, + { 60, 29, 137, 169, 2 }, + { 210, 186, 38, 36, 10 }, + { 73, 89, 27, 131, 12 }, }, + { { 80, 153, 112, 192, 3 }, + { 100, 26, 16, 152, 14 }, + { 192, 48, 233, 144, 10 }, + { 113, 144, 133, 130, 6 }, }, + { { 82, 162, 196, 106, 5 }, + { 37, 169, 90, 137, 1 }, + { 165, 98, 52, 84, 10 }, + { 137, 21, 169, 90, 4 }, }, + { { 80, 232, 170, 66, 4 }, + { 1, 204, 84, 136, 13 }, + { 36, 37, 81, 112, 10 }, + { 177, 18, 163, 56, 0 }, }, + { { 82, 242, 173, 89, 8 }, + { 81, 173, 157, 141, 4 }, + { 25, 171, 84, 244, 10 }, + { 43, 27, 155, 88, 10 }, }, + { { 82, 218, 235, 246, 10 }, + { 120, 220, 159, 137, 15 }, + { 86, 253, 117, 180, 10 }, + { 249, 31, 147, 177, 14 }, }, + { { 85, 46, 248, 226, 2 }, + { 161, 152, 14, 248, 15 }, + { 68, 113, 247, 74, 10 }, + { 241, 247, 1, 152, 5 }, }, + { { 81, 65, 96, 182, 2 }, + { 40, 30, 130, 192, 7 }, + { 70, 208, 104, 40, 10 }, + { 224, 52, 23, 129, 4 }, }, + { { 85, 104, 6, 21, 8 }, + { 137, 77, 129, 196, 8 }, + { 26, 134, 1, 106, 10 }, + { 18, 56, 27, 41, 1 }, }, + { { 83, 74, 126, 75, 13 }, + { 36, 109, 77, 221, 13 }, + { 189, 39, 229, 44, 10 }, + { 187, 187, 43, 98, 4 }, }, + { { 83, 107, 211, 224, 11 }, + { 53, 222, 11, 217, 10 }, + { 208, 124, 189, 108, 10 }, + { 89, 189, 7, 186, 12 }, }, + { { 87, 114, 130, 210, 7 }, + { 197, 220, 200, 201, 3 }, + { 228, 180, 20, 238, 10 }, + { 201, 49, 51, 186, 3 }, }, + { { 87, 129, 213, 88, 2 }, + { 176, 187, 144, 217, 0 }, + { 65, 170, 184, 30, 10 }, + { 9, 176, 157, 208, 13 }, }, + { { 87, 164, 195, 74, 8 }, + { 177, 232, 17, 233, 1 }, + { 21, 44, 50, 94, 10 }, + { 137, 120, 129, 120, 13 }, }, + { { 81, 151, 175, 148, 8 }, + { 88, 203, 157, 224, 6 }, + { 18, 159, 94, 152, 10 }, + { 96, 123, 157, 49, 10 }, }, + { { 87, 143, 23, 115, 11 }, + { 148, 91, 155, 253, 9 }, + { 220, 238, 143, 30, 10 }, + { 155, 253, 157, 162, 9 }, }, + { { 85, 194, 224, 207, 0 }, + { 168, 172, 24, 204, 7 }, + { 15, 48, 116, 58, 10 }, + { 227, 49, 131, 81, 5 }, }, + { { 87, 246, 164, 229, 1 }, + { 205, 141, 26, 237, 6 }, + { 138, 114, 86, 254, 10 }, + { 107, 117, 139, 27, 3 }, }, + { { 92, 5, 94, 2, 5 }, + { 166, 75, 68, 176, 1 }, + { 164, 7, 170, 3, 10 }, + { 128, 210, 45, 38, 5 }, }, + { { 94, 56, 204, 77, 0 }, + { 235, 169, 4, 141, 8 }, + { 11, 35, 49, 199, 10 }, + { 27, 18, 9, 93, 7 }, }, + { { 94, 101, 102, 31, 5 }, + { 175, 111, 192, 165, 5 }, + { 175, 134, 106, 103, 10 }, + { 170, 80, 63, 111, 5 }, }, + { { 90, 165, 235, 123, 5 }, + { 55, 234, 214, 173, 5 }, + { 173, 237, 122, 85, 10 }, + { 171, 86, 181, 126, 12 }, }, + { { 94, 173, 81, 224, 13 }, + { 183, 10, 83, 185, 10 }, + { 176, 120, 171, 87, 10 }, + { 89, 220, 165, 14, 13 }, }, + { { 88, 179, 133, 252, 1 }, + { 95, 171, 154, 136, 2 }, + { 131, 250, 28, 209, 10 }, + { 65, 21, 157, 95, 10 }, }, + { { 90, 197, 248, 110, 0 }, + { 42, 174, 22, 185, 5 }, + { 7, 97, 250, 53, 10 }, + { 169, 214, 135, 85, 4 }, }, + { { 88, 241, 107, 96, 0 }, + { 115, 78, 22, 136, 4 }, + { 0, 109, 104, 241, 10 }, + { 33, 22, 135, 44, 14 }, }, + { { 91, 48, 177, 32, 8 }, + { 83, 136, 3, 209, 4 }, + { 16, 72, 208, 205, 10 }, + { 40, 188, 1, 28, 10 }, }, + { { 95, 52, 239, 231, 11 }, + { 255, 217, 7, 237, 7 }, + { 222, 127, 114, 207, 10 }, + { 235, 126, 9, 191, 15 }, }, + { { 89, 64, 115, 102, 9 }, + { 62, 76, 3, 216, 5 }, + { 150, 108, 224, 41, 10 }, + { 161, 188, 3, 39, 12 }, }, + { { 93, 105, 19, 173, 1 }, + { 159, 110, 2, 212, 10 }, + { 139, 92, 137, 107, 10 }, + { 82, 180, 7, 111, 9 }, }, + { { 89, 82, 185, 227, 11 }, + { 86, 156, 15, 220, 7 }, + { 220, 121, 212, 169, 10 }, + { 227, 191, 3, 150, 10 }, }, + { { 93, 70, 244, 172, 2 }, + { 170, 189, 10, 240, 6 }, + { 67, 82, 246, 43, 10 }, + { 96, 245, 11, 213, 5 }, }, + { { 95, 128, 157, 46, 8 }, + { 154, 169, 23, 209, 1 }, + { 23, 75, 144, 31, 10 }, + { 136, 190, 137, 85, 9 }, }, + { { 95, 224, 251, 80, 14 }, + { 179, 220, 213, 217, 4 }, + { 112, 173, 240, 127, 10 }, + { 41, 186, 179, 188, 13 }, }, + { { 95, 235, 211, 215, 0 }, + { 187, 206, 152, 221, 11 }, + { 14, 188, 189, 127, 10 }, + { 219, 177, 151, 61, 13 }, }, + { { 100, 105, 134, 115, 5 }, + { 133, 199, 226, 140, 9 }, + { 172, 230, 25, 98, 6 }, + { 147, 20, 126, 58, 1 }, }, + { { 96, 188, 232, 205, 8 }, + { 105, 160, 53, 172, 14 }, + { 27, 49, 115, 208, 6 }, + { 115, 90, 192, 89, 6 }, }, + { { 100, 158, 227, 5, 6 }, + { 248, 208, 120, 164, 12 }, + { 106, 12, 119, 146, 6 }, + { 50, 81, 224, 177, 15 }, }, + { { 98, 212, 165, 37, 13 }, + { 92, 133, 115, 165, 4 }, + { 186, 74, 82, 180, 6 }, + { 42, 92, 234, 19, 10 }, }, + { { 98, 246, 143, 142, 3 }, + { 93, 245, 60, 161, 3 }, + { 199, 31, 22, 244, 6 }, + { 200, 83, 202, 251, 10 }, }, + { { 101, 96, 72, 36, 7 }, + { 173, 20, 102, 192, 0 }, + { 226, 65, 32, 106, 6 }, + { 0, 54, 98, 139, 5 }, }, + { { 99, 180, 12, 145, 8 }, + { 65, 1, 181, 229, 2 }, + { 24, 147, 2, 220, 6 }, + { 74, 122, 216, 8, 2 }, }, + { { 99, 159, 233, 153, 13 }, + { 116, 162, 253, 229, 14 }, + { 185, 153, 127, 156, 6 }, + { 122, 123, 244, 82, 14 }, }, + { { 101, 237, 229, 156, 7 }, + { 189, 183, 240, 224, 14 }, + { 227, 154, 123, 122, 6 }, + { 112, 112, 254, 219, 13 }, }, + { { 103, 235, 231, 112, 4 }, + { 177, 199, 250, 201, 12 }, + { 32, 238, 125, 126, 6 }, + { 57, 53, 254, 56, 13 }, }, + { { 106, 1, 200, 157, 4 }, + { 42, 162, 228, 133, 2 }, + { 43, 145, 56, 5, 6 }, + { 74, 18, 116, 85, 4 }, }, + { { 104, 61, 242, 11, 0 }, + { 99, 226, 32, 180, 13 }, + { 13, 4, 251, 193, 6 }, + { 178, 208, 68, 124, 6 }, }, + { { 104, 24, 102, 125, 13 }, + { 110, 97, 227, 140, 12 }, + { 187, 230, 97, 129, 6 }, + { 51, 28, 120, 103, 6 }, }, + { { 108, 84, 97, 80, 15 }, + { 246, 20, 225, 168, 4 }, + { 240, 168, 98, 163, 6 }, + { 33, 88, 114, 134, 15 }, }, + { { 104, 75, 24, 245, 13 }, + { 14, 6, 239, 156, 10 }, + { 186, 241, 141, 33, 6 }, + { 83, 159, 118, 7, 0 }, }, + { { 104, 79, 21, 129, 0 }, + { 18, 7, 40, 180, 10 }, + { 8, 26, 143, 33, 6 }, + { 82, 209, 78, 4, 8 }, }, + { { 110, 110, 244, 89, 0 }, + { 163, 165, 168, 189, 12 }, + { 9, 162, 247, 103, 6 }, + { 59, 209, 90, 92, 5 }, }, + { { 110, 152, 128, 38, 5 }, + { 206, 128, 114, 129, 9 }, + { 166, 64, 17, 151, 6 }, + { 152, 20, 224, 23, 3 }, }, + { { 110, 144, 99, 51, 3 }, + { 246, 80, 178, 133, 5 }, + { 204, 204, 96, 151, 6 }, + { 170, 20, 208, 166, 15 }, }, + { { 110, 241, 228, 10, 7 }, + { 231, 183, 112, 129, 5 }, + { 229, 2, 120, 247, 6 }, + { 168, 16, 238, 222, 7 }, }, + { { 108, 195, 36, 252, 0 }, + { 138, 39, 186, 136, 6 }, + { 3, 242, 76, 51, 6 }, + { 97, 21, 222, 69, 1 }, }, + { { 105, 0, 81, 91, 15 }, + { 54, 48, 225, 220, 1 }, + { 253, 168, 160, 9, 6 }, + { 131, 184, 112, 198, 12 }, }, + { { 107, 64, 14, 250, 12 }, + { 2, 101, 231, 201, 3 }, + { 53, 247, 0, 45, 6 }, + { 201, 62, 122, 100, 0 }, }, + { { 107, 124, 181, 68, 7 }, + { 95, 149, 96, 249, 12 }, + { 226, 42, 211, 237, 6 }, + { 57, 240, 106, 159, 10 }, }, + { { 105, 172, 245, 202, 2 }, + { 51, 177, 48, 248, 15 }, + { 69, 58, 243, 89, 6 }, + { 241, 240, 200, 220, 12 }, }, + { { 105, 167, 102, 247, 12 }, + { 43, 67, 251, 236, 7 }, + { 62, 246, 110, 89, 6 }, + { 227, 125, 252, 45, 4 }, }, + { { 109, 235, 50, 140, 11 }, + { 143, 118, 57, 208, 14 }, + { 211, 20, 205, 123, 6 }, + { 112, 185, 198, 239, 1 }, }, + { { 116, 23, 252, 110, 12 }, + { 232, 171, 111, 184, 5 }, + { 55, 99, 254, 130, 14 }, + { 161, 223, 109, 81, 7 }, }, + { { 112, 97, 170, 185, 4 }, + { 1, 238, 230, 132, 6 }, + { 41, 213, 88, 96, 14 }, + { 98, 22, 119, 120, 0 }, }, + { { 116, 67, 17, 82, 1 }, + { 148, 14, 168, 152, 1 }, + { 132, 168, 140, 34, 14 }, + { 129, 145, 87, 2, 9 }, }, + { { 112, 185, 187, 138, 9 }, + { 85, 234, 53, 144, 15 }, + { 149, 29, 217, 208, 14 }, + { 240, 154, 197, 122, 10 }, }, + { { 118, 148, 62, 229, 4 }, + { 200, 73, 118, 189, 6 }, + { 42, 119, 194, 150, 14 }, + { 107, 214, 233, 33, 3 }, }, + { { 118, 188, 96, 18, 9 }, + { 229, 8, 177, 161, 13 }, + { 148, 128, 99, 214, 14 }, + { 184, 88, 209, 10, 7 }, }, + { { 112, 155, 7, 165, 4 }, + { 88, 75, 122, 132, 10 }, + { 42, 94, 13, 144, 14 }, + { 82, 21, 237, 33, 10 }, }, + { { 116, 139, 191, 1, 3 }, + { 148, 219, 60, 148, 12 }, + { 200, 15, 221, 18, 14 }, + { 50, 147, 205, 178, 9 }, }, + { { 114, 248, 93, 79, 11 }, + { 125, 61, 53, 157, 9 }, + { 223, 43, 161, 244, 14 }, + { 155, 154, 203, 203, 14 }, }, + { { 118, 211, 218, 167, 15 }, + { 236, 222, 127, 149, 3 }, + { 254, 85, 188, 182, 14 }, + { 202, 159, 231, 179, 7 }, }, + { { 117, 36, 208, 249, 9 }, + { 165, 168, 163, 252, 2 }, + { 153, 240, 178, 74, 14 }, + { 67, 252, 81, 90, 5 }, }, + { { 117, 60, 188, 48, 6 }, + { 193, 153, 230, 240, 12 }, + { 96, 195, 211, 202, 14 }, + { 48, 246, 121, 152, 3 }, }, + { { 119, 3, 201, 213, 15 }, + { 188, 154, 237, 205, 2 }, + { 250, 185, 60, 14, 14 }, + { 75, 59, 117, 147, 13 }, }, + { { 113, 69, 59, 42, 5 }, + { 20, 110, 102, 240, 5 }, + { 165, 77, 202, 40, 14 }, + { 160, 246, 103, 98, 8 }, }, + { { 115, 108, 25, 223, 1 }, + { 29, 44, 164, 253, 11 }, + { 143, 185, 131, 108, 14 }, + { 219, 242, 83, 75, 8 }, }, + { { 113, 72, 238, 44, 10 }, + { 40, 253, 39, 192, 12 }, + { 83, 71, 113, 40, 14 }, + { 48, 62, 75, 241, 4 }, }, + { { 115, 111, 34, 124, 0 }, + { 9, 110, 170, 233, 12 }, + { 3, 228, 79, 108, 14 }, + { 57, 117, 87, 105, 0 }, }, + { { 115, 145, 99, 174, 14 }, + { 120, 122, 115, 193, 7 }, + { 119, 92, 104, 156, 14 }, + { 232, 60, 229, 225, 14 }, }, + { { 117, 131, 158, 142, 9 }, + { 140, 235, 61, 208, 3 }, + { 151, 23, 156, 26, 14 }, + { 192, 187, 205, 115, 1 }, }, + { { 119, 191, 100, 123, 6 }, + { 225, 59, 250, 237, 13 }, + { 109, 226, 111, 222, 14 }, + { 187, 117, 253, 200, 7 }, }, + { { 115, 229, 189, 18, 10 }, + { 17, 159, 181, 241, 5 }, + { 84, 139, 218, 124, 14 }, + { 168, 250, 223, 152, 8 }, }, + { { 115, 207, 104, 128, 3 }, + { 36, 30, 60, 225, 14 }, + { 192, 17, 111, 60, 14 }, + { 120, 115, 199, 130, 4 }, }, + { { 113, 247, 74, 10, 0 }, + { 97, 110, 60, 224, 1 }, + { 5, 5, 46, 248, 14 }, + { 128, 115, 199, 104, 6 }, }, + { { 120, 45, 207, 39, 6 }, + { 59, 219, 102, 164, 9 }, + { 110, 79, 59, 65, 14 }, + { 146, 86, 109, 189, 12 }, }, + { { 124, 2, 58, 157, 7 }, + { 142, 120, 236, 148, 6 }, + { 235, 149, 196, 3, 14 }, + { 98, 147, 113, 231, 1 }, }, + { { 122, 92, 206, 58, 4 }, + { 98, 237, 230, 161, 9 }, + { 37, 199, 51, 165, 14 }, + { 152, 86, 123, 116, 6 }, }, + { { 122, 114, 16, 7, 7 }, + { 79, 28, 104, 149, 1 }, + { 238, 0, 132, 229, 14 }, + { 138, 145, 99, 143, 2 }, }, + { { 126, 178, 36, 208, 11 }, + { 199, 25, 185, 137, 6 }, + { 208, 178, 68, 215, 14 }, + { 105, 25, 217, 142, 3 }, }, + { { 120, 253, 197, 213, 2 }, + { 123, 159, 176, 172, 10 }, + { 74, 186, 59, 241, 14 }, + { 83, 80, 223, 157, 14 }, }, + { { 126, 232, 117, 158, 9 }, + { 191, 45, 177, 145, 15 }, + { 151, 154, 225, 119, 14 }, + { 248, 152, 219, 79, 13 }, }, + { { 123, 28, 43, 68, 14 }, + { 90, 88, 101, 233, 12 }, + { 114, 45, 67, 141, 14 }, + { 57, 122, 97, 165, 10 }, }, + { { 127, 4, 160, 250, 2 }, + { 130, 184, 162, 233, 7 }, + { 69, 240, 82, 15, 14 }, + { 233, 116, 81, 212, 1 }, }, + { { 123, 39, 78, 235, 3 }, + { 39, 123, 46, 237, 3 }, + { 205, 119, 46, 77, 14 }, + { 203, 119, 77, 238, 4 }, }, + { { 125, 38, 127, 122, 1 }, + { 183, 105, 174, 248, 5 }, + { 133, 239, 230, 75, 14 }, + { 161, 247, 89, 110, 13 }, }, + { { 127, 50, 169, 94, 10 }, + { 219, 184, 173, 201, 5 }, + { 87, 169, 84, 207, 14 }, + { 169, 59, 81, 221, 11 }, }, + { { 121, 180, 170, 170, 7 }, + { 71, 248, 118, 224, 7 }, + { 229, 85, 82, 217, 14 }, + { 224, 118, 225, 254, 2 }, }, + { { 125, 176, 215, 37, 3 }, + { 255, 217, 50, 212, 0 }, + { 202, 78, 176, 219, 14 }, + { 2, 180, 201, 191, 15 }, }, + { { 127, 147, 79, 12, 2 }, + { 250, 123, 60, 193, 0 }, + { 67, 15, 44, 159, 14 }, + { 8, 51, 205, 229, 15 }, }, + { { 125, 252, 16, 66, 2 }, + { 195, 28, 48, 248, 9 }, + { 68, 32, 131, 251, 14 }, + { 145, 240, 195, 140, 3 }, }, + { { 125, 213, 216, 211, 15 }, + { 230, 158, 245, 252, 3 }, + { 252, 177, 186, 187, 14 }, + { 195, 250, 247, 150, 7 }, }, + { { 128, 56, 32, 103, 11 }, + { 77, 16, 3, 14, 13 }, + { 222, 96, 65, 192, 1 }, + { 183, 12, 0, 139, 2 }, }, + { { 132, 17, 185, 214, 3 }, + { 220, 146, 132, 26, 7 }, + { 198, 185, 216, 130, 1 }, + { 229, 130, 20, 147, 11 }, }, + { { 128, 3, 252, 100, 10 }, + { 40, 147, 15, 26, 4 }, + { 82, 99, 252, 0, 1 }, + { 37, 143, 12, 145, 4 }, }, + { { 130, 59, 15, 187, 10 }, + { 81, 115, 143, 7, 11 }, + { 93, 223, 13, 196, 1 }, + { 222, 15, 28, 232, 10 }, }, + { { 134, 42, 167, 117, 10 }, + { 153, 209, 139, 15, 12 }, + { 90, 238, 85, 70, 1 }, + { 63, 13, 24, 185, 9 }, }, + { { 132, 27, 89, 75, 7 }, + { 244, 50, 76, 30, 9 }, + { 237, 41, 173, 130, 1 }, + { 151, 131, 36, 194, 15 }, }, + { { 130, 68, 49, 200, 10 }, + { 16, 52, 1, 59, 6 }, + { 81, 56, 194, 36, 1 }, + { 109, 200, 2, 192, 8 }, }, + { { 130, 89, 216, 117, 3 }, + { 108, 150, 134, 31, 8 }, + { 202, 225, 185, 164, 1 }, + { 31, 134, 22, 147, 6 }, }, + { { 132, 72, 31, 220, 14 }, + { 152, 117, 197, 26, 10 }, + { 115, 191, 129, 34, 1 }, + { 85, 138, 58, 225, 9 }, }, + { { 130, 181, 229, 107, 4 }, + { 113, 163, 82, 47, 5 }, + { 45, 106, 122, 212, 1 }, + { 175, 68, 172, 88, 14 }, }, + { { 128, 183, 10, 183, 0 }, + { 73, 66, 158, 38, 3 }, + { 14, 213, 14, 208, 1 }, + { 198, 71, 148, 41, 2 }, }, + { { 134, 178, 80, 25, 10 }, + { 225, 48, 153, 23, 0 }, + { 89, 128, 164, 214, 1 }, + { 14, 137, 144, 200, 7 }, }, + { { 128, 204, 47, 123, 8 }, + { 16, 101, 151, 46, 13 }, + { 29, 239, 67, 48, 1 }, + { 183, 78, 154, 96, 8 }, }, + { { 134, 253, 182, 100, 8 }, + { 201, 199, 19, 59, 12 }, + { 18, 102, 219, 246, 1 }, + { 61, 204, 142, 57, 3 }, }, + { { 129, 121, 60, 62, 9 }, + { 77, 39, 135, 82, 13 }, + { 151, 195, 201, 232, 1 }, + { 180, 174, 30, 75, 2 }, }, + { { 133, 96, 132, 133, 11 }, + { 141, 149, 1, 70, 2 }, + { 218, 18, 16, 106, 1 }, + { 70, 40, 10, 155, 1 }, }, + { { 131, 98, 22, 146, 5 }, + { 5, 69, 200, 83, 3 }, + { 164, 150, 132, 108, 1 }, + { 204, 161, 58, 42, 0 }, }, + { { 131, 128, 229, 221, 15 }, + { 60, 177, 209, 79, 6 }, + { 251, 186, 112, 28, 1 }, + { 111, 40, 184, 211, 12 }, }, + { { 135, 160, 76, 203, 2 }, + { 161, 49, 20, 79, 3 }, + { 77, 51, 32, 94, 1 }, + { 207, 34, 136, 200, 5 }, }, + { { 135, 184, 138, 180, 13 }, + { 205, 192, 215, 67, 10 }, + { 178, 213, 17, 222, 1 }, + { 92, 46, 176, 59, 3 }, }, + { { 142, 2, 6, 56, 4 }, + { 130, 97, 202, 3, 0 }, + { 33, 198, 4, 7, 1 }, + { 12, 5, 56, 100, 1 }, }, + { { 140, 54, 125, 172, 7 }, + { 255, 49, 78, 50, 6 }, + { 227, 91, 230, 195, 1 }, + { 100, 199, 40, 207, 15 }, }, + { { 140, 65, 233, 44, 2 }, + { 186, 182, 6, 2, 4 }, + { 67, 73, 120, 35, 1 }, + { 36, 6, 6, 213, 13 }, }, + { { 140, 152, 59, 175, 12 }, + { 218, 96, 87, 22, 15 }, + { 63, 93, 193, 147, 1 }, + { 246, 142, 160, 101, 11 }, }, + { { 136, 244, 107, 79, 11 }, + { 127, 116, 21, 46, 5 }, + { 223, 45, 98, 241, 1 }, + { 167, 74, 130, 239, 14 }, }, + { { 143, 44, 197, 204, 4 }, + { 187, 161, 64, 107, 10 }, + { 35, 58, 51, 79, 1 }, + { 93, 96, 40, 93, 13 }, }, + { { 141, 55, 126, 115, 3 }, + { 231, 83, 142, 126, 5 }, + { 204, 231, 238, 203, 1 }, + { 167, 231, 28, 174, 7 }, }, + { { 139, 69, 79, 63, 7 }, + { 62, 119, 198, 103, 1 }, + { 239, 207, 42, 45, 1 }, + { 142, 102, 62, 231, 12 }, }, + { { 141, 86, 37, 150, 0 }, + { 218, 5, 136, 98, 7 }, + { 6, 154, 70, 171, 1 }, + { 228, 97, 26, 5, 11 }, }, + { { 139, 191, 24, 57, 2 }, + { 67, 50, 158, 119, 8 }, + { 73, 193, 143, 221, 1 }, + { 30, 231, 148, 204, 2 }, }, + { { 141, 166, 67, 9, 4 }, + { 179, 96, 88, 102, 0 }, + { 41, 12, 38, 91, 1 }, + { 6, 97, 160, 108, 13 }, }, + { { 141, 208, 202, 166, 8 }, + { 234, 196, 23, 66, 3 }, + { 22, 85, 48, 187, 1 }, + { 196, 46, 130, 53, 7 }, }, + { { 139, 246, 152, 79, 4 }, + { 75, 164, 92, 127, 1 }, + { 47, 33, 150, 253, 1 }, + { 143, 227, 162, 93, 2 }, }, + { { 144, 18, 153, 165, 4 }, + { 88, 136, 78, 22, 2 }, + { 42, 89, 148, 128, 9 }, + { 70, 135, 33, 17, 10 }, }, + { { 150, 46, 4, 135, 5 }, + { 141, 9, 72, 39, 11 }, + { 174, 18, 7, 70, 9 }, + { 222, 65, 41, 11, 1 }, }, + { { 146, 92, 163, 212, 0 }, + { 88, 204, 128, 43, 14 }, + { 2, 188, 83, 164, 9 }, + { 125, 64, 19, 49, 10 }, }, + { { 146, 133, 143, 119, 10 }, + { 24, 219, 151, 47, 1 }, + { 94, 239, 26, 20, 9 }, + { 143, 78, 157, 177, 8 }, }, + { { 148, 169, 61, 69, 10 }, + { 153, 27, 21, 30, 12 }, + { 90, 43, 201, 82, 9 }, + { 55, 138, 141, 137, 9 }, }, + { { 145, 32, 243, 51, 2 }, + { 49, 216, 130, 86, 5 }, + { 76, 204, 240, 72, 9 }, + { 166, 164, 17, 184, 12 }, }, + { { 145, 39, 109, 79, 3 }, + { 61, 59, 12, 110, 5 }, + { 207, 43, 110, 72, 9 }, + { 167, 99, 13, 203, 12 }, }, + { { 151, 51, 253, 233, 10 }, + { 241, 187, 15, 95, 6 }, + { 89, 123, 252, 206, 9 }, + { 111, 175, 13, 216, 15 }, }, + { { 151, 72, 243, 129, 3 }, + { 180, 220, 0, 87, 14 }, + { 200, 28, 241, 46, 9 }, + { 126, 160, 3, 178, 13 }, }, + { { 151, 159, 160, 6, 9 }, + { 204, 138, 25, 99, 13 }, + { 150, 0, 95, 158, 9 }, + { 188, 105, 133, 19, 3 }, }, + { { 147, 208, 63, 215, 12 }, + { 88, 77, 213, 95, 7 }, + { 62, 191, 192, 188, 9 }, + { 239, 170, 187, 33, 10 }, }, + { { 149, 252, 208, 110, 0 }, + { 233, 172, 18, 122, 9 }, + { 7, 96, 179, 250, 9 }, + { 149, 228, 131, 89, 7 }, }, + { { 147, 195, 178, 11, 1 }, + { 4, 238, 24, 87, 5 }, + { 141, 4, 220, 60, 9 }, + { 174, 161, 135, 114, 0 }, }, + { { 145, 195, 66, 1, 14 }, + { 32, 94, 89, 70, 0 }, + { 120, 4, 44, 56, 9 }, + { 6, 41, 167, 160, 4 }, }, + { { 158, 10, 212, 208, 9 }, + { 166, 137, 137, 27, 10 }, + { 144, 178, 181, 7, 9 }, + { 93, 137, 25, 22, 5 }, }, + { { 158, 62, 24, 70, 8 }, + { 203, 8, 13, 59, 9 }, + { 22, 33, 135, 199, 9 }, + { 157, 203, 1, 13, 3 }, }, + { { 154, 97, 122, 220, 9 }, + { 47, 110, 133, 27, 6 }, + { 147, 181, 232, 101, 9 }, + { 109, 138, 23, 111, 4 }, }, + { { 152, 200, 135, 101, 13 }, + { 30, 205, 83, 14, 8 }, + { 186, 110, 17, 49, 9 }, + { 23, 12, 171, 55, 8 }, }, + { { 152, 193, 223, 48, 9 }, + { 54, 207, 151, 18, 0 }, + { 144, 207, 184, 49, 9 }, + { 4, 142, 159, 54, 12 }, }, + { { 158, 249, 40, 250, 12 }, + { 195, 46, 215, 11, 15 }, + { 53, 241, 73, 247, 9 }, + { 253, 14, 183, 76, 3 }, }, + { { 158, 240, 171, 22, 1 }, + { 223, 204, 148, 3, 5 }, + { 134, 141, 80, 247, 9 }, + { 172, 2, 147, 63, 11 }, }, + { { 152, 251, 117, 9, 13 }, + { 119, 47, 89, 22, 12 }, + { 185, 10, 237, 241, 9 }, + { 54, 137, 175, 78, 14 }, }, + { { 159, 32, 1, 53, 4 }, + { 155, 8, 194, 71, 0 }, + { 42, 200, 0, 79, 9 }, + { 14, 36, 49, 13, 9 }, }, + { { 153, 18, 120, 7, 12 }, + { 106, 8, 77, 86, 5 }, + { 62, 1, 228, 137, 9 }, + { 166, 171, 33, 5, 6 }, }, + { { 155, 131, 245, 126, 11 }, + { 62, 187, 155, 91, 5 }, + { 215, 234, 252, 29, 9 }, + { 173, 173, 157, 215, 12 }, }, + { { 153, 249, 200, 173, 5 }, + { 111, 174, 86, 70, 10 }, + { 171, 81, 57, 249, 9 }, + { 86, 38, 167, 95, 6 }, }, + { { 157, 228, 46, 236, 2 }, + { 139, 125, 22, 106, 6 }, + { 67, 119, 66, 123, 9 }, + { 101, 102, 139, 237, 1 }, }, + { { 155, 219, 144, 210, 3 }, + { 70, 158, 152, 91, 11 }, + { 196, 176, 157, 189, 9 }, + { 221, 161, 151, 150, 2 }, }, + { { 159, 254, 132, 153, 15 }, + { 199, 189, 217, 103, 10 }, + { 249, 146, 23, 255, 9 }, + { 94, 105, 187, 222, 3 }, }, + { { 162, 66, 140, 215, 9 }, + { 12, 133, 173, 15, 3 }, + { 158, 179, 20, 36, 5 }, + { 207, 11, 90, 19, 0 }, }, + { { 164, 205, 25, 53, 7 }, + { 156, 22, 246, 54, 8 }, + { 234, 201, 139, 50, 5 }, + { 22, 198, 246, 131, 9 }, }, + { { 166, 213, 162, 25, 0 }, + { 192, 230, 176, 39, 4 }, + { 9, 132, 90, 182, 5 }, + { 46, 64, 214, 112, 3 }, }, + { { 160, 223, 192, 176, 6 }, + { 96, 150, 250, 34, 10 }, + { 96, 208, 63, 176, 5 }, + { 84, 69, 246, 144, 6 }, }, + { { 163, 49, 180, 144, 4 }, + { 65, 131, 224, 83, 6 }, + { 32, 146, 216, 204, 5 }, + { 108, 160, 124, 24, 2 }, }, + { { 167, 32, 134, 58, 15 }, + { 133, 241, 227, 67, 1 }, + { 245, 198, 16, 78, 5 }, + { 140, 44, 120, 250, 1 }, }, + { { 167, 56, 45, 40, 13 }, + { 213, 33, 103, 67, 12 }, + { 177, 75, 65, 206, 5 }, + { 60, 46, 104, 74, 11 }, }, + { { 163, 63, 204, 220, 14 }, + { 105, 179, 237, 107, 10 }, + { 115, 179, 63, 204, 5 }, + { 93, 107, 124, 217, 6 }, }, + { { 165, 62, 179, 132, 11 }, + { 221, 208, 41, 114, 14 }, + { 210, 28, 215, 202, 5 }, + { 116, 233, 64, 187, 11 }, }, + { { 161, 112, 26, 189, 7 }, + { 77, 116, 230, 86, 2 }, + { 235, 213, 128, 232, 5 }, + { 70, 166, 114, 235, 2 }, }, + { { 167, 103, 101, 55, 9 }, + { 189, 7, 171, 103, 5 }, + { 158, 202, 110, 110, 5 }, + { 174, 109, 94, 11, 13 }, }, + { { 165, 123, 102, 174, 4 }, + { 233, 103, 106, 66, 15 }, + { 39, 86, 109, 234, 5 }, + { 244, 37, 110, 105, 7 }, }, + { { 161, 213, 29, 147, 2 }, + { 80, 23, 180, 118, 3 }, + { 76, 155, 138, 184, 5 }, + { 198, 226, 222, 128, 10 }, }, + { { 163, 248, 233, 155, 14 }, + { 113, 180, 245, 71, 15 }, + { 125, 153, 113, 252, 5 }, + { 254, 42, 242, 216, 14 }, }, + { { 165, 211, 131, 218, 3 }, + { 212, 246, 184, 74, 3 }, + { 197, 188, 28, 186, 5 }, + { 197, 33, 214, 242, 11 }, }, + { { 165, 214, 235, 188, 6 }, + { 248, 244, 254, 98, 6 }, + { 99, 221, 118, 186, 5 }, + { 100, 103, 242, 241, 15 }, }, + { { 170, 38, 227, 151, 9 }, + { 63, 192, 169, 39, 7 }, + { 158, 156, 118, 69, 5 }, + { 238, 73, 80, 63, 12 }, }, + { { 174, 30, 201, 63, 12 }, + { 250, 160, 239, 39, 9 }, + { 63, 201, 55, 135, 5 }, + { 158, 79, 112, 85, 15 }, }, + { { 172, 119, 184, 237, 2 }, + { 203, 182, 46, 62, 6 }, + { 75, 113, 222, 227, 5 }, + { 103, 199, 70, 221, 3 }, }, + { { 170, 169, 238, 77, 15 }, + { 47, 243, 117, 15, 12 }, + { 251, 39, 121, 85, 5 }, + { 63, 10, 236, 255, 4 }, }, + { { 170, 128, 121, 170, 6 }, + { 50, 48, 118, 19, 7 }, + { 101, 89, 224, 21, 5 }, + { 236, 134, 224, 196, 12 }, }, + { { 174, 194, 96, 202, 1 }, + { 166, 36, 56, 11, 7 }, + { 133, 48, 100, 55, 5 }, + { 237, 1, 194, 70, 5 }, }, + { { 169, 58, 152, 98, 11 }, + { 71, 144, 47, 90, 9 }, + { 212, 97, 149, 201, 5 }, + { 149, 175, 64, 158, 2 }, }, + { { 171, 18, 81, 200, 12 }, + { 114, 32, 105, 91, 2 }, + { 49, 56, 164, 141, 5 }, + { 77, 169, 96, 68, 14 }, }, + { { 173, 102, 219, 216, 13 }, + { 183, 228, 237, 122, 2 }, + { 177, 189, 182, 107, 5 }, + { 69, 235, 114, 126, 13 }, }, + { { 173, 82, 221, 74, 1 }, + { 246, 165, 44, 90, 1 }, + { 133, 43, 180, 171, 5 }, + { 133, 163, 74, 86, 15 }, }, + { { 169, 182, 113, 16, 8 }, + { 115, 0, 185, 114, 4 }, + { 16, 136, 230, 217, 5 }, + { 36, 233, 208, 12, 14 }, }, + { { 173, 186, 226, 53, 0 }, + { 235, 192, 186, 70, 12 }, + { 10, 196, 117, 219, 5 }, + { 54, 37, 208, 61, 7 }, }, + { { 173, 151, 65, 223, 12 }, + { 250, 34, 249, 110, 3 }, + { 63, 184, 46, 155, 5 }, + { 199, 105, 244, 69, 15 }, }, + { { 171, 243, 182, 44, 7 }, + { 79, 247, 122, 83, 4 }, + { 227, 70, 220, 253, 5 }, + { 44, 165, 238, 255, 2 }, }, + { { 182, 34, 108, 113, 12 }, + { 161, 9, 239, 15, 4 }, + { 56, 227, 100, 70, 13 }, + { 47, 15, 121, 8, 5 }, }, + { { 180, 30, 37, 122, 7 }, + { 212, 57, 234, 42, 13 }, + { 229, 234, 71, 130, 13 }, + { 181, 69, 121, 194, 11 }, }, + { { 182, 55, 195, 138, 3 }, + { 245, 250, 40, 35, 3 }, + { 197, 28, 62, 198, 13 }, + { 204, 65, 69, 250, 15 }, }, + { { 176, 76, 212, 55, 0 }, + { 40, 141, 162, 54, 9 }, + { 14, 194, 179, 32, 13 }, + { 150, 196, 91, 17, 4 }, }, + { { 178, 127, 139, 5, 3 }, + { 93, 222, 44, 39, 8 }, + { 202, 13, 31, 228, 13 }, + { 30, 67, 71, 187, 10 }, }, + { { 176, 173, 216, 34, 5 }, + { 37, 138, 118, 50, 9 }, + { 164, 65, 187, 80, 13 }, + { 148, 198, 229, 26, 4 }, }, + { { 180, 155, 130, 43, 11 }, + { 196, 250, 59, 6, 9 }, + { 221, 68, 29, 146, 13 }, + { 150, 13, 197, 242, 3 }, }, + { { 176, 211, 78, 194, 3 }, + { 100, 95, 60, 10, 3 }, + { 196, 55, 44, 176, 13 }, + { 197, 3, 207, 162, 6 }, }, + { { 177, 61, 126, 195, 14 }, + { 97, 91, 101, 126, 15 }, + { 124, 55, 235, 200, 13 }, + { 247, 234, 109, 168, 6 }, }, + { { 177, 6, 164, 99, 14 }, + { 0, 153, 107, 110, 5 }, + { 124, 98, 86, 8, 13 }, + { 167, 109, 105, 144, 0 }, }, + { { 179, 43, 116, 63, 14 }, + { 41, 59, 235, 87, 13 }, + { 127, 194, 237, 76, 13 }, + { 190, 173, 125, 201, 4 }, }, + { { 183, 31, 214, 70, 15 }, + { 236, 219, 105, 123, 9 }, + { 246, 38, 191, 142, 13 }, + { 157, 233, 109, 179, 7 }, }, + { { 177, 110, 241, 244, 5 }, + { 61, 140, 234, 122, 14 }, + { 162, 248, 247, 104, 13 }, + { 117, 229, 115, 27, 12 }, }, + { { 177, 126, 83, 138, 14 }, + { 113, 124, 105, 114, 11 }, + { 117, 28, 167, 232, 13 }, + { 212, 233, 99, 232, 14 }, }, + { { 183, 98, 223, 55, 7 }, + { 189, 221, 238, 87, 1 }, + { 238, 207, 180, 110, 13 }, + { 142, 167, 123, 187, 13 }, }, + { { 177, 169, 248, 148, 9 }, + { 45, 138, 181, 82, 14 }, + { 146, 145, 249, 88, 13 }, + { 116, 170, 213, 27, 4 }, }, + { { 179, 183, 217, 210, 4 }, + { 113, 138, 252, 123, 3 }, + { 36, 185, 190, 220, 13 }, + { 205, 227, 245, 24, 14 }, }, + { { 179, 238, 187, 76, 6 }, + { 25, 252, 124, 123, 12 }, + { 99, 45, 215, 124, 13 }, + { 61, 227, 227, 249, 8 }, }, + { { 184, 44, 165, 82, 4 }, + { 19, 137, 224, 42, 13 }, + { 36, 170, 83, 65, 13 }, + { 181, 64, 121, 28, 8 }, }, + { { 184, 20, 13, 235, 15 }, + { 86, 57, 103, 46, 3 }, + { 253, 123, 2, 129, 13 }, + { 199, 78, 105, 198, 10 }, }, + { { 188, 25, 220, 199, 7 }, + { 238, 155, 100, 30, 11 }, + { 238, 51, 185, 131, 13 }, + { 215, 130, 109, 151, 7 }, }, + { { 188, 84, 38, 185, 6 }, + { 194, 125, 226, 38, 6 }, + { 105, 214, 66, 163, 13 }, + { 102, 68, 123, 228, 3 }, }, + { { 190, 103, 55, 196, 5 }, + { 159, 79, 104, 59, 6 }, + { 162, 62, 206, 103, 13 }, + { 109, 193, 111, 47, 9 }, }, + { { 184, 250, 232, 211, 5 }, + { 103, 140, 252, 14, 15 }, + { 172, 177, 117, 241, 13 }, + { 247, 3, 243, 30, 6 }, }, + { { 190, 194, 26, 55, 8 }, + { 138, 76, 191, 23, 1 }, + { 30, 197, 132, 55, 13 }, + { 142, 143, 211, 37, 1 }, }, + { { 187, 24, 207, 164, 11 }, + { 126, 217, 39, 67, 10 }, + { 210, 95, 49, 141, 13 }, + { 92, 46, 73, 183, 14 }, }, + { { 189, 1, 126, 246, 13 }, + { 174, 75, 231, 90, 7 }, + { 182, 247, 232, 11, 13 }, + { 229, 174, 125, 39, 5 }, }, + { { 189, 34, 47, 210, 7 }, + { 151, 89, 236, 74, 7 }, + { 228, 191, 68, 75, 13 }, + { 229, 35, 121, 174, 9 }, }, + { { 189, 2, 85, 133, 5 }, + { 190, 9, 104, 86, 2 }, + { 170, 26, 164, 11, 13 }, + { 70, 161, 105, 7, 13 }, }, + { { 185, 114, 78, 96, 5 }, + { 103, 77, 110, 74, 0 }, + { 160, 103, 36, 233, 13 }, + { 5, 39, 107, 46, 6 }, }, + { { 189, 119, 92, 22, 15 }, + { 239, 31, 237, 114, 1 }, + { 246, 131, 174, 235, 13 }, + { 132, 235, 127, 143, 7 }, }, + { { 185, 136, 172, 46, 1 }, + { 14, 169, 54, 66, 13 }, + { 135, 67, 81, 25, 13 }, + { 180, 38, 201, 87, 0 }, }, + { { 191, 160, 38, 103, 5 }, + { 143, 73, 114, 79, 5 }, + { 174, 102, 64, 95, 13 }, + { 175, 36, 233, 47, 1 }, }, + { { 185, 130, 150, 168, 6 }, + { 2, 249, 122, 82, 2 }, + { 97, 86, 148, 25, 13 }, + { 68, 165, 233, 244, 0 }, }, + { { 189, 135, 23, 24, 10 }, + { 146, 123, 185, 114, 0 }, + { 81, 142, 142, 27, 13 }, + { 4, 233, 221, 228, 9 }, }, + { { 191, 190, 239, 45, 7 }, + { 255, 249, 126, 103, 12 }, + { 235, 79, 119, 223, 13 }, + { 62, 103, 233, 255, 15 }, }, + { { 189, 250, 159, 0, 7 }, + { 215, 221, 124, 82, 8 }, + { 224, 15, 149, 251, 13 }, + { 20, 163, 235, 190, 11 }, }, + { { 194, 112, 53, 187, 8 }, + { 81, 37, 131, 151, 7 }, + { 29, 218, 192, 228, 3 }, + { 238, 156, 26, 72, 10 }, }, + { { 196, 108, 5, 172, 13 }, + { 157, 37, 67, 162, 10 }, + { 179, 90, 3, 98, 3 }, + { 84, 92, 42, 75, 9 }, }, + { { 196, 121, 84, 220, 3 }, + { 237, 55, 128, 154, 10 }, + { 195, 178, 169, 226, 3 }, + { 85, 144, 30, 203, 7 }, }, + { { 194, 106, 81, 13, 8 }, + { 57, 36, 9, 151, 8 }, + { 27, 8, 165, 100, 3 }, + { 30, 153, 2, 73, 12 }, }, + { { 198, 164, 159, 104, 4 }, + { 145, 225, 86, 187, 0 }, + { 33, 111, 146, 86, 3 }, + { 13, 214, 168, 120, 9 }, }, + { { 192, 158, 44, 235, 4 }, + { 64, 33, 94, 174, 15 }, + { 45, 115, 71, 144, 3 }, + { 247, 87, 168, 64, 2 }, }, + { { 198, 146, 33, 73, 13 }, + { 212, 32, 89, 143, 4 }, + { 185, 40, 68, 150, 3 }, + { 47, 25, 160, 66, 11 }, }, + { { 196, 186, 131, 207, 8 }, + { 217, 224, 25, 142, 11 }, + { 31, 60, 21, 210, 3 }, + { 215, 25, 128, 121, 11 }, }, + { { 198, 231, 133, 67, 11 }, + { 149, 151, 25, 175, 1 }, + { 220, 42, 30, 118, 3 }, + { 143, 89, 142, 154, 9 }, }, + { { 198, 215, 175, 70, 4 }, + { 216, 199, 92, 171, 5 }, + { 38, 47, 94, 182, 3 }, + { 173, 83, 174, 49, 11 }, }, + { { 197, 54, 85, 77, 11 }, + { 253, 49, 9, 254, 0 }, + { 219, 42, 166, 202, 3 }, + { 7, 249, 8, 203, 15 }, }, + { { 195, 147, 255, 13, 7 }, + { 124, 243, 92, 215, 4 }, + { 235, 15, 252, 156, 3 }, + { 46, 179, 172, 243, 14 }, }, + { { 193, 245, 112, 165, 10 }, + { 105, 22, 19, 246, 6 }, + { 90, 80, 234, 248, 3 }, + { 102, 252, 134, 137, 6 }, }, + { { 204, 57, 113, 197, 0 }, + { 251, 2, 0, 158, 14 }, + { 10, 56, 233, 195, 3 }, + { 119, 144, 4, 13, 15 }, }, + { { 200, 2, 137, 73, 2 }, + { 18, 176, 12, 142, 0 }, + { 73, 41, 20, 1, 3 }, + { 7, 19, 0, 212, 8 }, }, + { { 200, 124, 100, 74, 5 }, + { 103, 37, 64, 170, 13 }, + { 165, 34, 99, 225, 3 }, + { 181, 80, 42, 78, 6 }, }, + { { 202, 103, 225, 13, 7 }, + { 63, 182, 72, 167, 4 }, + { 235, 8, 126, 101, 3 }, + { 46, 81, 38, 223, 12 }, }, + { { 200, 153, 231, 66, 2 }, + { 114, 211, 16, 138, 13 }, + { 68, 46, 121, 145, 3 }, + { 181, 16, 140, 180, 14 }, }, + { { 200, 170, 178, 16, 5 }, + { 7, 192, 216, 146, 12 }, + { 160, 132, 213, 81, 3 }, + { 52, 145, 176, 62, 0 }, }, + { { 204, 166, 213, 159, 3 }, + { 191, 177, 152, 182, 3 }, + { 207, 154, 182, 83, 3 }, + { 198, 209, 152, 223, 13 }, }, + { { 202, 229, 134, 251, 14 }, + { 3, 247, 211, 175, 3 }, + { 125, 246, 26, 117, 3 }, + { 207, 92, 190, 252, 0 }, }, + { { 202, 217, 124, 49, 15 }, + { 102, 23, 215, 151, 12 }, + { 248, 195, 233, 181, 3 }, + { 62, 158, 190, 134, 6 }, }, + { { 206, 228, 202, 14, 6 }, + { 171, 244, 84, 163, 1 }, + { 103, 5, 50, 119, 3 }, + { 140, 82, 162, 253, 5 }, }, + { { 203, 45, 164, 50, 12 }, + { 3, 131, 195, 227, 13 }, + { 52, 194, 91, 77, 3 }, + { 188, 124, 60, 28, 0 }, }, + { { 203, 87, 214, 130, 2 }, + { 98, 215, 8, 243, 3 }, + { 68, 22, 190, 173, 3 }, + { 204, 241, 14, 180, 6 }, }, + { { 201, 144, 33, 177, 0 }, + { 82, 0, 146, 198, 6 }, + { 8, 216, 64, 153, 3 }, + { 102, 52, 144, 4, 10 }, }, + { { 201, 130, 95, 176, 10 }, + { 50, 81, 159, 210, 2 }, + { 80, 223, 164, 25, 3 }, + { 68, 191, 152, 164, 12 }, }, + { { 203, 158, 212, 36, 7 }, + { 110, 145, 90, 243, 8 }, + { 226, 66, 183, 157, 3 }, + { 28, 245, 168, 151, 6 }, }, + { { 205, 134, 171, 157, 13 }, + { 158, 224, 221, 230, 6 }, + { 187, 157, 86, 27, 3 }, + { 102, 123, 176, 119, 9 }, }, + { { 201, 201, 176, 119, 5 }, + { 14, 134, 210, 222, 13 }, + { 174, 224, 217, 57, 3 }, + { 183, 180, 182, 23, 0 }, }, + { { 201, 250, 31, 99, 2 }, + { 83, 85, 30, 222, 9 }, + { 76, 111, 133, 249, 3 }, + { 151, 183, 138, 172, 10 }, }, + { { 203, 247, 64, 6, 9 }, + { 111, 6, 25, 227, 1 }, + { 150, 0, 46, 253, 3 }, + { 140, 121, 134, 15, 6 }, }, + { { 205, 218, 13, 28, 15 }, + { 222, 53, 221, 194, 8 }, + { 243, 139, 5, 187, 3 }, + { 20, 59, 186, 199, 11 }, }, + { { 208, 11, 130, 83, 15 }, + { 4, 218, 201, 142, 9 }, + { 252, 164, 29, 0, 11 }, + { 151, 25, 53, 178, 0 }, }, + { { 212, 59, 207, 214, 15 }, + { 253, 219, 205, 138, 11 }, + { 246, 191, 61, 194, 11 }, + { 213, 27, 61, 187, 15 }, }, + { { 214, 98, 171, 209, 11 }, + { 149, 220, 141, 143, 6 }, + { 216, 189, 84, 102, 11 }, + { 111, 27, 19, 186, 9 }, }, + { { 212, 102, 200, 58, 15 }, + { 165, 188, 207, 162, 1 }, + { 245, 193, 54, 98, 11 }, + { 132, 95, 51, 218, 5 }, }, + { { 209, 32, 28, 120, 10 }, + { 1, 57, 135, 218, 0 }, + { 81, 227, 128, 72, 11 }, + { 5, 190, 25, 200, 0 }, }, + { { 211, 57, 65, 195, 9 }, + { 117, 10, 1, 207, 11 }, + { 156, 56, 41, 204, 11 }, + { 223, 56, 5, 10, 14 }, }, + { { 209, 7, 160, 253, 4 }, + { 8, 170, 202, 238, 6 }, + { 43, 240, 94, 8, 11 }, + { 103, 117, 53, 81, 0 }, }, + { { 209, 149, 58, 14, 2 }, + { 72, 122, 20, 242, 5 }, + { 71, 5, 202, 152, 11 }, + { 164, 242, 133, 225, 2 }, }, + { { 215, 189, 66, 29, 13 }, + { 237, 106, 209, 231, 8 }, + { 187, 132, 43, 222, 11 }, + { 30, 120, 181, 107, 7 }, }, + { { 215, 192, 166, 80, 13 }, + { 132, 205, 209, 203, 4 }, + { 176, 166, 80, 62, 11 }, + { 45, 56, 187, 50, 1 }, }, + { { 215, 229, 141, 245, 12 }, + { 153, 143, 215, 239, 2 }, + { 58, 251, 26, 126, 11 }, + { 79, 126, 191, 25, 9 }, }, + { { 215, 197, 83, 246, 7 }, + { 188, 94, 210, 251, 3 }, + { 230, 252, 170, 62, 11 }, + { 205, 244, 183, 163, 13 }, }, + { { 213, 254, 112, 184, 8 }, + { 225, 44, 155, 242, 14 }, + { 17, 208, 231, 250, 11 }, + { 116, 253, 147, 72, 7 }, }, + { { 216, 12, 185, 190, 6 }, + { 26, 184, 198, 178, 15 }, + { 103, 217, 211, 1, 11 }, + { 244, 214, 49, 213, 8 }, }, + { { 222, 52, 186, 37, 6 }, + { 203, 216, 70, 183, 4 }, + { 106, 69, 210, 199, 11 }, + { 46, 214, 33, 189, 3 }, }, + { { 220, 14, 198, 139, 14 }, + { 162, 249, 73, 166, 11 }, + { 125, 22, 55, 3, 11 }, + { 214, 89, 41, 244, 5 }, }, + { { 222, 3, 247, 164, 11 }, + { 190, 219, 11, 147, 6 }, + { 210, 94, 252, 7, 11 }, + { 108, 157, 13, 183, 13 }, }, + { { 218, 93, 11, 138, 2 }, + { 82, 126, 4, 163, 11 }, + { 69, 29, 11, 165, 11 }, + { 220, 82, 7, 228, 10 }, }, + { { 216, 113, 249, 127, 15 }, + { 127, 190, 199, 158, 5 }, + { 255, 233, 248, 225, 11 }, + { 167, 158, 55, 223, 14 }, }, + { { 220, 189, 130, 231, 2 }, + { 203, 218, 18, 174, 11 }, + { 78, 116, 27, 211, 11 }, + { 215, 84, 133, 189, 3 }, }, + { { 220, 176, 90, 229, 5 }, + { 239, 72, 86, 158, 2 }, + { 170, 117, 160, 211, 11 }, + { 71, 150, 161, 47, 7 }, }, + { { 218, 147, 222, 80, 0 }, + { 98, 203, 156, 155, 0 }, + { 0, 167, 188, 149, 11 }, + { 13, 147, 157, 52, 6 }, }, + { { 218, 183, 241, 191, 8 }, + { 123, 170, 155, 183, 7 }, + { 31, 216, 254, 213, 11 }, + { 238, 221, 149, 93, 14 }, }, + { { 222, 159, 2, 50, 14 }, + { 194, 90, 219, 163, 9 }, + { 116, 196, 15, 151, 11 }, + { 156, 93, 181, 164, 3 }, }, + { { 218, 196, 13, 85, 0 }, + { 26, 13, 148, 175, 0 }, + { 10, 171, 2, 53, 11 }, + { 15, 82, 155, 5, 8 }, }, + { { 220, 197, 171, 128, 14 }, + { 146, 222, 85, 162, 6 }, + { 112, 29, 90, 51, 11 }, + { 100, 90, 167, 180, 9 }, }, + { { 218, 194, 178, 58, 10 }, + { 2, 252, 155, 147, 5 }, + { 85, 196, 212, 53, 11 }, + { 172, 157, 147, 244, 0 }, }, + { { 218, 218, 88, 253, 0 }, + { 106, 44, 158, 159, 10 }, + { 11, 241, 165, 181, 11 }, + { 95, 151, 147, 69, 6 }, }, + { { 217, 37, 142, 168, 1 }, + { 7, 235, 6, 226, 2 }, + { 129, 87, 26, 73, 11 }, + { 68, 118, 13, 126, 0 }, }, + { { 221, 5, 118, 59, 14 }, + { 162, 123, 195, 246, 5 }, + { 125, 198, 234, 11, 11 }, + { 166, 252, 61, 228, 5 }, }, + { { 221, 46, 153, 8, 12 }, + { 147, 168, 77, 242, 8 }, + { 49, 9, 151, 75, 11 }, + { 20, 251, 33, 92, 9 }, }, + { { 217, 85, 104, 199, 5 }, + { 110, 14, 68, 238, 7 }, + { 174, 49, 106, 169, 11 }, + { 231, 114, 39, 7, 6 }, }, + { { 219, 80, 109, 76, 3 }, + { 126, 61, 4, 203, 4 }, + { 195, 43, 96, 173, 11 }, + { 45, 50, 11, 199, 14 }, }, + { { 223, 104, 170, 56, 8 }, + { 131, 236, 135, 195, 12 }, + { 17, 197, 81, 111, 11 }, + { 60, 62, 19, 124, 1 }, }, + { { 219, 66, 135, 167, 5 }, + { 30, 205, 74, 199, 3 }, + { 174, 94, 20, 45, 11 }, + { 206, 53, 43, 55, 8 }, }, + { { 219, 129, 40, 179, 13 }, + { 6, 10, 215, 199, 7 }, + { 188, 209, 72, 29, 11 }, + { 238, 62, 181, 6, 0 }, }, + { { 217, 163, 194, 250, 13 }, + { 39, 234, 219, 202, 3 }, + { 181, 244, 60, 89, 11 }, + { 197, 61, 181, 126, 4 }, }, + { { 221, 179, 56, 211, 8 }, + { 195, 10, 157, 222, 7 }, + { 28, 177, 204, 219, 11 }, + { 231, 187, 149, 12, 3 }, }, + { { 217, 202, 123, 155, 1 }, + { 54, 108, 156, 214, 15 }, + { 141, 157, 229, 57, 11 }, + { 246, 179, 147, 102, 12 }, }, + { { 219, 246, 158, 176, 7 }, + { 71, 221, 222, 243, 2 }, + { 224, 215, 150, 253, 11 }, + { 76, 247, 187, 190, 2 }, }, + { { 226, 8, 108, 175, 8 }, + { 40, 33, 39, 135, 15 }, + { 31, 83, 97, 4, 7 }, + { 254, 30, 72, 65, 4 }, }, + { { 224, 27, 171, 150, 4 }, + { 88, 194, 236, 130, 15 }, + { 38, 157, 93, 128, 7 }, + { 244, 19, 116, 49, 10 }, }, + { { 224, 72, 115, 243, 0 }, + { 48, 68, 162, 158, 15 }, + { 12, 252, 225, 32, 7 }, + { 247, 148, 82, 32, 12 }, }, + { { 228, 117, 169, 91, 14 }, + { 209, 182, 229, 174, 5 }, + { 125, 169, 90, 226, 7 }, + { 167, 90, 118, 216, 11 }, }, + { { 230, 128, 236, 116, 9 }, + { 172, 129, 183, 139, 4 }, + { 146, 227, 112, 22, 7 }, + { 45, 30, 216, 19, 5 }, }, + { { 226, 175, 118, 72, 7 }, + { 37, 115, 120, 187, 12 }, + { 225, 38, 239, 84, 7 }, + { 61, 209, 236, 234, 4 }, }, + { { 224, 162, 200, 17, 13 }, + { 37, 128, 253, 134, 0 }, + { 184, 129, 52, 80, 7 }, + { 6, 27, 240, 26, 4 }, }, + { { 226, 203, 113, 80, 12 }, + { 48, 6, 249, 155, 12 }, + { 48, 168, 237, 52, 7 }, + { 61, 153, 246, 0, 12 }, }, + { { 224, 210, 73, 197, 15 }, + { 124, 20, 125, 142, 2 }, + { 250, 57, 36, 176, 7 }, + { 71, 27, 226, 131, 14 }, }, + { { 225, 12, 194, 130, 9 }, + { 36, 192, 33, 226, 11 }, + { 148, 20, 51, 8, 7 }, + { 212, 120, 64, 50, 4 }, }, + { { 225, 127, 68, 52, 3 }, + { 109, 23, 170, 226, 8 }, + { 194, 194, 47, 232, 7 }, + { 20, 117, 94, 139, 6 }, }, + { { 231, 173, 69, 177, 14 }, + { 177, 19, 243, 231, 10 }, + { 120, 218, 43, 94, 7 }, + { 94, 124, 252, 136, 13 }, }, + { { 225, 182, 90, 159, 9 }, + { 109, 96, 189, 246, 3 }, + { 159, 149, 166, 216, 7 }, + { 198, 251, 208, 107, 6 }, }, + { { 231, 252, 32, 65, 12 }, + { 193, 4, 113, 239, 12 }, + { 56, 32, 67, 254, 7 }, + { 63, 120, 226, 8, 3 }, }, + { { 238, 51, 65, 56, 1 }, + { 247, 34, 170, 131, 0 }, + { 129, 200, 44, 199, 7 }, + { 12, 21, 84, 78, 15 }, }, + { { 238, 124, 54, 51, 4 }, + { 195, 69, 226, 183, 13 }, + { 44, 198, 195, 231, 7 }, + { 190, 212, 122, 44, 3 }, }, + { { 236, 112, 123, 248, 10 }, + { 243, 116, 167, 154, 6 }, + { 81, 253, 224, 227, 7 }, + { 101, 158, 82, 236, 15 }, }, + { { 232, 123, 59, 230, 14 }, + { 91, 86, 111, 154, 15 }, + { 118, 125, 205, 225, 7 }, + { 245, 159, 102, 173, 10 }, }, + { { 232, 149, 232, 57, 1 }, + { 102, 162, 182, 166, 4 }, + { 137, 193, 122, 145, 7 }, + { 38, 86, 212, 86, 6 }, }, + { { 238, 172, 9, 125, 5 }, + { 159, 32, 246, 175, 8 }, + { 171, 233, 3, 87, 7 }, + { 31, 86, 240, 79, 9 }, }, + { { 236, 161, 201, 55, 4 }, + { 187, 130, 246, 134, 1 }, + { 46, 201, 56, 83, 7 }, + { 134, 22, 244, 29, 13 }, }, + { { 232, 151, 189, 197, 5 }, + { 94, 131, 124, 190, 6 }, + { 170, 59, 222, 145, 7 }, + { 103, 211, 236, 23, 10 }, }, + { { 232, 204, 18, 29, 0 }, + { 10, 100, 176, 182, 8 }, + { 11, 132, 131, 49, 7 }, + { 22, 208, 210, 101, 0 }, }, + { { 238, 193, 29, 105, 8 }, + { 146, 39, 55, 159, 0 }, + { 25, 107, 136, 55, 7 }, + { 15, 158, 206, 68, 9 }, }, + { { 237, 7, 255, 219, 10 }, + { 178, 243, 173, 254, 7 }, + { 93, 191, 254, 11, 7 }, + { 231, 251, 92, 244, 13 }, }, + { { 237, 105, 243, 54, 11 }, + { 191, 214, 163, 210, 13 }, + { 214, 204, 249, 107, 7 }, + { 180, 188, 86, 191, 13 }, }, + { { 237, 118, 20, 181, 12 }, + { 203, 5, 235, 246, 2 }, + { 58, 210, 134, 235, 7 }, + { 70, 253, 122, 13, 3 }, }, + { { 239, 132, 33, 209, 7 }, + { 150, 16, 240, 239, 6 }, + { 232, 184, 66, 31, 7 }, + { 111, 112, 240, 134, 9 }, }, + { { 235, 245, 218, 120, 2 }, + { 99, 246, 182, 251, 0 }, + { 65, 229, 186, 253, 7 }, + { 13, 246, 214, 252, 6 }, }, + { { 233, 249, 229, 214, 13 }, + { 127, 135, 241, 202, 15 }, + { 182, 186, 121, 249, 7 }, + { 245, 56, 254, 31, 14 }, }, + { { 235, 211, 15, 145, 9 }, + { 86, 71, 189, 199, 2 }, + { 152, 159, 12, 189, 7 }, + { 78, 59, 222, 38, 10 }, }, + { { 233, 219, 241, 32, 9 }, + { 118, 134, 59, 210, 12 }, + { 144, 72, 253, 185, 7 }, + { 52, 189, 198, 22, 14 }, }, + { { 244, 41, 139, 109, 8 }, + { 153, 234, 39, 142, 8 }, + { 27, 109, 25, 66, 15 }, + { 23, 30, 69, 121, 9 }, }, + { { 246, 10, 52, 34, 5 }, + { 132, 9, 106, 147, 13 }, + { 164, 66, 197, 6, 15 }, + { 188, 149, 105, 2, 1 }, }, + { { 244, 88, 85, 155, 1 }, + { 244, 45, 160, 150, 11 }, + { 141, 154, 161, 162, 15 }, + { 214, 144, 91, 66, 15 }, }, + { { 242, 118, 55, 97, 3 }, + { 85, 93, 42, 191, 4 }, + { 200, 110, 198, 228, 15 }, + { 47, 213, 75, 170, 10 }, }, + { { 244, 75, 10, 224, 6 }, + { 128, 94, 110, 138, 10 }, + { 96, 117, 13, 34, 15 }, + { 85, 23, 103, 160, 1 }, }, + { { 246, 110, 129, 75, 12 }, + { 145, 172, 105, 175, 9 }, + { 61, 40, 23, 102, 15 }, + { 159, 89, 99, 88, 9 }, }, + { { 246, 67, 29, 76, 6 }, + { 152, 63, 108, 155, 0 }, + { 99, 43, 140, 38, 15 }, + { 13, 147, 111, 193, 9 }, }, + { { 244, 87, 34, 238, 1 }, + { 204, 110, 42, 170, 7 }, + { 135, 116, 78, 162, 15 }, + { 229, 85, 71, 99, 3 }, }, + { { 244, 153, 83, 229, 11 }, + { 252, 90, 51, 158, 10 }, + { 218, 124, 169, 146, 15 }, + { 87, 156, 197, 163, 15 }, }, + { { 240, 252, 47, 27, 5 }, + { 85, 109, 244, 166, 13 }, + { 173, 143, 67, 240, 15 }, + { 182, 82, 251, 106, 10 }, }, + { { 247, 9, 30, 9, 12 }, + { 128, 107, 101, 215, 8 }, + { 57, 7, 137, 14, 15 }, + { 30, 186, 109, 96, 1 }, }, + { { 247, 38, 87, 194, 0 }, + { 177, 73, 40, 251, 3 }, + { 4, 62, 166, 78, 15 }, + { 205, 241, 73, 40, 13 }, }, + { { 241, 133, 27, 198, 13 }, + { 28, 74, 117, 250, 3 }, + { 182, 61, 138, 24, 15 }, + { 197, 250, 229, 35, 8 }, }, + { { 243, 136, 98, 232, 1 }, + { 36, 104, 50, 203, 14 }, + { 129, 116, 97, 28, 15 }, + { 125, 52, 193, 98, 4 }, }, + { { 247, 169, 183, 234, 6 }, + { 145, 251, 114, 219, 15 }, + { 101, 126, 217, 94, 15 }, + { 253, 180, 237, 248, 9 }, }, + { { 247, 138, 138, 92, 2 }, + { 136, 248, 188, 203, 8 }, + { 67, 165, 21, 30, 15 }, + { 29, 51, 209, 241, 1 }, }, + { { 243, 237, 194, 21, 2 }, + { 41, 222, 176, 231, 8 }, + { 74, 132, 59, 124, 15 }, + { 30, 112, 215, 185, 4 }, }, + { { 245, 245, 148, 242, 4 }, + { 193, 143, 242, 250, 3 }, + { 36, 242, 154, 250, 15 }, + { 197, 244, 255, 24, 3 }, }, + { { 248, 15, 62, 15, 4 }, + { 10, 107, 108, 182, 13 }, + { 47, 7, 207, 1, 15 }, + { 182, 211, 109, 101, 0 }, }, + { { 252, 190, 99, 125, 14 }, + { 251, 120, 251, 174, 12 }, + { 123, 236, 103, 211, 15 }, + { 55, 93, 241, 237, 15 }, }, + { { 248, 237, 33, 73, 11 }, + { 23, 62, 49, 174, 12 }, + { 217, 40, 75, 113, 15 }, + { 55, 88, 199, 206, 8 }, }, + { { 250, 216, 194, 128, 14 }, + { 98, 220, 113, 131, 10 }, + { 112, 20, 49, 181, 15 }, + { 92, 24, 227, 180, 6 }, }, + { { 254, 229, 5, 156, 6 }, + { 155, 63, 240, 163, 2 }, + { 99, 154, 10, 119, 15 }, + { 76, 80, 255, 205, 9 }, }, + { { 254, 196, 253, 27, 8 }, + { 178, 173, 181, 183, 5 }, + { 29, 139, 242, 55, 15 }, + { 174, 218, 219, 84, 13 }, }, + { { 250, 210, 225, 31, 4 }, + { 122, 172, 248, 135, 5 }, + { 47, 136, 116, 181, 15 }, + { 174, 17, 243, 85, 14 }, }, + { { 252, 195, 230, 115, 2 }, + { 162, 223, 186, 142, 5 }, + { 76, 230, 124, 51, 15 }, + { 167, 21, 223, 180, 5 }, }, + { { 249, 63, 162, 110, 11 }, + { 79, 250, 43, 234, 13 }, + { 215, 100, 95, 201, 15 }, + { 181, 125, 69, 255, 2 }, }, + { { 251, 86, 194, 94, 3 }, + { 110, 252, 168, 235, 1 }, + { 199, 164, 54, 173, 15 }, + { 141, 113, 83, 247, 6 }, }, + { { 255, 78, 140, 162, 15 }, + { 134, 157, 111, 227, 11 }, + { 244, 83, 23, 47, 15 }, + { 220, 127, 107, 150, 1 }, }, + { { 255, 79, 112, 226, 4 }, + { 162, 14, 106, 251, 15 }, + { 36, 112, 239, 47, 15 }, + { 253, 245, 103, 4, 5 }, }, + { { 249, 177, 42, 52, 9 }, + { 79, 74, 183, 194, 4 }, + { 146, 197, 72, 217, 15 }, + { 36, 62, 213, 47, 2 }, }, + { { 255, 135, 146, 120, 1 }, + { 134, 234, 186, 251, 0 }, + { 129, 228, 158, 31, 15 }, + { 13, 245, 213, 118, 1 }, }, + { { 249, 195, 188, 9, 10 }, + { 2, 191, 61, 214, 4 }, + { 89, 3, 220, 57, 15 }, + { 38, 187, 207, 212, 0 }, }, + { { 255, 202, 25, 209, 12 }, + { 146, 12, 253, 223, 10 }, + { 56, 185, 133, 63, 15 }, + { 95, 187, 243, 4, 9 }, }, + { { 255, 239, 23, 165, 10 }, + { 155, 95, 59, 247, 10 }, + { 90, 94, 143, 127, 15 }, + { 94, 253, 207, 173, 9 }, }, + { { 253, 254, 218, 140, 1 }, + { 239, 236, 60, 242, 10 }, + { 131, 21, 183, 251, 15 }, + { 84, 243, 195, 127, 7 }, }, + { { 6, 66, 233, 9, 7 }, + { 180, 180, 76, 5, 4 }, + { 233, 9, 116, 38, 0 }, + { 42, 3, 34, 210, 13 }, }, + { { 3, 36, 36, 70, 13 }, + { 13, 1, 65, 105, 5 }, + { 182, 34, 66, 76, 0 }, + { 169, 104, 40, 11, 0 }, }, + { { 7, 94, 92, 135, 12 }, + { 232, 5, 77, 117, 11 }, + { 62, 19, 167, 174, 0 }, + { 218, 235, 42, 1, 7 }, }, + { { 7, 136, 66, 250, 6 }, + { 160, 112, 210, 73, 11 }, + { 101, 244, 33, 30, 0 }, + { 217, 36, 176, 224, 5 }, }, + { { 10, 7, 178, 27, 11 }, + { 6, 242, 137, 53, 5 }, + { 221, 132, 222, 5, 0 }, + { 170, 201, 20, 246, 0 }, }, + { { 16, 148, 220, 241, 14 }, + { 96, 153, 215, 60, 2 }, + { 120, 243, 178, 144, 8 }, + { 67, 206, 185, 144, 6 }, }, + { { 22, 162, 90, 176, 15 }, + { 165, 88, 223, 17, 2 }, + { 240, 213, 164, 86, 8 }, + { 72, 143, 177, 170, 5 }, }, + { { 16, 253, 203, 109, 14 }, + { 121, 254, 87, 44, 8 }, + { 123, 109, 59, 240, 8 }, + { 19, 78, 167, 249, 14 }, }, + { { 17, 52, 172, 162, 12 }, + { 65, 137, 71, 96, 7 }, + { 52, 83, 82, 200, 8 }, + { 224, 110, 41, 24, 2 }, }, + { { 17, 166, 62, 16, 1 }, + { 5, 73, 156, 112, 4 }, + { 128, 135, 198, 88, 8 }, + { 32, 227, 153, 42, 0 }, }, + { { 30, 88, 44, 226, 2 }, + { 194, 29, 6, 9, 15 }, + { 68, 115, 65, 167, 8 }, + { 249, 6, 11, 132, 3 }, }, + { { 24, 99, 58, 139, 0 }, + { 3, 110, 12, 20, 7 }, + { 13, 21, 204, 97, 8 }, + { 226, 131, 7, 108, 0 }, }, + { { 26, 99, 1, 47, 13 }, + { 31, 46, 75, 5, 1 }, + { 191, 72, 12, 101, 8 }, + { 138, 13, 39, 79, 8 }, }, + { { 26, 241, 71, 206, 15 }, + { 127, 127, 81, 9, 3 }, + { 247, 62, 40, 245, 8 }, + { 201, 8, 175, 239, 14 }, }, + { { 25, 108, 177, 139, 1 }, + { 23, 172, 0, 116, 15 }, + { 141, 24, 211, 105, 8 }, + { 242, 224, 3, 94, 8 }, }, + { { 29, 149, 207, 92, 12 }, + { 250, 235, 213, 104, 0 }, + { 51, 175, 58, 155, 8 }, + { 1, 106, 189, 117, 15 }, }, + { { 25, 158, 56, 146, 2 }, + { 66, 24, 156, 112, 15 }, + { 68, 145, 199, 153, 8 }, + { 240, 227, 145, 132, 2 }, }, + { { 31, 252, 86, 35, 10 }, + { 227, 93, 19, 117, 9 }, + { 92, 70, 163, 255, 8 }, + { 154, 236, 139, 172, 7 }, }, + { { 36, 27, 41, 179, 3 }, + { 212, 18, 174, 4, 15 }, + { 204, 217, 77, 130, 4 }, + { 242, 7, 84, 130, 11 }, }, + { { 38, 231, 136, 133, 0 }, + { 137, 134, 60, 37, 2 }, + { 10, 17, 30, 118, 4 }, + { 74, 67, 198, 25, 1 }, }, + { { 33, 7, 54, 188, 1 }, + { 12, 99, 170, 112, 6 }, + { 131, 214, 206, 8, 4 }, + { 96, 229, 92, 99, 0 }, }, + { { 40, 77, 70, 86, 10 }, + { 42, 87, 161, 40, 9 }, + { 86, 166, 43, 33, 4 }, + { 145, 72, 94, 165, 4 }, }, + { { 46, 236, 227, 69, 8 }, + { 187, 196, 49, 45, 12 }, + { 26, 44, 115, 119, 4 }, + { 59, 72, 194, 61, 13 }, }, + { { 45, 79, 225, 77, 4 }, + { 186, 166, 104, 108, 12 }, + { 43, 40, 127, 43, 4 }, + { 51, 97, 102, 85, 13 }, }, + { { 45, 138, 40, 60, 7 }, + { 142, 48, 254, 64, 12 }, + { 227, 193, 69, 27, 4 }, + { 48, 39, 240, 199, 1 }, }, + { { 52, 248, 62, 55, 1 }, + { 205, 77, 182, 20, 13 }, + { 142, 199, 193, 242, 12 }, + { 178, 134, 219, 43, 3 }, }, + { { 56, 99, 93, 125, 8 }, + { 59, 47, 175, 28, 0 }, + { 27, 235, 172, 97, 12 }, + { 3, 143, 95, 77, 12 }, }, + { { 63, 162, 28, 196, 12 }, + { 139, 9, 125, 89, 2 }, + { 50, 51, 132, 95, 12 }, + { 73, 171, 233, 13, 1 }, }, + { { 66, 221, 151, 174, 14 }, + { 88, 247, 83, 177, 11 }, + { 119, 94, 155, 180, 2 }, + { 216, 220, 174, 241, 10 }, }, + { { 68, 223, 18, 214, 11 }, + { 204, 86, 153, 184, 11 }, + { 214, 180, 143, 178, 2 }, + { 209, 217, 150, 163, 3 }, }, + { { 65, 58, 205, 236, 8 }, + { 121, 161, 15, 200, 10 }, + { 19, 123, 53, 200, 2 }, + { 81, 63, 8, 89, 14 }, }, + { { 65, 192, 219, 73, 4 }, + { 48, 228, 84, 220, 0 }, + { 41, 45, 176, 56, 2 }, + { 3, 178, 162, 112, 12 }, }, + { { 76, 180, 0, 200, 13 }, + { 199, 32, 81, 168, 2 }, + { 177, 48, 2, 211, 2 }, + { 65, 88, 160, 78, 3 }, }, + { { 73, 35, 72, 149, 2 }, + { 43, 18, 140, 196, 2 }, + { 74, 145, 44, 73, 2 }, + { 66, 51, 20, 141, 4 }, }, + { { 82, 55, 185, 92, 6 }, + { 89, 186, 204, 185, 4 }, + { 99, 169, 222, 196, 10 }, + { 41, 211, 53, 217, 10 }, }, + { { 82, 81, 56, 221, 10 }, + { 72, 62, 133, 157, 6 }, + { 91, 177, 200, 164, 10 }, + { 107, 154, 23, 193, 2 }, }, + { { 94, 113, 146, 65, 3 }, + { 199, 222, 0, 157, 0 }, + { 200, 36, 152, 231, 10 }, + { 11, 144, 7, 190, 3 }, }, + { { 92, 178, 113, 235, 13 }, + { 247, 40, 91, 156, 7 }, + { 189, 120, 228, 211, 10 }, + { 227, 157, 161, 78, 15 }, }, + { { 91, 238, 55, 54, 6 }, + { 27, 93, 218, 241, 13 }, + { 102, 206, 199, 125, 10 }, + { 184, 245, 187, 173, 8 }, }, + { { 96, 5, 132, 21, 2 }, + { 8, 147, 160, 164, 0 }, + { 74, 130, 26, 0, 6 }, + { 2, 80, 92, 145, 0 }, }, + { { 100, 8, 128, 51, 8 }, + { 128, 128, 163, 132, 9 }, + { 28, 192, 17, 2, 6 }, + { 146, 28, 80, 16, 1 }, }, + { { 110, 40, 135, 140, 3 }, + { 159, 241, 32, 129, 10 }, + { 195, 30, 17, 71, 6 }, + { 88, 16, 72, 255, 9 }, }, + { { 108, 85, 84, 181, 7 }, + { 238, 23, 226, 180, 2 }, + { 234, 210, 170, 163, 6 }, + { 66, 212, 126, 135, 7 }, }, + { { 104, 186, 254, 70, 1 }, + { 111, 193, 60, 152, 13 }, + { 134, 39, 245, 209, 6 }, + { 177, 147, 200, 63, 6 }, }, + { { 104, 179, 85, 202, 7 }, + { 119, 51, 120, 152, 3 }, + { 229, 58, 172, 209, 6 }, + { 193, 145, 236, 206, 14 }, }, + { { 117, 156, 155, 102, 9 }, + { 220, 200, 55, 248, 9 }, + { 150, 109, 147, 154, 14 }, + { 145, 254, 193, 51, 11 }, }, + { { 124, 11, 200, 10, 0 }, + { 162, 170, 44, 128, 9 }, + { 5, 1, 61, 3, 14 }, + { 144, 19, 69, 84, 5 }, }, + { { 126, 89, 104, 196, 9 }, + { 238, 14, 37, 137, 14 }, + { 146, 49, 105, 167, 14 }, + { 121, 26, 71, 7, 7 }, }, + { { 120, 111, 61, 52, 5 }, + { 31, 15, 238, 176, 12 }, + { 162, 203, 207, 97, 14 }, + { 48, 215, 127, 15, 8 }, }, + { { 120, 193, 191, 255, 10 }, + { 26, 255, 183, 156, 7 }, + { 95, 255, 216, 49, 14 }, + { 227, 158, 223, 245, 8 }, }, + { { 122, 218, 185, 69, 10 }, + { 90, 156, 61, 157, 12 }, + { 90, 41, 213, 181, 14 }, + { 59, 155, 195, 149, 10 }, }, + { { 128, 60, 123, 156, 0 }, + { 121, 96, 132, 50, 14 }, + { 3, 157, 227, 192, 1 }, + { 116, 194, 16, 105, 14 }, }, + { { 135, 20, 88, 249, 7 }, + { 228, 48, 198, 127, 2 }, + { 233, 241, 162, 142, 1 }, + { 79, 230, 48, 194, 7 }, }, + { { 131, 27, 5, 165, 13 }, + { 92, 3, 75, 71, 10 }, + { 186, 90, 13, 140, 1 }, + { 94, 45, 44, 3, 10 }, }, + { { 136, 59, 180, 103, 4 }, + { 75, 131, 74, 30, 13 }, + { 46, 98, 221, 193, 1 }, + { 183, 133, 44, 29, 2 }, }, + { { 140, 169, 136, 156, 0 }, + { 139, 162, 148, 2, 10 }, + { 3, 145, 25, 83, 1 }, + { 84, 2, 148, 93, 1 }, }, + { { 142, 177, 3, 26, 11 }, + { 215, 114, 145, 3, 1 }, + { 213, 140, 8, 215, 1 }, + { 140, 8, 148, 238, 11 }, }, + { { 143, 39, 191, 54, 4 }, + { 155, 195, 206, 115, 5 }, + { 38, 207, 222, 79, 1 }, + { 172, 231, 60, 61, 9 }, }, + { { 143, 22, 179, 202, 0 }, + { 210, 224, 8, 123, 7 }, + { 5, 60, 214, 143, 1 }, + { 237, 225, 0, 116, 11 }, }, + { { 148, 24, 222, 74, 0 }, + { 224, 233, 4, 26, 9 }, + { 5, 39, 177, 130, 9 }, + { 149, 130, 9, 112, 7 }, }, + { { 147, 69, 36, 197, 0 }, + { 8, 15, 0, 111, 6 }, + { 10, 50, 74, 44, 9 }, + { 111, 96, 15, 1, 0 }, }, + { { 156, 253, 156, 216, 5 }, + { 199, 175, 212, 58, 10 }, + { 161, 179, 155, 243, 9 }, + { 85, 194, 191, 94, 3 }, }, + { { 155, 34, 55, 120, 13 }, + { 23, 105, 203, 91, 4 }, + { 177, 238, 196, 77, 9 }, + { 45, 173, 57, 110, 8 }, }, + { { 153, 250, 163, 209, 6 }, + { 83, 220, 216, 78, 14 }, + { 104, 188, 85, 249, 9 }, + { 119, 33, 179, 188, 10 }, }, + { { 162, 159, 28, 84, 3 }, + { 76, 19, 188, 59, 8 }, + { 194, 163, 143, 148, 5 }, + { 29, 195, 220, 131, 2 }, }, + { { 172, 79, 91, 26, 11 }, + { 182, 118, 173, 50, 9 }, + { 213, 141, 175, 35, 5 }, + { 148, 203, 86, 230, 13 }, }, + { { 172, 191, 109, 54, 6 }, + { 251, 19, 254, 34, 13 }, + { 102, 203, 111, 211, 5 }, + { 180, 71, 252, 141, 15 }, }, + { { 169, 185, 76, 64, 8 }, + { 99, 3, 53, 74, 8 }, + { 16, 35, 41, 217, 5 }, + { 21, 42, 204, 12, 6 }, }, + { { 182, 40, 233, 2, 12 }, + { 177, 136, 101, 3, 13 }, + { 52, 9, 113, 70, 13 }, + { 188, 10, 97, 24, 13 }, }, + { { 180, 109, 218, 222, 0 }, + { 169, 238, 164, 58, 11 }, + { 7, 181, 187, 98, 13 }, + { 213, 194, 87, 121, 5 }, }, + { { 178, 110, 246, 10, 1 }, + { 37, 237, 40, 51, 13 }, + { 133, 6, 247, 100, 13 }, + { 188, 193, 75, 122, 4 }, }, + { { 179, 37, 153, 105, 14 }, + { 17, 186, 103, 127, 0 }, + { 121, 105, 154, 76, 13 }, + { 15, 238, 101, 216, 8 }, }, + { { 179, 80, 40, 17, 5 }, + { 68, 12, 228, 71, 4 }, + { 168, 129, 64, 172, 13 }, + { 46, 34, 115, 2, 2 }, }, + { { 190, 28, 27, 59, 6 }, + { 210, 120, 230, 55, 9 }, + { 109, 205, 131, 135, 13 }, + { 158, 198, 113, 228, 11 }, }, + { { 188, 127, 85, 99, 14 }, + { 243, 31, 107, 62, 9 }, + { 124, 106, 175, 227, 13 }, + { 151, 205, 111, 140, 15 }, }, + { { 188, 175, 230, 141, 5 }, + { 175, 235, 120, 38, 14 }, + { 171, 22, 127, 83, 13 }, + { 118, 65, 237, 127, 5 }, }, + { { 189, 37, 146, 140, 4 }, + { 139, 234, 96, 114, 2 }, + { 35, 20, 154, 75, 13 }, + { 68, 224, 101, 125, 1 }, }, + { { 196, 27, 214, 183, 1 }, + { 236, 195, 138, 150, 11 }, + { 142, 214, 189, 130, 3 }, + { 214, 149, 28, 51, 7 }, }, + { { 198, 99, 190, 252, 14 }, + { 137, 247, 207, 155, 6 }, + { 115, 247, 220, 102, 3 }, + { 109, 159, 62, 249, 1 }, }, + { { 198, 86, 130, 19, 13 }, + { 196, 196, 201, 167, 1 }, + { 188, 132, 22, 166, 3 }, + { 142, 89, 50, 50, 3 }, }, + { { 198, 139, 73, 36, 10 }, + { 184, 18, 31, 131, 8 }, + { 82, 73, 45, 22, 3 }, + { 28, 31, 132, 129, 13 }, }, + { { 193, 61, 206, 190, 2 }, + { 105, 243, 134, 226, 11 }, + { 71, 215, 59, 200, 3 }, + { 212, 118, 28, 249, 6 }, }, + { { 197, 25, 109, 109, 1 }, + { 252, 35, 6, 206, 12 }, + { 139, 107, 105, 138, 3 }, + { 55, 54, 12, 67, 15 }, }, + { { 199, 77, 61, 239, 11 }, + { 156, 55, 7, 255, 15 }, + { 223, 123, 203, 46, 3 }, + { 255, 254, 14, 195, 9 }, }, + { { 193, 90, 43, 220, 9 }, + { 92, 100, 141, 202, 14 }, + { 147, 189, 69, 168, 3 }, + { 117, 59, 18, 99, 10 }, }, + { { 202, 35, 114, 33, 12 }, + { 35, 66, 75, 151, 4 }, + { 56, 68, 236, 69, 3 }, + { 46, 157, 36, 44, 4 }, }, + { { 207, 199, 244, 213, 9 }, + { 174, 135, 153, 255, 6 }, + { 154, 178, 254, 63, 3 }, + { 111, 249, 158, 23, 5 }, }, + { { 208, 49, 39, 226, 6 }, + { 81, 91, 66, 138, 7 }, + { 100, 126, 72, 192, 11 }, + { 229, 20, 45, 168, 10 }, }, + { { 212, 119, 84, 14, 0 }, + { 233, 47, 8, 178, 1 }, + { 7, 2, 174, 226, 11 }, + { 132, 209, 15, 73, 7 }, }, + { { 209, 134, 49, 90, 2 }, + { 16, 56, 152, 250, 5 }, + { 69, 168, 198, 24, 11 }, + { 165, 241, 145, 192, 8 }, }, + { { 219, 76, 100, 122, 11 }, + { 38, 61, 131, 235, 13 }, + { 213, 226, 99, 45, 11 }, + { 189, 124, 27, 198, 4 }, }, + { { 219, 132, 135, 144, 8 }, + { 18, 201, 145, 227, 2 }, + { 16, 158, 18, 29, 11 }, + { 76, 120, 153, 52, 8 }, }, + { { 223, 222, 6, 112, 0 }, + { 194, 77, 154, 235, 8 }, + { 0, 230, 7, 191, 11 }, + { 29, 117, 155, 36, 3 }, }, + { { 226, 41, 186, 96, 0 }, + { 1, 194, 38, 155, 12 }, + { 0, 101, 217, 68, 7 }, + { 61, 150, 68, 56, 0 }, }, + { { 225, 64, 224, 141, 6 }, + { 40, 180, 96, 198, 6 }, + { 107, 16, 112, 40, 7 }, + { 102, 48, 98, 209, 4 }, }, + { { 225, 154, 144, 165, 2 }, + { 72, 144, 58, 214, 10 }, + { 74, 80, 149, 152, 7 }, + { 86, 181, 192, 145, 2 }, }, + { { 231, 242, 192, 250, 9 }, + { 229, 164, 187, 203, 3 }, + { 149, 240, 52, 254, 7 }, + { 205, 61, 210, 90, 7 }, }, + { { 238, 173, 190, 131, 8 }, + { 131, 195, 53, 183, 15 }, + { 28, 23, 219, 87, 7 }, + { 254, 218, 204, 60, 1 }, }, + { { 240, 28, 242, 124, 1 }, + { 108, 232, 162, 186, 12 }, + { 131, 228, 243, 128, 15 }, + { 53, 212, 81, 115, 6 }, }, + { { 247, 101, 168, 38, 4 }, + { 137, 142, 102, 227, 5 }, + { 38, 65, 90, 110, 15 }, + { 172, 118, 103, 25, 1 }, }, + { { 247, 236, 195, 164, 13 }, + { 189, 204, 115, 227, 10 }, + { 178, 92, 51, 126, 15 }, + { 92, 124, 227, 59, 13 }, }, + { { 248, 45, 84, 113, 4 }, + { 35, 11, 226, 190, 8 }, + { 40, 226, 171, 65, 15 }, + { 23, 212, 125, 12, 4 }, }, + { { 254, 133, 143, 205, 11 }, + { 158, 251, 53, 175, 2 }, + { 219, 63, 26, 23, 15 }, + { 79, 90, 205, 247, 9 }, }, + { { 248, 227, 91, 11, 6 }, + { 51, 126, 124, 150, 1 }, + { 109, 13, 172, 113, 15 }, + { 134, 147, 231, 236, 12 }, }, + { { 254, 214, 62, 31, 15 }, + { 206, 125, 253, 183, 5 }, + { 255, 135, 198, 183, 15 }, + { 174, 219, 251, 231, 3 }, }, }; + +static unsigned char DICT_7X7_1000_BYTES[][4][7] = + { { { 221, 92, 108, 165, 202, 10, 1 }, + { 99, 179, 173, 228, 49, 180, 0 }, + { 168, 41, 210, 155, 29, 93, 1 }, + { 22, 198, 19, 218, 230, 227, 0 }, }, + { { 228, 27, 241, 62, 64, 171, 0 }, + { 17, 253, 137, 11, 181, 42, 1 }, + { 106, 129, 62, 71, 236, 19, 1 }, + { 170, 86, 232, 72, 223, 196, 0 }, }, + { { 158, 170, 43, 172, 93, 39, 1 }, + { 163, 182, 158, 145, 75, 171, 1 }, + { 242, 93, 26, 234, 42, 188, 1 }, + { 234, 233, 68, 188, 182, 226, 1 }, }, + { { 166, 103, 5, 183, 233, 76, 0 }, + { 221, 48, 50, 221, 165, 172, 0 }, + { 25, 75, 246, 208, 115, 50, 1 }, + { 26, 210, 221, 166, 6, 93, 1 }, }, + { { 198, 188, 123, 19, 50, 86, 0 }, + { 253, 193, 139, 113, 154, 97, 0 }, + { 53, 38, 100, 111, 30, 177, 1 }, + { 67, 44, 199, 104, 193, 223, 1 }, }, + { { 88, 128, 20, 35, 89, 238, 0 }, + { 4, 122, 28, 80, 184, 133, 1 }, + { 59, 205, 98, 20, 0, 141, 0 }, + { 208, 142, 133, 28, 47, 16, 0 }, }, + { { 211, 107, 190, 111, 84, 72, 0 }, + { 164, 240, 198, 70, 247, 63, 0 }, + { 9, 21, 123, 62, 235, 101, 1 }, + { 126, 119, 177, 49, 135, 146, 1 }, }, + { { 60, 161, 109, 136, 139, 219, 0 }, + { 37, 14, 191, 169, 44, 198, 0 }, + { 109, 232, 136, 219, 66, 158, 0 }, + { 49, 154, 74, 254, 184, 82, 0 }, }, + { { 137, 7, 31, 86, 150, 158, 0 }, + { 112, 74, 104, 49, 231, 125, 0 }, + { 60, 180, 181, 124, 112, 72, 1 }, + { 95, 115, 198, 11, 41, 7, 0 }, }, + { { 187, 101, 177, 141, 110, 63, 0 }, + { 200, 86, 142, 255, 101, 242, 1 }, + { 126, 59, 88, 198, 211, 110, 1 }, + { 167, 211, 127, 184, 181, 9, 1 }, }, + { { 245, 0, 209, 130, 244, 144, 1 }, + { 11, 88, 37, 139, 208, 113, 0 }, + { 132, 151, 160, 197, 128, 87, 1 }, + { 71, 5, 232, 210, 13, 104, 0 }, }, + { { 2, 38, 112, 184, 84, 26, 0 }, + { 208, 112, 139, 128, 96, 75, 0 }, + { 44, 21, 14, 135, 50, 32, 0 }, + { 105, 3, 0, 232, 135, 5, 1 }, }, + { { 84, 243, 107, 151, 72, 240, 1 }, + { 55, 25, 135, 197, 159, 200, 1 }, + { 135, 137, 116, 235, 103, 149, 0 }, + { 137, 252, 209, 240, 204, 118, 0 }, }, + { { 158, 36, 136, 70, 35, 153, 1 }, + { 235, 14, 86, 34, 161, 96, 0 }, + { 204, 226, 49, 8, 146, 60, 1 }, + { 3, 66, 162, 53, 56, 107, 1 }, }, + { { 189, 255, 50, 107, 201, 203, 1 }, + { 71, 255, 254, 76, 174, 186, 0 }, + { 233, 201, 235, 38, 127, 222, 1 }, + { 46, 186, 153, 63, 255, 241, 0 }, }, + { { 206, 17, 5, 176, 90, 151, 1 }, + { 147, 63, 8, 177, 20, 229, 0 }, + { 244, 173, 6, 208, 68, 57, 1 }, + { 83, 148, 70, 136, 126, 100, 1 }, }, + { { 211, 240, 97, 114, 31, 139, 0 }, + { 144, 45, 223, 37, 248, 177, 0 }, + { 104, 252, 39, 67, 7, 229, 1 }, + { 70, 143, 210, 125, 218, 4, 1 }, }, + { { 1, 225, 158, 206, 200, 237, 1 }, + { 38, 92, 98, 150, 175, 150, 1 }, + { 219, 137, 185, 188, 195, 192, 0 }, + { 180, 250, 180, 163, 29, 50, 0 }, }, + { { 38, 203, 204, 42, 112, 120, 1 }, + { 175, 176, 1, 14, 172, 79, 1 }, + { 143, 7, 42, 25, 233, 178, 0 }, + { 249, 26, 184, 64, 6, 250, 1 }, }, + { { 56, 125, 77, 118, 65, 19, 0 }, + { 112, 183, 95, 13, 133, 68, 0 }, + { 100, 65, 55, 89, 95, 14, 0 }, + { 17, 80, 216, 125, 118, 135, 0 }, }, + { { 58, 144, 74, 233, 225, 233, 1 }, + { 174, 63, 117, 200, 42, 2, 1 }, + { 203, 195, 203, 169, 4, 174, 0 }, + { 160, 42, 9, 215, 126, 58, 1 }, }, + { { 70, 212, 132, 123, 60, 221, 0 }, + { 221, 45, 64, 86, 248, 199, 0 }, + { 93, 158, 111, 16, 149, 177, 0 }, + { 113, 143, 181, 1, 90, 93, 1 }, }, + { { 72, 146, 167, 222, 158, 43, 1 }, + { 18, 7, 232, 163, 251, 143, 1 }, + { 234, 60, 189, 242, 164, 137, 0 }, + { 248, 239, 226, 139, 240, 36, 0 }, }, + { { 94, 118, 195, 228, 152, 168, 0 }, + { 193, 43, 103, 135, 51, 137, 1 }, + { 10, 140, 147, 225, 183, 61, 0 }, + { 200, 230, 112, 243, 106, 65, 1 }, }, + { { 116, 162, 222, 250, 47, 136, 1 }, + { 59, 104, 87, 170, 250, 142, 0 }, + { 136, 250, 47, 189, 162, 151, 0 }, + { 56, 175, 170, 245, 11, 110, 0 }, }, + { { 148, 93, 226, 40, 19, 89, 0 }, + { 69, 165, 149, 38, 38, 99, 0 }, + { 77, 100, 10, 35, 221, 20, 1 }, + { 99, 50, 50, 84, 210, 209, 0 }, }, + { { 155, 18, 149, 100, 237, 163, 1 }, + { 138, 127, 124, 3, 65, 188, 1 }, + { 226, 219, 147, 84, 164, 108, 1 }, + { 158, 193, 96, 31, 127, 40, 1 }, }, + { { 161, 212, 148, 45, 130, 248, 0 }, + { 68, 105, 32, 110, 41, 118, 1 }, + { 15, 160, 218, 20, 149, 194, 1 }, + { 183, 74, 59, 2, 75, 17, 0 }, }, + { { 187, 120, 230, 49, 108, 208, 1 }, + { 158, 187, 135, 78, 66, 244, 0 }, + { 133, 155, 70, 51, 143, 110, 1 }, + { 23, 161, 57, 112, 238, 188, 1 }, }, + { { 203, 181, 221, 203, 219, 24, 0 }, + { 224, 83, 115, 227, 188, 247, 0 }, + { 12, 109, 233, 221, 214, 233, 1 }, + { 119, 158, 227, 231, 101, 3, 1 }, }, + { { 211, 229, 238, 183, 131, 51, 0 }, + { 240, 36, 191, 230, 159, 116, 1 }, + { 102, 96, 246, 187, 211, 229, 1 }, + { 151, 124, 179, 254, 146, 7, 1 }, }, + { { 242, 148, 35, 63, 209, 156, 1 }, + { 210, 57, 180, 89, 187, 99, 0 }, + { 156, 197, 254, 98, 20, 167, 1 }, + { 99, 110, 205, 22, 206, 37, 1 }, }, + { { 253, 214, 209, 159, 59, 230, 1 }, + { 95, 75, 29, 255, 153, 187, 1 }, + { 179, 238, 124, 197, 181, 223, 1 }, + { 238, 204, 255, 220, 105, 125, 0 }, }, + { { 58, 70, 36, 238, 132, 238, 1 }, + { 198, 42, 236, 156, 225, 14, 1 }, + { 187, 144, 187, 146, 49, 46, 0 }, + { 184, 67, 156, 155, 170, 49, 1 }, }, + { { 98, 24, 126, 36, 59, 15, 0 }, + { 168, 229, 153, 56, 51, 133, 0 }, + { 120, 110, 18, 63, 12, 35, 0 }, + { 80, 230, 14, 76, 211, 138, 1 }, }, + { { 10, 205, 67, 111, 157, 195, 0 }, + { 196, 174, 121, 69, 207, 131, 0 }, + { 97, 220, 251, 97, 89, 168, 0 }, + { 96, 249, 209, 79, 58, 145, 1 }, }, + { { 14, 64, 212, 195, 142, 2, 0 }, + { 129, 66, 105, 230, 192, 132, 0 }, + { 32, 56, 225, 149, 129, 56, 0 }, + { 16, 129, 179, 203, 33, 64, 1 }, }, + { { 20, 159, 217, 160, 98, 36, 0 }, + { 105, 241, 5, 179, 12, 8, 1 }, + { 18, 35, 2, 205, 252, 148, 0 }, + { 136, 24, 102, 208, 71, 203, 0 }, }, + { { 25, 162, 225, 26, 101, 237, 0 }, + { 28, 30, 151, 19, 232, 26, 1 }, + { 91, 211, 44, 67, 162, 204, 0 }, + { 172, 11, 228, 116, 188, 28, 0 }, }, + { { 43, 80, 130, 126, 27, 52, 0 }, + { 144, 35, 80, 62, 131, 211, 1 }, + { 22, 108, 63, 32, 133, 106, 0 }, + { 229, 224, 190, 5, 98, 4, 1 }, }, + { { 41, 105, 56, 146, 159, 163, 0 }, + { 48, 206, 186, 172, 196, 145, 1 }, + { 98, 252, 164, 142, 75, 74, 0 }, + { 196, 145, 154, 174, 185, 134, 0 }, }, + { { 44, 247, 84, 219, 244, 86, 1 }, + { 95, 83, 107, 220, 204, 79, 0 }, + { 181, 23, 237, 149, 119, 154, 0 }, + { 121, 25, 157, 235, 101, 125, 0 }, }, + { { 53, 233, 63, 158, 54, 124, 0 }, + { 61, 192, 134, 189, 239, 87, 1 }, + { 31, 54, 60, 254, 75, 214, 0 }, + { 245, 123, 222, 176, 129, 222, 0 }, }, + { { 63, 116, 171, 216, 61, 243, 1 }, + { 255, 15, 222, 143, 66, 211, 1 }, + { 231, 222, 13, 234, 151, 126, 0 }, + { 229, 161, 120, 189, 248, 127, 1 }, }, + { { 68, 208, 41, 244, 255, 64, 0 }, + { 61, 49, 240, 165, 89, 129, 0 }, + { 1, 127, 151, 202, 5, 145, 0 }, + { 64, 205, 82, 135, 198, 94, 0 }, }, + { { 72, 64, 228, 132, 42, 245, 0 }, + { 12, 14, 129, 182, 17, 196, 1 }, + { 87, 170, 16, 147, 129, 9, 0 }, + { 145, 196, 54, 192, 184, 24, 0 }, }, + { { 95, 25, 19, 104, 52, 2, 0 }, + { 137, 227, 76, 1, 86, 19, 0 }, + { 32, 22, 11, 100, 76, 125, 0 }, + { 100, 53, 64, 25, 99, 200, 1 }, }, + { { 94, 178, 114, 64, 61, 93, 1 }, + { 143, 71, 215, 16, 122, 201, 0 }, + { 221, 94, 1, 39, 38, 189, 0 }, + { 73, 175, 4, 117, 241, 120, 1 }, }, + { { 111, 155, 36, 31, 21, 194, 0 }, + { 149, 139, 152, 72, 221, 31, 0 }, + { 33, 212, 124, 18, 108, 251, 0 }, + { 124, 93, 137, 12, 232, 212, 1 }, }, + { { 114, 170, 249, 32, 168, 227, 1 }, + { 174, 236, 175, 11, 24, 136, 1 }, + { 227, 138, 130, 79, 170, 167, 0 }, + { 136, 140, 104, 122, 155, 186, 1 }, }, + { { 121, 136, 117, 59, 134, 54, 1 }, + { 18, 226, 173, 121, 216, 86, 1 }, + { 182, 48, 238, 87, 8, 207, 0 }, + { 181, 13, 207, 90, 163, 164, 0 }, }, + { { 123, 231, 19, 174, 121, 81, 1 }, + { 206, 118, 22, 141, 159, 219, 0 }, + { 197, 79, 58, 228, 115, 239, 0 }, + { 109, 252, 216, 180, 55, 57, 1 }, }, + { { 133, 106, 70, 201, 89, 224, 0 }, + { 5, 152, 83, 196, 2, 191, 1 }, + { 3, 205, 73, 177, 43, 80, 1 }, + { 254, 160, 17, 229, 12, 208, 0 }, }, + { { 142, 63, 238, 242, 217, 81, 1 }, + { 247, 183, 243, 130, 134, 237, 0 }, + { 197, 77, 167, 187, 254, 56, 1 }, + { 91, 176, 160, 231, 246, 247, 1 }, }, + { { 173, 56, 205, 33, 115, 103, 1 }, + { 47, 183, 27, 123, 0, 53, 1 }, + { 243, 103, 66, 89, 142, 90, 1 }, + { 214, 0, 111, 108, 118, 250, 0 }, }, + { { 177, 55, 37, 48, 185, 63, 1 }, + { 90, 37, 190, 25, 36, 253, 1 }, + { 254, 78, 134, 82, 118, 70, 1 }, + { 223, 146, 76, 62, 210, 45, 0 }, }, + { { 186, 195, 128, 210, 81, 219, 0 }, + { 148, 30, 92, 142, 172, 105, 0 }, + { 109, 197, 37, 128, 225, 174, 1 }, + { 75, 26, 184, 157, 60, 20, 1 }, }, + { { 193, 117, 109, 93, 24, 166, 1 }, + { 114, 9, 203, 85, 21, 183, 1 }, + { 178, 140, 93, 91, 87, 65, 1 }, + { 246, 212, 85, 105, 200, 39, 0 }, }, + { { 206, 97, 211, 126, 229, 17, 0 }, + { 153, 118, 115, 7, 215, 98, 0 }, + { 68, 83, 191, 101, 195, 57, 1 }, + { 35, 117, 240, 103, 55, 76, 1 }, }, + { { 210, 92, 152, 54, 221, 91, 1 }, + { 246, 245, 60, 6, 241, 225, 0 }, + { 237, 93, 182, 12, 157, 37, 1 }, + { 67, 199, 176, 30, 87, 183, 1 }, }, + { { 208, 106, 145, 233, 239, 206, 1 }, + { 14, 248, 126, 247, 112, 170, 0 }, + { 185, 251, 203, 196, 171, 5, 1 }, + { 42, 135, 119, 191, 15, 184, 0 }, }, + { { 73, 228, 227, 141, 169, 90, 1 }, + { 78, 2, 187, 199, 59, 210, 0 }, + { 173, 74, 216, 227, 147, 201, 0 }, + { 37, 238, 113, 238, 160, 57, 0 }, }, + { { 82, 209, 159, 40, 31, 179, 0 }, + { 160, 109, 28, 39, 94, 199, 1 }, + { 102, 252, 10, 124, 197, 165, 0 }, + { 241, 189, 114, 28, 91, 2, 1 }, }, + { { 2, 24, 175, 24, 115, 160, 1 }, + { 186, 153, 144, 35, 2, 7, 1 }, + { 130, 231, 12, 122, 140, 32, 0 }, + { 240, 32, 98, 4, 204, 174, 1 }, }, + { { 5, 192, 75, 56, 81, 119, 0 }, + { 53, 52, 25, 21, 10, 83, 1 }, + { 119, 69, 14, 105, 1, 208, 0 }, + { 229, 40, 84, 76, 22, 86, 0 }, }, + { { 15, 88, 169, 33, 137, 159, 0 }, + { 161, 175, 184, 87, 32, 208, 0 }, + { 124, 200, 194, 74, 141, 120, 0 }, + { 5, 130, 117, 14, 250, 194, 1 }, }, + { { 15, 206, 165, 68, 68, 26, 0 }, + { 193, 146, 200, 7, 105, 92, 0 }, + { 44, 17, 17, 82, 185, 248, 0 }, + { 29, 75, 112, 9, 164, 193, 1 }, }, + { { 21, 238, 211, 115, 20, 216, 1 }, + { 87, 232, 71, 71, 234, 89, 0 }, + { 141, 148, 103, 101, 187, 212, 0 }, + { 77, 43, 241, 113, 11, 245, 0 }, }, + { { 22, 241, 226, 252, 69, 158, 1 }, + { 147, 57, 223, 150, 111, 66, 0 }, + { 188, 209, 31, 163, 199, 180, 0 }, + { 33, 123, 52, 253, 206, 100, 1 }, }, + { { 30, 94, 76, 65, 59, 115, 0 }, + { 237, 135, 93, 100, 0, 205, 1 }, + { 103, 110, 65, 25, 61, 60, 0 }, + { 217, 128, 19, 93, 112, 219, 1 }, }, + { { 31, 194, 82, 102, 200, 118, 1 }, + { 135, 114, 109, 20, 139, 216, 1 }, + { 183, 9, 179, 37, 33, 252, 0 }, + { 141, 232, 148, 91, 39, 112, 1 }, }, + { { 39, 33, 22, 246, 49, 159, 1 }, + { 155, 108, 90, 152, 167, 85, 0 }, + { 252, 198, 55, 180, 66, 114, 0 }, + { 85, 114, 140, 173, 27, 108, 1 }, }, + { { 39, 155, 122, 137, 27, 220, 0 }, + { 165, 201, 145, 248, 46, 219, 0 }, + { 29, 236, 72, 175, 108, 242, 0 }, + { 109, 186, 15, 196, 201, 210, 1 }, }, + { { 46, 12, 48, 75, 106, 108, 0 }, + { 205, 210, 192, 120, 160, 130, 1 }, + { 27, 43, 105, 6, 24, 58, 0 }, + { 160, 130, 143, 1, 165, 217, 1 }, }, + { { 47, 169, 226, 121, 230, 43, 0 }, + { 153, 182, 235, 106, 110, 18, 1 }, + { 106, 51, 207, 35, 202, 250, 0 }, + { 164, 59, 43, 107, 182, 204, 1 }, }, + { { 44, 207, 211, 2, 147, 19, 1 }, + { 67, 198, 57, 47, 142, 73, 0 }, + { 228, 100, 160, 101, 249, 154, 0 }, + { 73, 56, 250, 78, 49, 225, 0 }, }, + { { 49, 83, 3, 2, 18, 230, 1 }, + { 6, 9, 12, 61, 134, 25, 1 }, + { 179, 164, 32, 96, 101, 70, 0 }, + { 204, 48, 222, 24, 72, 48, 0 }, }, + { { 48, 120, 115, 90, 253, 41, 1 }, + { 26, 213, 247, 13, 226, 131, 1 }, + { 202, 95, 173, 103, 15, 6, 0 }, + { 224, 163, 216, 119, 213, 172, 0 }, }, + { { 55, 226, 67, 44, 167, 160, 1 }, + { 139, 40, 55, 45, 75, 26, 1 }, + { 130, 242, 154, 97, 35, 246, 0 }, + { 172, 105, 90, 118, 10, 104, 1 }, }, + { { 76, 11, 79, 138, 255, 84, 0 }, + { 45, 146, 49, 177, 214, 207, 0 }, + { 21, 127, 168, 249, 104, 25, 0 }, + { 121, 181, 198, 198, 36, 218, 0 }, }, + { { 78, 218, 184, 202, 73, 33, 1 }, + { 163, 215, 208, 134, 152, 138, 1 }, + { 194, 73, 41, 142, 173, 185, 0 }, + { 168, 140, 176, 133, 245, 226, 1 }, }, + { { 76, 218, 221, 21, 152, 110, 0 }, + { 53, 195, 41, 87, 57, 141, 1 }, + { 59, 12, 212, 93, 173, 153, 0 }, + { 216, 206, 117, 74, 97, 214, 0 }, }, + { { 86, 123, 85, 187, 37, 96, 1 }, + { 159, 225, 23, 197, 212, 14, 1 }, + { 131, 82, 110, 213, 111, 53, 0 }, + { 184, 21, 209, 244, 67, 252, 1 }, }, + { { 84, 151, 117, 241, 41, 203, 0 }, + { 93, 109, 221, 193, 60, 140, 0 }, + { 105, 202, 71, 215, 116, 149, 0 }, + { 24, 158, 65, 221, 219, 93, 0 }, }, + { { 84, 152, 140, 205, 4, 162, 0 }, + { 33, 137, 76, 194, 89, 6, 1 }, + { 34, 144, 89, 152, 140, 149, 0 }, + { 176, 77, 33, 153, 72, 194, 0 }, }, + { { 94, 110, 164, 168, 119, 169, 0 }, + { 201, 190, 150, 166, 112, 15, 1 }, + { 74, 247, 10, 146, 187, 61, 0 }, + { 248, 7, 50, 180, 190, 201, 1 }, }, + { { 99, 4, 81, 119, 99, 178, 0 }, + { 216, 120, 89, 105, 145, 80, 1 }, + { 38, 227, 119, 69, 16, 99, 0 }, + { 133, 68, 203, 77, 15, 13, 1 }, }, + { { 105, 226, 58, 120, 116, 228, 1 }, + { 62, 122, 194, 28, 90, 27, 1 }, + { 147, 151, 15, 46, 35, 203, 0 }, + { 236, 45, 28, 33, 175, 62, 0 }, }, + { { 109, 23, 226, 74, 80, 28, 1 }, + { 67, 19, 193, 26, 182, 91, 0 }, + { 156, 5, 41, 35, 244, 91, 0 }, + { 109, 54, 172, 65, 228, 97, 0 }, }, + { { 112, 62, 128, 226, 203, 177, 0 }, + { 64, 189, 118, 170, 144, 200, 1 }, + { 70, 233, 163, 128, 190, 7, 0 }, + { 137, 132, 170, 183, 94, 129, 0 }, }, + { { 119, 80, 90, 139, 20, 43, 1 }, + { 163, 69, 13, 204, 242, 19, 1 }, + { 234, 20, 104, 173, 5, 119, 0 }, + { 228, 39, 153, 216, 81, 98, 1 }, }, + { { 130, 100, 225, 43, 36, 151, 1 }, + { 202, 44, 139, 87, 192, 98, 0 }, + { 244, 146, 106, 67, 147, 32, 1 }, + { 35, 1, 245, 104, 154, 41, 1 }, }, + { { 131, 253, 167, 209, 241, 197, 0 }, + { 220, 157, 242, 215, 14, 53, 0 }, + { 81, 199, 197, 242, 223, 224, 1 }, + { 86, 56, 117, 167, 220, 157, 1 }, }, + { { 147, 189, 107, 77, 96, 125, 1 }, + { 238, 149, 199, 81, 47, 114, 1 }, + { 223, 3, 89, 107, 94, 228, 1 }, + { 167, 122, 69, 113, 212, 187, 1 }, }, + { { 148, 216, 165, 194, 122, 237, 1 }, + { 15, 157, 196, 183, 168, 165, 1 }, + { 219, 175, 33, 210, 141, 148, 1 }, + { 210, 138, 246, 145, 220, 248, 0 }, }, + { { 148, 247, 189, 26, 15, 79, 1 }, + { 119, 69, 158, 55, 236, 174, 0 }, + { 249, 120, 44, 94, 247, 148, 1 }, + { 58, 155, 246, 60, 209, 119, 0 }, }, + { { 152, 241, 170, 62, 148, 248, 0 }, + { 52, 43, 166, 6, 239, 99, 1 }, + { 15, 148, 190, 42, 199, 140, 1 }, + { 227, 123, 176, 50, 234, 22, 0 }, }, + { { 169, 138, 223, 140, 87, 40, 0 }, + { 32, 210, 17, 171, 107, 63, 1 }, + { 10, 117, 24, 253, 168, 202, 1 }, + { 254, 107, 106, 196, 37, 130, 0 }, }, + { { 177, 14, 54, 223, 65, 63, 0 }, + { 80, 212, 220, 216, 163, 126, 1 }, + { 126, 65, 125, 182, 56, 70, 1 }, + { 191, 98, 141, 157, 149, 133, 0 }, }, + { { 185, 234, 28, 100, 226, 107, 0 }, + { 44, 246, 110, 44, 41, 60, 1 }, + { 107, 35, 147, 28, 43, 206, 1 }, + { 158, 74, 26, 59, 55, 154, 0 }, }, + { { 188, 38, 74, 240, 242, 183, 1 }, + { 123, 62, 111, 184, 2, 105, 1 }, + { 246, 167, 135, 169, 50, 30, 1 }, + { 203, 32, 14, 251, 62, 111, 0 }, }, + { { 189, 63, 146, 218, 126, 170, 0 }, + { 89, 219, 78, 170, 230, 187, 1 }, + { 42, 191, 45, 164, 254, 94, 1 }, + { 238, 179, 170, 185, 109, 205, 0 }, }, + { { 192, 53, 46, 74, 202, 87, 0 }, + { 100, 21, 234, 48, 150, 230, 0 }, + { 117, 41, 169, 58, 86, 1, 1 }, + { 51, 180, 134, 43, 212, 19, 0 }, }, + { { 194, 54, 220, 36, 22, 128, 1 }, + { 226, 105, 3, 34, 81, 45, 0 }, + { 128, 180, 18, 29, 182, 33, 1 }, + { 90, 69, 34, 96, 75, 35, 1 }, }, + { { 211, 79, 245, 172, 136, 118, 1 }, + { 198, 224, 173, 151, 21, 254, 1 }, + { 183, 8, 154, 215, 249, 101, 1 }, + { 191, 212, 116, 218, 131, 177, 1 }, }, + { { 215, 6, 210, 240, 31, 150, 0 }, + { 209, 104, 93, 178, 82, 249, 0 }, + { 52, 252, 7, 165, 176, 117, 1 }, + { 79, 165, 38, 221, 11, 69, 1 }, }, + { { 216, 50, 245, 201, 86, 5, 0 }, + { 0, 87, 199, 243, 80, 47, 0 }, + { 80, 53, 73, 215, 166, 13, 1 }, + { 122, 5, 103, 241, 245, 0, 0 }, }, + { { 216, 217, 65, 4, 131, 170, 0 }, + { 0, 139, 61, 37, 61, 32, 1 }, + { 42, 224, 144, 65, 77, 141, 1 }, + { 130, 94, 82, 94, 104, 128, 0 }, }, + { { 224, 211, 100, 108, 77, 209, 1 }, + { 6, 61, 209, 12, 93, 238, 0 }, + { 197, 217, 27, 19, 101, 131, 1 }, + { 59, 221, 24, 69, 222, 48, 0 }, }, + { { 225, 232, 37, 60, 152, 205, 0 }, + { 20, 172, 162, 29, 57, 183, 0 }, + { 89, 140, 158, 82, 11, 195, 1 }, + { 118, 206, 92, 34, 154, 148, 0 }, }, + { { 229, 118, 79, 139, 232, 157, 1 }, + { 107, 29, 35, 221, 178, 254, 0 }, + { 220, 139, 232, 249, 55, 83, 1 }, + { 63, 166, 221, 226, 92, 107, 0 }, }, + { { 232, 80, 250, 159, 250, 74, 0 }, + { 60, 83, 169, 238, 179, 163, 0 }, + { 41, 47, 252, 175, 133, 11, 1 }, + { 98, 230, 187, 202, 229, 30, 0 }, }, + { { 235, 128, 234, 240, 130, 92, 1 }, + { 182, 34, 225, 186, 58, 112, 0 }, + { 157, 32, 135, 171, 128, 235, 1 }, + { 7, 46, 46, 195, 162, 54, 1 }, }, + { { 239, 108, 113, 174, 69, 200, 1 }, + { 199, 250, 147, 141, 241, 50, 0 }, + { 137, 209, 58, 199, 27, 123, 1 }, + { 38, 71, 216, 228, 175, 241, 1 }, }, + { { 247, 217, 64, 237, 177, 12, 0 }, + { 137, 161, 117, 220, 61, 51, 0 }, + { 24, 70, 219, 129, 77, 247, 1 }, + { 102, 94, 29, 215, 66, 200, 1 }, }, + { { 254, 214, 40, 241, 92, 60, 1 }, + { 243, 51, 196, 220, 120, 233, 1 }, + { 158, 29, 71, 138, 53, 191, 1 }, + { 203, 143, 29, 145, 230, 103, 1 }, }, + { { 20, 199, 225, 220, 124, 3, 1 }, + { 91, 20, 205, 135, 77, 139, 0 }, + { 224, 31, 29, 195, 241, 148, 0 }, + { 104, 217, 112, 217, 148, 109, 0 }, }, + { { 34, 236, 79, 160, 255, 225, 0 }, + { 236, 188, 51, 173, 74, 133, 1 }, + { 67, 255, 130, 249, 27, 162, 0 }, + { 208, 169, 90, 230, 30, 155, 1 }, }, + { { 164, 134, 72, 34, 231, 234, 0 }, + { 109, 56, 57, 40, 232, 40, 1 }, + { 43, 243, 162, 9, 48, 146, 1 }, + { 138, 11, 138, 78, 14, 91, 0 }, }, + { { 179, 172, 156, 128, 134, 12, 1 }, + { 226, 192, 38, 186, 104, 52, 0 }, + { 152, 48, 128, 156, 154, 230, 1 }, + { 22, 11, 46, 178, 1, 163, 1 }, }, + { { 235, 197, 247, 106, 12, 68, 0 }, + { 196, 98, 193, 31, 222, 182, 0 }, + { 17, 24, 43, 119, 209, 235, 1 }, + { 54, 189, 252, 65, 163, 17, 1 }, }, + { { 0, 36, 28, 209, 199, 244, 0 }, + { 116, 88, 114, 240, 64, 68, 1 }, + { 23, 241, 197, 156, 18, 0, 0 }, + { 145, 1, 7, 167, 13, 23, 0 }, }, + { { 0, 65, 85, 60, 228, 236, 0 }, + { 28, 120, 33, 21, 101, 6, 1 }, + { 27, 147, 158, 85, 65, 0, 0 }, + { 176, 83, 84, 66, 15, 28, 0 }, }, + { { 2, 119, 133, 135, 91, 48, 1 }, + { 194, 17, 18, 231, 133, 205, 1 }, + { 134, 109, 112, 208, 247, 32, 0 }, + { 217, 208, 243, 164, 68, 33, 1 }, }, + { { 1, 218, 232, 155, 2, 227, 0 }, + { 52, 141, 137, 230, 136, 26, 1 }, + { 99, 160, 108, 139, 173, 192, 0 }, + { 172, 8, 179, 200, 216, 150, 0 }, }, + { { 4, 40, 5, 26, 111, 159, 0 }, + { 25, 156, 26, 49, 224, 198, 0 }, + { 124, 251, 44, 80, 10, 16, 0 }, + { 49, 131, 198, 44, 28, 204, 0 }, }, + { { 7, 145, 0, 75, 83, 171, 0 }, + { 129, 29, 88, 96, 172, 19, 1 }, + { 106, 229, 105, 0, 68, 240, 0 }, + { 228, 26, 131, 13, 92, 64, 1 }, }, + { { 10, 89, 153, 210, 227, 215, 1 }, + { 190, 223, 120, 183, 132, 64, 0 }, + { 245, 227, 165, 204, 205, 40, 0 }, + { 1, 16, 246, 143, 125, 190, 1 }, }, + { { 9, 178, 243, 249, 248, 177, 1 }, + { 26, 127, 227, 195, 10, 219, 1 }, + { 198, 143, 207, 231, 166, 200, 0 }, + { 237, 168, 97, 227, 255, 44, 0 }, }, + { { 13, 49, 205, 127, 92, 218, 1 }, + { 55, 59, 75, 67, 229, 215, 0 }, + { 173, 157, 127, 89, 198, 88, 0 }, + { 117, 211, 225, 105, 110, 118, 0 }, }, + { { 14, 110, 159, 79, 116, 37, 1 }, + { 235, 214, 66, 87, 195, 15, 1 }, + { 210, 23, 121, 124, 187, 56, 0 }, + { 248, 97, 245, 33, 53, 235, 1 }, }, + { { 23, 114, 128, 242, 245, 110, 0 }, + { 157, 49, 126, 150, 224, 25, 1 }, + { 59, 87, 167, 128, 167, 116, 0 }, + { 204, 3, 180, 191, 70, 92, 1 }, }, + { { 22, 255, 97, 63, 186, 238, 0 }, + { 221, 169, 175, 117, 173, 139, 1 }, + { 59, 174, 254, 67, 127, 180, 0 }, + { 232, 218, 215, 122, 202, 221, 1 }, }, + { { 25, 50, 221, 83, 147, 173, 1 }, + { 50, 79, 119, 115, 160, 29, 1 }, + { 218, 228, 229, 93, 166, 76, 0 }, + { 220, 2, 231, 119, 121, 38, 0 }, }, + { { 30, 55, 90, 161, 138, 28, 1 }, + { 227, 99, 39, 240, 38, 200, 0 }, + { 156, 40, 194, 173, 118, 60, 0 }, + { 9, 178, 7, 242, 99, 99, 1 }, }, + { { 30, 243, 155, 67, 46, 40, 0 }, + { 169, 67, 70, 103, 238, 136, 1 }, + { 10, 58, 97, 108, 231, 188, 0 }, + { 136, 187, 243, 49, 97, 74, 1 }, }, + { { 35, 152, 218, 66, 102, 77, 0 }, + { 172, 213, 65, 58, 234, 16, 0 }, + { 89, 51, 33, 45, 140, 226, 0 }, + { 4, 43, 174, 65, 85, 154, 1 }, }, + { { 35, 183, 98, 39, 73, 166, 1 }, + { 194, 57, 155, 88, 143, 152, 1 }, + { 178, 201, 114, 35, 118, 226, 0 }, + { 140, 248, 141, 108, 206, 33, 1 }, }, + { { 39, 232, 89, 247, 30, 7, 0 }, + { 177, 228, 75, 253, 201, 145, 0 }, + { 112, 60, 119, 205, 11, 242, 0 }, + { 68, 201, 223, 233, 19, 198, 1 }, }, + { { 41, 207, 36, 233, 56, 245, 0 }, + { 76, 174, 192, 220, 12, 223, 1 }, + { 87, 142, 75, 146, 121, 202, 0 }, + { 253, 152, 29, 129, 186, 153, 0 }, }, + { { 43, 202, 225, 232, 241, 71, 1 }, + { 142, 182, 249, 159, 8, 27, 0 }, + { 241, 71, 139, 195, 169, 234, 0 }, + { 108, 8, 124, 207, 182, 184, 1 }, }, + { { 46, 52, 167, 60, 136, 145, 0 }, + { 209, 47, 162, 11, 3, 198, 0 }, + { 68, 136, 158, 114, 150, 58, 0 }, + { 49, 224, 104, 34, 250, 69, 1 }, }, + { { 44, 130, 54, 130, 20, 241, 1 }, + { 7, 78, 128, 136, 202, 77, 1 }, + { 199, 148, 32, 182, 32, 154, 0 }, + { 217, 41, 136, 128, 185, 112, 0 }, }, + { { 52, 119, 39, 34, 124, 128, 0 }, + { 73, 57, 134, 13, 198, 141, 0 }, + { 0, 159, 34, 114, 119, 22, 0 }, + { 88, 177, 216, 48, 206, 73, 0 }, }, + { { 62, 106, 78, 239, 207, 36, 0 }, + { 161, 178, 119, 252, 195, 142, 1 }, + { 18, 121, 251, 185, 43, 62, 0 }, + { 184, 225, 159, 247, 38, 194, 1 }, }, + { { 61, 189, 0, 186, 135, 236, 0 }, + { 85, 171, 54, 184, 236, 18, 1 }, + { 27, 240, 174, 128, 94, 222, 0 }, + { 164, 27, 142, 182, 106, 213, 0 }, }, + { { 65, 89, 70, 17, 248, 154, 0 }, + { 24, 153, 41, 68, 54, 213, 0 }, + { 44, 143, 196, 49, 77, 65, 0 }, + { 85, 182, 17, 74, 76, 140, 0 }, }, + { { 66, 160, 192, 80, 251, 19, 1 }, + { 154, 20, 123, 34, 24, 193, 0 }, + { 228, 111, 133, 1, 130, 161, 0 }, + { 65, 140, 34, 111, 20, 44, 1 }, }, + { { 66, 215, 59, 80, 17, 218, 1 }, + { 246, 73, 216, 5, 62, 73, 0 }, + { 173, 196, 5, 110, 117, 161, 0 }, + { 73, 62, 80, 13, 201, 55, 1 }, }, + { { 71, 21, 159, 236, 40, 89, 1 }, + { 239, 101, 64, 131, 55, 214, 0 }, + { 205, 10, 27, 252, 212, 113, 0 }, + { 53, 246, 96, 129, 83, 123, 1 }, }, + { { 72, 72, 74, 156, 121, 141, 1 }, + { 58, 158, 17, 148, 51, 131, 0 }, + { 216, 207, 28, 169, 9, 9, 0 }, + { 96, 230, 20, 196, 60, 174, 0 }, }, + { { 75, 141, 239, 56, 120, 19, 0 }, + { 248, 182, 137, 3, 30, 215, 0 }, + { 100, 15, 14, 123, 216, 233, 0 }, + { 117, 188, 96, 72, 182, 143, 1 }, }, + { { 72, 197, 79, 119, 42, 255, 1 }, + { 126, 46, 73, 117, 191, 196, 1 }, + { 255, 170, 119, 121, 81, 137, 0 }, + { 145, 254, 215, 73, 58, 63, 0 }, }, + { { 73, 205, 149, 222, 250, 38, 0 }, + { 88, 210, 104, 183, 157, 151, 1 }, + { 50, 47, 189, 212, 217, 201, 0 }, + { 244, 220, 246, 139, 37, 141, 0 }, }, + { { 74, 248, 19, 154, 40, 250, 0 }, + { 156, 203, 10, 133, 186, 194, 1 }, + { 47, 138, 44, 228, 15, 169, 0 }, + { 161, 174, 208, 168, 105, 156, 1 }, }, + { { 76, 39, 45, 47, 65, 165, 0 }, + { 97, 62, 146, 81, 149, 14, 1 }, + { 82, 193, 122, 90, 114, 25, 0 }, + { 184, 84, 197, 36, 190, 67, 0 }, }, + { { 76, 63, 202, 163, 60, 106, 1 }, + { 111, 163, 11, 194, 246, 137, 1 }, + { 171, 30, 98, 169, 254, 25, 0 }, + { 200, 183, 161, 232, 98, 251, 0 }, }, + { { 76, 105, 150, 233, 240, 91, 0 }, + { 13, 246, 106, 198, 54, 71, 0 }, + { 109, 7, 203, 180, 203, 25, 0 }, + { 113, 54, 49, 171, 55, 216, 0 }, }, + { { 83, 59, 100, 210, 198, 162, 0 }, + { 144, 153, 239, 160, 212, 28, 1 }, + { 34, 177, 165, 147, 110, 101, 0 }, + { 156, 21, 130, 251, 204, 132, 1 }, }, + { { 81, 70, 77, 104, 180, 53, 1 }, + { 106, 36, 101, 21, 80, 95, 1 }, + { 214, 22, 139, 89, 49, 69, 0 }, + { 253, 5, 84, 83, 18, 43, 0 }, }, + { { 81, 86, 86, 98, 113, 201, 1 }, + { 78, 125, 85, 4, 178, 29, 0 }, + { 201, 199, 35, 53, 53, 69, 0 }, + { 92, 38, 144, 85, 95, 57, 0 }, }, + { { 80, 115, 53, 140, 113, 223, 1 }, + { 14, 93, 158, 149, 53, 79, 0 }, + { 253, 199, 24, 214, 103, 5, 0 }, + { 121, 86, 84, 188, 221, 56, 0 }, }, + { { 84, 74, 231, 46, 2, 158, 1 }, + { 3, 168, 141, 55, 179, 78, 0 }, + { 188, 160, 58, 115, 169, 21, 0 }, + { 57, 102, 246, 88, 138, 224, 0 }, }, + { { 84, 219, 22, 238, 94, 71, 0 }, + { 5, 245, 76, 180, 223, 143, 0 }, + { 113, 61, 59, 180, 109, 149, 0 }, + { 120, 253, 150, 153, 87, 208, 0 }, }, + { { 86, 240, 240, 30, 140, 100, 1 }, + { 151, 65, 167, 22, 217, 130, 1 }, + { 147, 24, 188, 7, 135, 181, 0 }, + { 160, 205, 180, 114, 193, 116, 1 }, }, + { { 91, 106, 3, 196, 23, 123, 1 }, + { 134, 134, 94, 165, 115, 89, 1 }, + { 239, 116, 17, 224, 43, 109, 0 }, + { 205, 103, 82, 189, 48, 176, 1 }, }, + { { 91, 249, 239, 82, 25, 108, 1 }, + { 182, 131, 215, 23, 190, 149, 1 }, + { 155, 76, 37, 123, 207, 237, 0 }, + { 212, 190, 244, 117, 224, 182, 1 }, }, + { { 92, 116, 249, 2, 251, 190, 0 }, + { 105, 91, 191, 55, 176, 193, 1 }, + { 62, 239, 160, 79, 151, 29, 0 }, + { 193, 134, 246, 126, 237, 75, 0 }, }, + { { 94, 157, 11, 47, 79, 122, 1 }, + { 231, 179, 28, 97, 255, 194, 1 }, + { 175, 121, 122, 104, 92, 189, 0 }, + { 161, 255, 195, 28, 102, 243, 1 }, }, + { { 99, 67, 213, 185, 201, 130, 0 }, + { 144, 120, 57, 207, 20, 158, 0 }, + { 32, 201, 206, 213, 225, 99, 0 }, + { 60, 148, 121, 206, 15, 4, 1 }, }, + { { 96, 93, 164, 245, 22, 155, 1 }, + { 82, 173, 200, 238, 117, 69, 0 }, + { 236, 180, 87, 146, 221, 3, 0 }, + { 81, 87, 59, 137, 218, 165, 0 }, }, + { { 100, 136, 47, 135, 72, 47, 0 }, + { 33, 148, 136, 217, 187, 132, 1 }, + { 122, 9, 112, 250, 8, 147, 0 }, + { 144, 238, 205, 136, 148, 194, 0 }, }, + { { 104, 106, 145, 148, 102, 64, 0 }, + { 28, 210, 2, 175, 81, 8, 0 }, + { 1, 51, 20, 196, 171, 11, 0 }, + { 8, 69, 122, 160, 37, 156, 0 }, }, + { { 108, 55, 209, 238, 204, 101, 1 }, + { 71, 119, 99, 155, 213, 138, 1 }, + { 211, 25, 187, 197, 246, 27, 0 }, + { 168, 213, 236, 227, 119, 113, 0 }, }, + { { 109, 89, 245, 135, 43, 42, 0 }, + { 9, 195, 153, 239, 181, 148, 1 }, + { 42, 106, 112, 215, 205, 91, 0 }, + { 148, 214, 251, 204, 225, 200, 0 }, }, + { { 115, 31, 7, 158, 40, 12, 0 }, + { 216, 129, 4, 153, 183, 158, 0 }, + { 24, 10, 60, 240, 124, 103, 0 }, + { 60, 246, 204, 144, 64, 141, 1 }, }, + { { 114, 38, 22, 146, 200, 199, 1 }, + { 214, 92, 46, 152, 146, 140, 0 }, + { 241, 137, 164, 180, 50, 39, 0 }, + { 24, 164, 140, 186, 29, 53, 1 }, }, + { { 115, 131, 239, 207, 199, 93, 1 }, + { 166, 20, 245, 251, 255, 94, 0 }, + { 221, 113, 249, 251, 224, 231, 0 }, + { 61, 127, 239, 215, 148, 50, 1 }, }, + { { 113, 246, 244, 181, 170, 13, 0 }, + { 88, 101, 167, 254, 57, 156, 0 }, + { 88, 42, 214, 151, 183, 199, 0 }, + { 28, 206, 63, 242, 211, 13, 0 }, }, + { { 118, 16, 2, 241, 170, 59, 0 }, + { 153, 37, 108, 232, 50, 192, 1 }, + { 110, 42, 199, 160, 4, 55, 0 }, + { 129, 166, 11, 155, 82, 76, 1 }, }, + { { 118, 84, 255, 84, 38, 33, 0 }, + { 249, 69, 197, 47, 83, 4, 1 }, + { 66, 50, 21, 127, 149, 55, 0 }, + { 144, 101, 122, 81, 209, 79, 1 }, }, + { { 122, 227, 200, 30, 38, 54, 0 }, + { 184, 2, 15, 62, 221, 74, 1 }, + { 54, 50, 60, 9, 227, 175, 0 }, + { 169, 93, 190, 120, 32, 14, 1 }, }, + { { 126, 32, 91, 27, 109, 102, 0 }, + { 189, 82, 31, 89, 210, 130, 1 }, + { 51, 91, 108, 109, 2, 63, 0 }, + { 160, 165, 205, 124, 37, 94, 1 }, }, + { { 124, 113, 102, 127, 179, 75, 1 }, + { 31, 39, 255, 108, 183, 7, 0 }, + { 233, 102, 255, 51, 71, 31, 0 }, + { 112, 118, 155, 127, 242, 124, 0 }, }, + { { 126, 136, 249, 129, 71, 26, 0 }, + { 161, 210, 157, 235, 120, 64, 0 }, + { 44, 113, 64, 207, 136, 191, 0 }, + { 1, 15, 107, 220, 165, 194, 1 }, }, + { { 127, 189, 182, 243, 152, 70, 0 }, + { 213, 227, 238, 218, 158, 149, 0 }, + { 49, 12, 231, 182, 222, 255, 0 }, + { 84, 188, 173, 187, 227, 213, 1 }, }, + { { 131, 186, 63, 66, 156, 103, 0 }, + { 164, 197, 234, 17, 202, 189, 1 }, + { 115, 28, 161, 126, 46, 224, 1 }, + { 222, 169, 196, 43, 209, 146, 1 }, }, + { { 131, 179, 163, 188, 159, 85, 1 }, + { 150, 37, 178, 179, 79, 251, 0 }, + { 213, 124, 158, 226, 230, 224, 1 }, + { 111, 249, 102, 166, 210, 52, 1 }, }, + { { 134, 184, 56, 238, 195, 91, 1 }, + { 167, 245, 250, 160, 169, 98, 0 }, + { 237, 97, 187, 142, 14, 176, 1 }, + { 35, 74, 130, 175, 215, 242, 1 }, }, + { { 138, 251, 116, 46, 23, 43, 0 }, + { 128, 231, 155, 36, 237, 47, 1 }, + { 106, 116, 58, 23, 111, 168, 1 }, + { 250, 91, 146, 108, 243, 128, 1 }, }, + { { 142, 5, 134, 252, 131, 42, 0 }, + { 209, 34, 120, 162, 39, 38, 1 }, + { 42, 96, 159, 176, 208, 56, 1 }, + { 178, 114, 34, 143, 34, 69, 1 }, }, + { { 143, 72, 60, 30, 32, 231, 0 }, + { 189, 206, 136, 20, 129, 54, 1 }, + { 115, 130, 60, 30, 9, 120, 1 }, + { 182, 64, 148, 8, 185, 222, 1 }, }, + { { 142, 83, 181, 252, 28, 224, 0 }, + { 149, 107, 192, 135, 69, 175, 1 }, + { 3, 156, 31, 214, 229, 56, 1 }, + { 250, 209, 112, 129, 235, 84, 1 }, }, + { { 141, 157, 35, 130, 73, 96, 0 }, + { 69, 147, 144, 129, 142, 176, 1 }, + { 3, 73, 32, 226, 92, 216, 1 }, + { 134, 184, 192, 132, 228, 209, 0 }, }, + { { 141, 185, 144, 117, 146, 156, 1 }, + { 19, 235, 98, 114, 45, 113, 0 }, + { 156, 164, 215, 4, 206, 216, 1 }, + { 71, 90, 39, 35, 107, 228, 0 }, }, + { { 144, 19, 230, 225, 162, 176, 0 }, + { 8, 41, 229, 226, 6, 108, 1 }, + { 6, 162, 195, 179, 228, 4, 1 }, + { 155, 48, 35, 211, 202, 8, 0 }, }, + { { 147, 155, 55, 123, 26, 144, 1 }, + { 146, 233, 196, 97, 142, 255, 0 }, + { 132, 172, 111, 118, 108, 228, 1 }, + { 127, 184, 195, 17, 203, 164, 1 }, }, + { { 151, 14, 49, 237, 242, 168, 1 }, + { 203, 248, 228, 225, 33, 59, 1 }, + { 138, 167, 219, 198, 56, 116, 1 }, + { 238, 66, 67, 147, 143, 233, 1 }, }, + { { 151, 52, 94, 59, 153, 170, 0 }, + { 241, 105, 63, 64, 162, 183, 1 }, + { 42, 204, 238, 61, 22, 116, 1 }, + { 246, 162, 129, 126, 75, 71, 1 }, }, + { { 149, 122, 253, 44, 125, 85, 0 }, + { 45, 245, 151, 23, 65, 255, 0 }, + { 85, 95, 26, 95, 175, 84, 1 }, + { 127, 193, 116, 116, 215, 218, 0 }, }, + { { 151, 221, 14, 6, 20, 80, 1 }, + { 231, 129, 4, 4, 207, 117, 0 }, + { 133, 20, 48, 56, 93, 244, 1 }, + { 87, 121, 144, 16, 64, 243, 1 }, }, + { { 154, 154, 4, 227, 189, 68, 1 }, + { 142, 163, 116, 208, 200, 173, 0 }, + { 145, 94, 227, 144, 44, 172, 1 }, + { 90, 137, 133, 151, 98, 184, 1 }, }, + { { 155, 190, 173, 173, 13, 248, 1 }, + { 230, 171, 150, 195, 105, 254, 1 }, + { 143, 216, 90, 218, 190, 236, 1 }, + { 191, 203, 97, 180, 234, 179, 1 }, }, + { { 157, 103, 144, 38, 24, 93, 0 }, + { 69, 102, 6, 22, 165, 249, 0 }, + { 93, 12, 50, 4, 243, 92, 1 }, + { 79, 210, 180, 48, 51, 81, 0 }, }, + { { 159, 129, 201, 246, 175, 127, 0 }, + { 189, 38, 125, 179, 237, 240, 1 }, + { 127, 122, 183, 201, 192, 252, 1 }, + { 135, 219, 230, 223, 50, 94, 1 }, }, + { { 159, 247, 196, 109, 149, 97, 1 }, + { 199, 39, 119, 70, 77, 63, 1 }, + { 195, 84, 219, 17, 247, 252, 1 }, + { 254, 89, 49, 119, 114, 113, 1 }, }, + { { 167, 3, 62, 38, 186, 122, 0 }, + { 173, 96, 168, 40, 167, 253, 1 }, + { 47, 46, 178, 62, 96, 114, 1 }, + { 223, 242, 138, 10, 131, 90, 1 }, }, + { { 166, 79, 109, 144, 30, 111, 0 }, + { 245, 132, 137, 189, 100, 173, 1 }, + { 123, 60, 4, 219, 121, 50, 1 }, + { 218, 147, 94, 200, 144, 215, 1 }, }, + { { 166, 84, 136, 155, 103, 3, 0 }, + { 249, 21, 24, 238, 192, 34, 0 }, + { 96, 115, 108, 136, 149, 50, 1 }, + { 34, 1, 187, 140, 84, 79, 1 }, }, + { { 164, 250, 171, 52, 23, 138, 0 }, + { 49, 169, 154, 47, 107, 41, 0 }, + { 40, 244, 22, 106, 175, 146, 1 }, + { 74, 107, 122, 44, 202, 198, 0 }, }, + { { 169, 35, 165, 79, 219, 255, 1 }, + { 6, 30, 250, 123, 165, 255, 1 }, + { 255, 237, 249, 82, 226, 74, 1 }, + { 255, 210, 239, 47, 188, 48, 0 }, }, + { { 175, 22, 89, 55, 116, 14, 1 }, + { 251, 115, 9, 89, 225, 57, 0 }, + { 184, 23, 118, 77, 52, 122, 1 }, + { 78, 67, 205, 72, 103, 111, 1 }, }, + { { 174, 60, 2, 96, 17, 226, 1 }, + { 199, 171, 90, 8, 2, 33, 1 }, + { 163, 196, 3, 32, 30, 58, 1 }, + { 194, 32, 8, 45, 106, 241, 1 }, }, + { { 174, 166, 70, 29, 70, 134, 0 }, + { 209, 26, 11, 120, 75, 46, 0 }, + { 48, 177, 92, 49, 50, 186, 1 }, + { 58, 105, 15, 104, 44, 69, 1 }, }, + { { 174, 169, 188, 206, 254, 64, 0 }, + { 173, 210, 226, 170, 205, 167, 0 }, + { 1, 63, 185, 158, 202, 186, 1 }, + { 114, 217, 170, 163, 165, 218, 1 }, }, + { { 177, 170, 233, 41, 210, 190, 0 }, + { 32, 184, 175, 123, 40, 123, 1 }, + { 62, 165, 202, 75, 170, 198, 1 }, + { 239, 10, 111, 122, 142, 130, 0 }, }, + { { 179, 236, 199, 247, 61, 40, 0 }, + { 216, 160, 87, 207, 235, 181, 1 }, + { 10, 94, 119, 241, 155, 230, 1 }, + { 214, 235, 249, 245, 2, 141, 1 }, }, + { { 179, 242, 249, 209, 5, 96, 1 }, + { 182, 65, 215, 207, 72, 56, 1 }, + { 131, 80, 69, 207, 167, 230, 1 }, + { 142, 9, 121, 245, 193, 54, 1 }, }, + { { 183, 83, 178, 95, 132, 147, 0 }, + { 145, 77, 236, 78, 199, 122, 0 }, + { 100, 144, 253, 38, 229, 118, 1 }, + { 47, 113, 185, 27, 217, 68, 1 }, }, + { { 180, 99, 115, 84, 106, 222, 0 }, + { 29, 88, 207, 61, 39, 232, 0 }, + { 61, 171, 21, 103, 99, 22, 1 }, + { 11, 242, 94, 121, 141, 92, 0 }, }, + { { 182, 97, 89, 33, 192, 136, 0 }, + { 161, 120, 39, 77, 36, 32, 0 }, + { 8, 129, 194, 77, 67, 54, 1 }, + { 2, 18, 89, 114, 15, 66, 1 }, }, + { { 186, 184, 208, 95, 68, 186, 1 }, + { 146, 219, 79, 74, 233, 98, 1 }, + { 174, 145, 125, 5, 142, 174, 1 }, + { 163, 75, 169, 121, 109, 164, 1 }, }, + { { 186, 192, 40, 124, 16, 3, 1 }, + { 178, 38, 204, 12, 9, 35, 0 }, + { 224, 4, 31, 10, 1, 174, 1 }, + { 98, 72, 24, 25, 178, 38, 1 }, }, + { { 188, 9, 19, 77, 135, 103, 0 }, + { 5, 198, 124, 121, 71, 34, 1 }, + { 115, 112, 217, 100, 72, 30, 1 }, + { 162, 113, 79, 31, 49, 208, 0 }, }, + { { 190, 60, 15, 68, 249, 12, 0 }, + { 233, 147, 118, 25, 35, 165, 0 }, + { 24, 79, 145, 120, 30, 62, 1 }, + { 82, 226, 76, 55, 100, 203, 1 }, }, + { { 193, 39, 249, 24, 172, 4, 0 }, + { 120, 64, 163, 19, 84, 186, 0 }, + { 16, 26, 140, 79, 242, 65, 1 }, + { 46, 149, 100, 98, 129, 15, 0 }, }, + { { 194, 54, 103, 175, 248, 99, 1 }, + { 206, 53, 171, 193, 147, 175, 1 }, + { 227, 15, 250, 243, 54, 33, 1 }, + { 250, 228, 193, 234, 214, 57, 1 }, }, + { { 194, 202, 232, 228, 90, 50, 0 }, + { 160, 176, 201, 166, 25, 233, 1 }, + { 38, 45, 19, 139, 169, 161, 1 }, + { 203, 204, 50, 201, 134, 130, 1 }, }, + { { 198, 18, 22, 32, 228, 28, 0 }, + { 137, 113, 32, 16, 114, 108, 0 }, + { 28, 19, 130, 52, 36, 49, 1 }, + { 27, 39, 4, 2, 71, 72, 1 }, }, + { { 199, 229, 93, 208, 65, 150, 0 }, + { 241, 88, 91, 149, 28, 116, 0 }, + { 52, 193, 5, 221, 83, 241, 1 }, + { 23, 28, 84, 237, 13, 71, 1 }, }, + { { 202, 89, 204, 102, 248, 236, 0 }, + { 172, 187, 97, 22, 181, 165, 1 }, + { 27, 143, 179, 25, 205, 41, 1 }, + { 210, 214, 180, 67, 110, 154, 1 }, }, + { { 200, 251, 13, 58, 124, 119, 1 }, + { 62, 183, 10, 21, 220, 239, 1 }, + { 247, 31, 46, 88, 111, 137, 1 }, + { 251, 157, 212, 40, 118, 190, 0 }, }, + { { 204, 35, 191, 4, 244, 230, 0 }, + { 45, 90, 170, 19, 87, 45, 1 }, + { 51, 151, 144, 126, 226, 25, 1 }, + { 218, 117, 100, 42, 173, 90, 0 }, }, + { { 204, 185, 29, 222, 17, 77, 0 }, + { 53, 199, 82, 145, 189, 39, 0 }, + { 89, 68, 61, 220, 78, 153, 1 }, + { 114, 94, 196, 165, 113, 214, 0 }, }, + { { 214, 71, 53, 203, 221, 81, 1 }, + { 199, 84, 244, 197, 212, 239, 0 }, + { 197, 93, 233, 214, 113, 53, 1 }, + { 123, 149, 209, 151, 149, 113, 1 }, }, + { { 212, 80, 13, 82, 140, 243, 1 }, + { 55, 13, 108, 5, 208, 228, 1 }, + { 231, 152, 165, 88, 5, 21, 1 }, + { 147, 133, 208, 27, 88, 118, 0 }, }, + { { 214, 126, 50, 1, 136, 117, 0 }, + { 197, 197, 166, 84, 18, 232, 1 }, + { 87, 8, 192, 38, 63, 53, 1 }, + { 139, 164, 21, 50, 209, 209, 1 }, }, + { { 214, 239, 103, 48, 199, 69, 1 }, + { 215, 180, 183, 53, 94, 44, 0 }, + { 209, 113, 134, 115, 123, 181, 1 }, + { 26, 61, 86, 118, 150, 245, 1 }, }, + { { 217, 10, 193, 255, 8, 72, 0 }, + { 20, 162, 69, 195, 177, 186, 0 }, + { 9, 8, 127, 193, 168, 77, 1 }, + { 46, 198, 225, 209, 34, 148, 0 }, }, + { { 218, 68, 144, 181, 192, 149, 0 }, + { 208, 126, 36, 214, 17, 96, 0 }, + { 84, 129, 214, 132, 145, 45, 1 }, + { 3, 68, 53, 146, 63, 5, 1 }, }, + { { 218, 142, 240, 138, 31, 78, 1 }, + { 198, 194, 157, 178, 248, 171, 0 }, + { 185, 124, 40, 135, 184, 173, 1 }, + { 106, 143, 166, 220, 161, 177, 1 }, }, + { { 226, 186, 72, 154, 71, 244, 1 }, + { 182, 153, 19, 184, 216, 106, 1 }, + { 151, 241, 44, 137, 46, 163, 1 }, + { 171, 13, 142, 228, 76, 182, 1 }, }, + { { 226, 251, 142, 197, 152, 25, 0 }, + { 160, 133, 98, 206, 63, 237, 0 }, + { 76, 12, 209, 184, 239, 163, 1 }, + { 91, 254, 57, 163, 80, 130, 1 }, }, + { { 229, 62, 6, 191, 131, 16, 1 }, + { 83, 161, 50, 232, 147, 126, 0 }, + { 132, 96, 254, 176, 62, 83, 1 }, + { 63, 100, 139, 166, 66, 229, 0 }, }, + { { 230, 135, 176, 105, 180, 112, 0 }, + { 205, 96, 224, 74, 92, 107, 1 }, + { 7, 22, 203, 6, 240, 179, 1 }, + { 235, 29, 41, 3, 131, 89, 1 }, }, + { { 229, 161, 35, 223, 231, 210, 0 }, + { 29, 24, 250, 233, 223, 114, 0 }, + { 37, 243, 253, 226, 66, 211, 1 }, + { 39, 125, 203, 175, 140, 92, 0 }, }, + { { 228, 231, 74, 54, 176, 3, 0 }, + { 121, 36, 43, 12, 159, 41, 0 }, + { 96, 6, 182, 41, 115, 147, 1 }, + { 74, 124, 152, 106, 18, 79, 0 }, }, + { { 235, 25, 74, 181, 219, 227, 0 }, + { 180, 191, 57, 232, 23, 177, 1 }, + { 99, 237, 214, 169, 76, 107, 1 }, + { 198, 244, 11, 206, 126, 150, 1 }, }, + { { 235, 104, 214, 214, 135, 188, 0 }, + { 144, 202, 115, 190, 243, 116, 1 }, + { 30, 240, 181, 181, 139, 107, 1 }, + { 151, 103, 190, 231, 41, 132, 1 }, }, + { { 239, 76, 157, 25, 92, 242, 0 }, + { 245, 218, 8, 79, 80, 247, 1 }, + { 39, 157, 76, 92, 153, 123, 1 }, + { 247, 133, 121, 8, 45, 215, 1 }, }, + { { 240, 140, 65, 178, 161, 79, 1 }, + { 94, 164, 61, 153, 184, 32, 0 }, + { 249, 66, 166, 193, 24, 135, 1 }, + { 2, 14, 204, 222, 18, 189, 0 }, }, + { { 245, 42, 227, 102, 145, 91, 0 }, + { 5, 164, 255, 11, 179, 121, 0 }, + { 109, 68, 179, 99, 170, 87, 1 }, + { 79, 102, 232, 127, 146, 208, 0 }, }, + { { 247, 127, 207, 238, 89, 167, 0 }, + { 225, 189, 95, 159, 151, 191, 1 }, + { 114, 205, 59, 249, 255, 119, 1 }, + { 254, 244, 252, 253, 94, 195, 1 }, }, + { { 248, 218, 47, 9, 127, 235, 0 }, + { 44, 159, 156, 109, 122, 175, 1 }, + { 107, 255, 72, 122, 45, 143, 1 }, + { 250, 175, 91, 28, 252, 154, 0 }, }, + { { 252, 25, 236, 171, 9, 228, 1 }, + { 39, 171, 149, 218, 148, 166, 1 }, + { 147, 200, 106, 155, 204, 31, 1 }, + { 178, 148, 173, 212, 234, 242, 0 }, }, + { { 147, 75, 97, 136, 48, 21, 0 }, + { 136, 132, 133, 149, 4, 123, 0 }, + { 84, 6, 8, 195, 105, 100, 1 }, + { 111, 16, 84, 208, 144, 136, 1 }, }, + { { 200, 173, 102, 37, 128, 21, 1 }, + { 66, 166, 163, 80, 31, 100, 0 }, + { 212, 0, 210, 51, 90, 137, 1 }, + { 19, 124, 5, 98, 178, 161, 0 }, }, + { { 28, 218, 21, 111, 160, 160, 1 }, + { 11, 235, 100, 69, 137, 14, 1 }, + { 130, 130, 251, 84, 45, 156, 0 }, + { 184, 72, 209, 19, 107, 232, 0 }, }, + { { 35, 147, 244, 147, 165, 30, 0 }, + { 152, 65, 185, 218, 236, 92, 0 }, + { 60, 82, 228, 151, 228, 226, 0 }, + { 29, 27, 173, 206, 193, 12, 1 }, }, + { { 47, 88, 146, 189, 228, 140, 1 }, + { 155, 251, 32, 222, 99, 18, 0 }, + { 152, 147, 222, 164, 141, 122, 0 }, + { 36, 99, 61, 130, 111, 236, 1 }, }, + { { 67, 161, 247, 150, 104, 223, 0 }, + { 156, 92, 139, 147, 191, 212, 0 }, + { 125, 139, 52, 247, 194, 225, 0 }, + { 21, 254, 228, 232, 157, 28, 1 }, }, + { { 73, 80, 111, 179, 116, 206, 1 }, + { 62, 59, 137, 213, 242, 21, 0 }, + { 185, 151, 102, 251, 5, 73, 0 }, + { 84, 39, 213, 200, 238, 62, 0 }, }, + { { 82, 96, 94, 14, 152, 127, 0 }, + { 164, 68, 47, 20, 179, 199, 1 }, + { 127, 12, 184, 61, 3, 37, 0 }, + { 241, 230, 148, 122, 17, 18, 1 }, }, + { { 82, 145, 24, 227, 252, 161, 0 }, + { 168, 125, 100, 192, 220, 129, 1 }, + { 66, 159, 227, 140, 68, 165, 0 }, + { 192, 157, 129, 147, 95, 10, 1 }, }, + { { 101, 223, 133, 82, 42, 63, 1 }, + { 91, 133, 72, 63, 188, 220, 1 }, + { 254, 42, 37, 80, 253, 211, 0 }, + { 157, 158, 254, 9, 80, 237, 0 }, }, + { { 113, 72, 12, 215, 43, 160, 0 }, + { 56, 136, 84, 236, 145, 148, 1 }, + { 2, 234, 117, 152, 9, 71, 0 }, + { 148, 196, 155, 149, 8, 142, 0 }, }, + { { 142, 6, 96, 125, 160, 37, 0 }, + { 217, 38, 225, 80, 1, 42, 1 }, + { 82, 2, 223, 3, 48, 56, 1 }, + { 170, 64, 5, 67, 178, 77, 1 }, }, + { { 150, 170, 33, 3, 84, 112, 1 }, + { 135, 144, 134, 65, 200, 105, 1 }, + { 135, 21, 96, 66, 42, 180, 1 }, + { 203, 9, 193, 48, 132, 240, 1 }, }, + { { 161, 37, 174, 137, 93, 100, 1 }, + { 102, 16, 146, 218, 70, 183, 1 }, + { 147, 93, 72, 186, 210, 66, 1 }, + { 246, 177, 45, 164, 132, 51, 0 }, }, + { { 188, 234, 86, 10, 46, 231, 1 }, + { 15, 206, 15, 60, 202, 174, 1 }, + { 243, 186, 40, 53, 43, 158, 1 }, + { 186, 169, 158, 120, 57, 248, 0 }, }, + { { 194, 210, 70, 164, 139, 68, 0 }, + { 132, 33, 49, 180, 27, 172, 0 }, + { 17, 104, 146, 177, 37, 161, 1 }, + { 26, 236, 22, 198, 66, 16, 1 }, }, + { { 218, 226, 149, 139, 69, 176, 0 }, + { 128, 90, 22, 199, 216, 110, 1 }, + { 6, 209, 104, 212, 163, 173, 1 }, + { 187, 13, 241, 180, 45, 0, 1 }, }, + { { 236, 63, 99, 213, 29, 216, 0 }, + { 85, 139, 211, 201, 119, 233, 0 }, + { 13, 220, 85, 227, 126, 27, 1 }, + { 75, 247, 73, 229, 232, 213, 0 }, }, + { { 255, 105, 45, 60, 107, 15, 0 }, + { 185, 182, 158, 61, 53, 182, 0 }, + { 120, 107, 30, 90, 75, 127, 1 }, + { 54, 214, 94, 60, 182, 206, 1 }, }, + { { 0, 28, 79, 167, 70, 146, 1 }, + { 98, 185, 9, 225, 195, 68, 0 }, + { 164, 177, 114, 249, 28, 0, 0 }, + { 17, 97, 195, 200, 78, 163, 0 }, }, + { { 2, 24, 244, 93, 202, 150, 0 }, + { 144, 217, 233, 114, 1, 198, 0 }, + { 52, 169, 221, 23, 140, 32, 0 }, + { 49, 192, 39, 75, 205, 132, 1 }, }, + { { 3, 209, 163, 72, 235, 88, 0 }, + { 140, 17, 240, 39, 46, 210, 0 }, + { 13, 107, 137, 98, 197, 224, 0 }, + { 37, 186, 114, 7, 196, 24, 1 }, }, + { { 4, 27, 131, 253, 41, 154, 1 }, + { 27, 169, 88, 195, 39, 202, 0 }, + { 172, 202, 95, 224, 236, 16, 0 }, + { 41, 242, 97, 141, 74, 236, 0 }, }, + { { 9, 141, 142, 139, 148, 215, 1 }, + { 102, 142, 40, 210, 206, 87, 0 }, + { 245, 148, 232, 184, 216, 200, 0 }, + { 117, 57, 165, 138, 56, 179, 0 }, }, + { { 8, 163, 222, 26, 89, 139, 0 }, + { 48, 94, 27, 2, 174, 143, 0 }, + { 104, 205, 44, 61, 226, 136, 0 }, + { 120, 186, 160, 108, 61, 6, 0 }, }, + { { 11, 188, 12, 8, 32, 24, 0 }, + { 232, 131, 2, 0, 40, 86, 0 }, + { 12, 2, 8, 24, 30, 232, 0 }, + { 53, 10, 0, 32, 96, 139, 1 }, }, + { { 8, 180, 118, 44, 124, 199, 0 }, + { 76, 127, 139, 16, 75, 135, 0 }, + { 113, 159, 26, 55, 22, 136, 0 }, + { 112, 233, 4, 104, 255, 25, 0 }, }, + { { 8, 252, 181, 200, 85, 47, 1 }, + { 66, 215, 218, 151, 104, 7, 1 }, + { 250, 85, 9, 214, 159, 136, 0 }, + { 240, 11, 116, 173, 245, 161, 0 }, }, + { { 14, 22, 54, 84, 254, 34, 0 }, + { 217, 83, 232, 32, 67, 141, 1 }, + { 34, 63, 149, 54, 52, 56, 0 }, + { 216, 225, 2, 11, 229, 77, 1 }, }, + { { 18, 44, 177, 159, 7, 34, 1 }, + { 210, 192, 158, 227, 193, 2, 1 }, + { 162, 112, 124, 198, 154, 36, 0 }, + { 160, 65, 227, 188, 129, 165, 1 }, }, + { { 18, 69, 156, 171, 22, 232, 0 }, + { 228, 104, 4, 230, 228, 7, 1 }, + { 11, 180, 106, 156, 209, 36, 0 }, + { 240, 19, 179, 144, 11, 19, 1 }, }, + { { 19, 147, 126, 245, 174, 67, 1 }, + { 190, 101, 237, 224, 79, 156, 0 }, + { 225, 58, 215, 191, 100, 228, 0 }, + { 28, 249, 3, 219, 211, 62, 1 }, }, + { { 22, 41, 244, 18, 48, 140, 0 }, + { 153, 200, 135, 18, 164, 5, 0 }, + { 24, 134, 36, 23, 202, 52, 0 }, + { 80, 18, 164, 112, 137, 204, 1 }, }, + { { 21, 124, 103, 88, 122, 66, 0 }, + { 93, 145, 207, 37, 2, 151, 0 }, + { 33, 47, 13, 115, 31, 84, 0 }, + { 116, 160, 82, 121, 196, 221, 0 }, }, + { { 21, 204, 177, 89, 131, 214, 1 }, + { 87, 200, 252, 119, 8, 82, 0 }, + { 181, 224, 205, 70, 153, 212, 0 }, + { 37, 8, 119, 31, 137, 245, 0 }, }, + { { 23, 227, 47, 161, 221, 185, 0 }, + { 161, 60, 182, 197, 110, 221, 1 }, + { 78, 221, 194, 250, 99, 244, 0 }, + { 221, 187, 81, 182, 158, 66, 1 }, }, + { { 27, 70, 90, 142, 239, 10, 1 }, + { 234, 82, 61, 164, 227, 154, 0 }, + { 168, 123, 184, 173, 49, 108, 0 }, + { 44, 227, 146, 222, 37, 43, 1 }, }, + { { 27, 105, 78, 73, 179, 188, 1 }, + { 170, 138, 119, 116, 38, 87, 1 }, + { 158, 230, 201, 57, 75, 108, 0 }, + { 245, 50, 23, 119, 40, 170, 1 }, }, + { { 27, 196, 203, 219, 144, 138, 1 }, + { 242, 10, 109, 199, 170, 19, 0 }, + { 168, 132, 237, 233, 145, 236, 0 }, + { 100, 42, 241, 219, 40, 39, 1 }, }, + { { 31, 44, 109, 141, 119, 17, 1 }, + { 235, 150, 151, 225, 65, 87, 0 }, + { 196, 119, 88, 219, 26, 124, 0 }, + { 117, 65, 67, 244, 180, 235, 1 }, }, + { { 31, 87, 63, 15, 8, 234, 1 }, + { 231, 75, 140, 69, 167, 158, 1 }, + { 171, 136, 120, 126, 117, 124, 0 }, + { 188, 242, 209, 24, 233, 115, 1 }, }, + { { 28, 109, 100, 176, 216, 126, 1 }, + { 87, 178, 175, 148, 36, 197, 1 }, + { 191, 13, 134, 147, 91, 28, 0 }, + { 209, 146, 20, 250, 166, 245, 0 }, }, + { { 31, 214, 51, 233, 39, 78, 1 }, + { 207, 99, 220, 245, 106, 26, 0 }, + { 185, 114, 75, 230, 53, 252, 0 }, + { 44, 43, 87, 157, 227, 121, 1 }, }, + { { 31, 237, 94, 0, 105, 171, 1 }, + { 235, 222, 31, 4, 46, 148, 1 }, + { 234, 203, 0, 61, 91, 252, 0 }, + { 148, 186, 16, 124, 61, 235, 1 }, }, + { { 32, 25, 133, 43, 191, 166, 0 }, + { 8, 169, 56, 123, 196, 135, 1 }, + { 50, 254, 234, 80, 204, 2, 0 }, + { 240, 145, 239, 14, 74, 136, 0 }, }, + { { 34, 61, 51, 120, 7, 207, 0 }, + { 212, 237, 218, 57, 102, 2, 0 }, + { 121, 240, 15, 102, 94, 34, 0 }, + { 32, 51, 78, 45, 219, 149, 1 }, }, + { { 32, 99, 33, 28, 107, 49, 0 }, + { 24, 20, 146, 45, 5, 202, 1 }, + { 70, 107, 28, 66, 99, 2, 0 }, + { 169, 208, 90, 36, 148, 12, 0 }, }, + { { 34, 110, 87, 72, 118, 216, 1 }, + { 206, 216, 67, 45, 98, 79, 0 }, + { 141, 183, 9, 117, 59, 34, 0 }, + { 121, 35, 90, 97, 13, 185, 1 }, }, + { { 32, 116, 241, 178, 248, 194, 1 }, + { 94, 121, 171, 143, 128, 129, 0 }, + { 161, 143, 166, 199, 151, 2, 0 }, + { 64, 128, 248, 234, 207, 61, 0 }, }, + { { 33, 172, 61, 93, 237, 110, 0 }, + { 124, 208, 250, 89, 105, 150, 1 }, + { 59, 91, 221, 94, 26, 194, 0 }, + { 180, 203, 77, 47, 133, 159, 0 }, }, + { { 33, 195, 23, 29, 144, 62, 1 }, + { 18, 64, 40, 93, 47, 95, 1 }, + { 190, 4, 220, 116, 97, 194, 0 }, + { 253, 122, 93, 10, 1, 36, 0 }, }, + { { 39, 47, 226, 196, 67, 84, 0 }, + { 197, 144, 211, 186, 7, 88, 0 }, + { 21, 97, 17, 163, 250, 114, 0 }, + { 13, 112, 46, 229, 132, 209, 1 }, }, + { { 39, 159, 50, 224, 232, 133, 0 }, + { 201, 253, 224, 152, 14, 152, 0 }, + { 80, 139, 131, 166, 124, 242, 0 }, + { 12, 184, 12, 131, 223, 201, 1 }, }, + { { 38, 190, 33, 114, 154, 9, 1 }, + { 211, 165, 226, 41, 168, 137, 0 }, + { 200, 44, 167, 66, 62, 178, 0 }, + { 72, 138, 202, 35, 210, 229, 1 }, }, + { { 42, 3, 57, 247, 158, 125, 1 }, + { 182, 102, 224, 249, 229, 201, 1 }, + { 223, 60, 247, 206, 96, 42, 0 }, + { 201, 211, 207, 131, 179, 54, 1 }, }, + { { 43, 24, 147, 239, 72, 235, 0 }, + { 132, 255, 72, 203, 163, 146, 1 }, + { 107, 137, 123, 228, 140, 106, 0 }, + { 164, 226, 233, 137, 127, 144, 1 }, }, + { { 41, 20, 232, 100, 42, 8, 0 }, + { 104, 35, 193, 42, 33, 144, 0 }, + { 8, 42, 19, 11, 148, 74, 0 }, + { 4, 194, 42, 65, 226, 11, 0 }, }, + { { 41, 104, 166, 84, 120, 185, 0 }, + { 24, 158, 194, 14, 35, 213, 1 }, + { 78, 143, 21, 50, 139, 74, 0 }, + { 213, 226, 56, 33, 188, 140, 0 }, }, + { { 42, 193, 129, 37, 248, 55, 0 }, + { 136, 54, 40, 95, 13, 193, 1 }, + { 118, 15, 210, 64, 193, 170, 0 }, + { 193, 216, 125, 10, 54, 8, 1 }, }, + { { 40, 212, 32, 190, 102, 221, 1 }, + { 94, 63, 128, 188, 233, 66, 0 }, + { 221, 179, 62, 130, 21, 138, 0 }, + { 33, 75, 158, 128, 254, 61, 0 }, }, + { { 41, 251, 7, 40, 147, 64, 0 }, + { 4, 163, 50, 45, 14, 31, 0 }, + { 1, 100, 138, 112, 111, 202, 0 }, + { 124, 56, 90, 38, 98, 144, 0 }, }, + { { 45, 79, 27, 209, 68, 171, 0 }, + { 113, 222, 72, 205, 102, 24, 1 }, + { 106, 145, 69, 236, 121, 90, 0 }, + { 140, 51, 89, 137, 61, 199, 0 }, }, + { { 44, 77, 58, 93, 125, 209, 0 }, + { 125, 222, 208, 76, 71, 195, 0 }, + { 69, 223, 93, 46, 89, 26, 0 }, + { 97, 241, 25, 5, 189, 223, 0 }, }, + { { 46, 201, 163, 115, 80, 78, 1 }, + { 151, 178, 200, 95, 174, 1, 0 }, + { 185, 5, 103, 98, 201, 186, 0 }, + { 64, 58, 253, 9, 166, 244, 1 }, }, + { { 47, 242, 157, 56, 166, 39, 0 }, + { 185, 103, 42, 63, 72, 30, 1 }, + { 114, 50, 142, 92, 167, 250, 0 }, + { 188, 9, 126, 42, 115, 78, 1 }, }, + { { 51, 14, 242, 29, 118, 78, 0 }, + { 220, 208, 141, 122, 99, 27, 0 }, + { 57, 55, 92, 39, 184, 102, 0 }, + { 108, 99, 47, 88, 133, 157, 1 }, }, + { { 51, 46, 161, 171, 153, 4, 0 }, + { 192, 160, 182, 219, 128, 155, 0 }, + { 16, 76, 234, 194, 186, 102, 0 }, + { 108, 128, 237, 182, 130, 129, 1 }, }, + { { 49, 41, 200, 243, 224, 220, 1 }, + { 62, 184, 103, 218, 164, 80, 0 }, + { 157, 131, 231, 137, 202, 70, 0 }, + { 5, 18, 173, 243, 14, 190, 0 }, }, + { { 53, 0, 254, 245, 196, 8, 0 }, + { 49, 112, 229, 202, 99, 20, 0 }, + { 8, 17, 215, 191, 128, 86, 0 }, + { 20, 99, 41, 211, 135, 70, 0 }, }, + { { 55, 36, 110, 62, 36, 63, 0 }, + { 249, 36, 143, 24, 227, 86, 1 }, + { 126, 18, 62, 59, 18, 118, 0 }, + { 181, 99, 140, 120, 146, 79, 1 }, }, + { { 53, 81, 121, 197, 146, 92, 1 }, + { 39, 65, 229, 253, 37, 81, 0 }, + { 157, 36, 209, 207, 69, 86, 0 }, + { 69, 82, 95, 211, 193, 114, 0 }, }, + { { 54, 184, 189, 91, 81, 60, 0 }, + { 177, 209, 214, 91, 168, 71, 1 }, + { 30, 69, 109, 94, 142, 182, 0 }, + { 241, 10, 237, 53, 197, 198, 1 }, }, + { { 54, 221, 188, 202, 44, 119, 0 }, + { 237, 197, 204, 158, 204, 198, 1 }, + { 119, 26, 41, 158, 221, 182, 0 }, + { 177, 153, 188, 153, 209, 219, 1 }, }, + { { 55, 221, 249, 250, 116, 140, 0 }, + { 249, 249, 197, 159, 236, 19, 0 }, + { 24, 151, 47, 207, 221, 246, 0 }, + { 100, 27, 252, 209, 207, 207, 1 }, }, + { { 57, 8, 64, 158, 68, 97, 1 }, + { 22, 150, 5, 136, 193, 18, 1 }, + { 195, 17, 60, 129, 8, 78, 0 }, + { 164, 65, 136, 208, 52, 180, 0 }, }, + { { 59, 1, 93, 14, 177, 201, 1 }, + { 174, 78, 53, 9, 165, 23, 0 }, + { 201, 198, 184, 93, 64, 110, 0 }, + { 116, 82, 200, 86, 57, 58, 1 }, }, + { { 58, 14, 216, 225, 88, 0, 1 }, + { 226, 242, 69, 202, 0, 137, 0 }, + { 128, 13, 67, 141, 184, 46, 0 }, + { 72, 128, 41, 209, 39, 163, 1 }, }, + { { 56, 110, 40, 10, 241, 243, 1 }, + { 110, 158, 190, 12, 128, 75, 1 }, + { 231, 199, 168, 10, 59, 14, 0 }, + { 233, 0, 152, 62, 188, 187, 0 }, }, + { { 57, 97, 243, 168, 199, 88, 1 }, + { 6, 114, 183, 175, 102, 82, 0 }, + { 141, 113, 138, 231, 195, 78, 0 }, + { 37, 51, 122, 246, 167, 48, 0 }, }, + { { 57, 175, 5, 221, 138, 171, 1 }, + { 82, 142, 110, 233, 45, 158, 1 }, + { 234, 168, 221, 208, 122, 206, 0 }, + { 188, 218, 75, 187, 56, 165, 0 }, }, + { { 57, 172, 253, 140, 32, 166, 1 }, + { 106, 202, 143, 155, 9, 22, 1 }, + { 178, 130, 24, 223, 154, 206, 0 }, + { 180, 72, 108, 248, 169, 171, 0 }, }, + { { 56, 215, 227, 25, 189, 206, 1 }, + { 94, 11, 189, 95, 110, 139, 0 }, + { 185, 222, 204, 99, 245, 142, 0 }, + { 104, 187, 125, 94, 232, 61, 0 }, }, + { { 62, 29, 177, 174, 155, 223, 1 }, + { 199, 239, 188, 187, 165, 195, 0 }, + { 253, 236, 186, 198, 220, 62, 0 }, + { 97, 210, 238, 158, 251, 241, 1 }, }, + { { 63, 57, 54, 16, 131, 58, 1 }, + { 147, 195, 190, 40, 38, 84, 1 }, + { 174, 96, 132, 54, 78, 126, 0 }, + { 149, 50, 10, 62, 225, 228, 1 }, }, + { { 61, 68, 172, 103, 73, 121, 1 }, + { 103, 54, 212, 78, 161, 212, 1 }, + { 207, 73, 115, 26, 145, 94, 0 }, + { 149, 194, 185, 21, 182, 115, 0 }, }, + { { 62, 216, 120, 160, 212, 247, 1 }, + { 167, 255, 173, 156, 72, 65, 1 }, + { 247, 149, 130, 143, 13, 190, 0 }, + { 193, 9, 28, 218, 255, 242, 1 }, }, + { { 67, 45, 92, 111, 10, 140, 0 }, + { 224, 232, 67, 112, 181, 150, 0 }, + { 24, 168, 123, 29, 90, 97, 0 }, + { 52, 214, 135, 97, 11, 131, 1 }, }, + { { 66, 52, 3, 145, 156, 82, 1 }, + { 214, 1, 42, 193, 82, 193, 0 }, + { 165, 28, 196, 224, 22, 33, 0 }, + { 65, 165, 65, 170, 64, 53, 1 }, }, + { { 64, 59, 107, 130, 137, 239, 0 }, + { 36, 141, 187, 145, 182, 136, 1 }, + { 123, 200, 160, 235, 110, 1, 0 }, + { 136, 182, 196, 238, 216, 146, 0 }, }, + { { 67, 109, 145, 73, 70, 116, 0 }, + { 196, 208, 66, 119, 84, 82, 1 }, + { 23, 49, 73, 68, 219, 97, 0 }, + { 165, 21, 119, 33, 5, 145, 1 }, }, + { { 67, 151, 133, 107, 233, 44, 1 }, + { 202, 49, 112, 83, 188, 158, 1 }, + { 154, 75, 235, 80, 244, 225, 0 }, + { 188, 158, 229, 7, 70, 41, 1 }, }, + { { 71, 29, 159, 177, 209, 0, 1 }, + { 243, 241, 48, 195, 22, 21, 0 }, + { 128, 69, 198, 252, 220, 113, 0 }, + { 84, 52, 97, 134, 71, 231, 1 }, }, + { { 70, 43, 251, 166, 87, 121, 0 }, + { 165, 244, 147, 163, 247, 73, 1 }, + { 79, 117, 50, 239, 234, 49, 0 }, + { 201, 119, 226, 228, 151, 210, 1 }, }, + { { 70, 92, 73, 248, 0, 58, 1 }, + { 243, 161, 73, 133, 48, 66, 1 }, + { 174, 0, 15, 201, 29, 49, 0 }, + { 161, 6, 80, 201, 66, 231, 1 }, }, + { { 69, 118, 247, 32, 48, 38, 1 }, + { 75, 97, 139, 23, 18, 29, 1 }, + { 178, 6, 2, 119, 183, 81, 0 }, + { 220, 36, 116, 104, 195, 105, 0 }, }, + { { 70, 168, 40, 69, 154, 252, 0 }, + { 165, 136, 226, 112, 57, 193, 1 }, + { 31, 172, 209, 10, 10, 177, 0 }, + { 193, 206, 7, 35, 136, 210, 1 }, }, + { { 68, 160, 107, 94, 1, 10, 0 }, + { 49, 0, 219, 1, 187, 2, 0 }, + { 40, 64, 61, 107, 2, 145, 0 }, + { 32, 110, 192, 109, 128, 70, 0 }, }, + { { 69, 185, 78, 113, 53, 37, 0 }, + { 57, 165, 83, 80, 94, 21, 1 }, + { 82, 86, 71, 57, 78, 209, 0 }, + { 212, 61, 5, 101, 82, 206, 0 }, }, + { { 75, 113, 215, 160, 253, 197, 1 }, + { 142, 127, 51, 151, 86, 149, 0 }, + { 209, 223, 130, 245, 199, 105, 0 }, + { 84, 181, 116, 230, 127, 56, 1 }, }, + { { 79, 60, 121, 82, 108, 209, 1 }, + { 255, 223, 195, 1, 208, 208, 0 }, + { 197, 155, 37, 79, 30, 121, 0 }, + { 5, 133, 192, 97, 253, 255, 1 }, }, + { { 79, 178, 80, 33, 70, 98, 0 }, + { 133, 115, 11, 96, 88, 24, 1 }, + { 35, 49, 66, 5, 38, 249, 0 }, + { 140, 13, 3, 104, 103, 80, 1 }, }, + { { 79, 239, 177, 247, 108, 238, 1 }, + { 223, 250, 202, 215, 253, 152, 1 }, + { 187, 155, 119, 198, 251, 249, 0 }, + { 140, 223, 245, 169, 175, 253, 1 }, }, + { { 82, 75, 82, 250, 189, 204, 0 }, + { 156, 232, 117, 148, 246, 139, 0 }, + { 25, 222, 175, 165, 105, 37, 0 }, + { 104, 183, 148, 215, 11, 156, 1 }, }, + { { 83, 200, 244, 221, 123, 200, 1 }, + { 158, 216, 213, 230, 57, 151, 0 }, + { 137, 239, 93, 151, 137, 229, 0 }, + { 116, 206, 51, 213, 141, 188, 1 }, }, + { { 87, 5, 99, 141, 134, 48, 0 }, + { 193, 0, 165, 225, 87, 82, 1 }, + { 6, 48, 216, 227, 80, 117, 0 }, + { 165, 117, 67, 210, 128, 65, 1 }, }, + { { 85, 75, 225, 85, 227, 0, 0 }, + { 25, 144, 245, 103, 21, 24, 0 }, + { 0, 99, 213, 67, 233, 85, 0 }, + { 12, 84, 115, 87, 132, 204, 0 }, }, + { { 85, 138, 47, 175, 183, 98, 0 }, + { 45, 160, 188, 225, 219, 31, 1 }, + { 35, 118, 250, 250, 40, 213, 0 }, + { 252, 109, 195, 158, 130, 218, 0 }, }, + { { 84, 153, 49, 31, 160, 83, 0 }, + { 29, 197, 172, 65, 157, 66, 0 }, + { 101, 2, 252, 70, 76, 149, 0 }, + { 33, 92, 193, 26, 209, 220, 0 }, }, + { { 88, 13, 255, 122, 69, 176, 1 }, + { 114, 250, 213, 3, 214, 70, 1 }, + { 134, 209, 47, 127, 216, 13, 0 }, + { 177, 53, 224, 85, 175, 167, 0 }, }, + { { 90, 18, 17, 208, 247, 52, 1 }, + { 154, 83, 116, 177, 80, 73, 1 }, + { 150, 119, 133, 196, 36, 45, 0 }, + { 201, 5, 70, 151, 101, 44, 1 }, }, + { { 95, 17, 24, 22, 24, 164, 0 }, + { 177, 75, 4, 16, 149, 145, 1 }, + { 18, 140, 52, 12, 68, 125, 0 }, + { 196, 212, 132, 16, 105, 70, 1 }, }, + { { 93, 61, 87, 179, 227, 47, 0 }, + { 89, 247, 63, 241, 182, 20, 1 }, + { 122, 99, 230, 245, 94, 93, 0 }, + { 148, 54, 199, 254, 119, 205, 0 }, }, + { { 93, 74, 155, 244, 162, 166, 0 }, + { 57, 234, 108, 183, 19, 24, 1 }, + { 50, 162, 151, 236, 169, 93, 0 }, + { 140, 100, 118, 155, 43, 206, 0 }, }, + { { 94, 137, 78, 179, 16, 89, 0 }, + { 181, 166, 5, 192, 190, 69, 0 }, + { 77, 4, 102, 185, 72, 189, 0 }, + { 81, 62, 129, 208, 50, 214, 1 }, }, + { { 95, 182, 4, 116, 142, 30, 1 }, + { 211, 35, 110, 48, 121, 220, 0 }, + { 188, 56, 151, 16, 54, 253, 0 }, + { 29, 207, 6, 59, 98, 101, 1 }, }, + { { 95, 252, 162, 7, 10, 47, 1 }, + { 195, 135, 142, 118, 187, 144, 1 }, + { 250, 40, 112, 34, 159, 253, 0 }, + { 132, 238, 183, 56, 240, 225, 1 }, }, + { { 98, 20, 52, 163, 198, 43, 1 }, + { 194, 117, 168, 232, 240, 4, 1 }, + { 234, 49, 226, 150, 20, 35, 0 }, + { 144, 7, 139, 138, 215, 33, 1 }, }, + { { 96, 69, 249, 159, 75, 213, 0 }, + { 116, 92, 145, 255, 149, 194, 0 }, + { 85, 233, 124, 207, 209, 3, 0 }, + { 33, 212, 255, 196, 157, 23, 0 }, }, + { { 96, 94, 235, 202, 23, 75, 1 }, + { 102, 133, 217, 175, 242, 11, 0 }, + { 233, 116, 41, 235, 189, 3, 0 }, + { 104, 39, 250, 205, 208, 179, 0 }, }, + { { 97, 111, 101, 43, 244, 79, 0 }, + { 76, 180, 171, 93, 244, 31, 0 }, + { 121, 23, 234, 83, 123, 67, 0 }, + { 124, 23, 221, 106, 150, 153, 0 }, }, + { { 96, 111, 231, 237, 175, 53, 0 }, + { 72, 164, 243, 255, 87, 206, 1 }, + { 86, 122, 219, 243, 251, 3, 0 }, + { 185, 245, 127, 231, 146, 137, 0 }, }, + { { 98, 227, 54, 39, 167, 213, 0 }, + { 140, 108, 178, 124, 223, 76, 0 }, + { 85, 242, 242, 54, 99, 163, 0 }, + { 25, 125, 159, 38, 155, 24, 1 }, }, + { { 101, 50, 13, 146, 242, 6, 0 }, + { 57, 17, 42, 185, 144, 29, 0 }, + { 48, 39, 164, 216, 38, 83, 0 }, + { 92, 4, 206, 170, 68, 78, 0 }, }, + { { 101, 174, 116, 178, 81, 246, 0 }, + { 85, 248, 155, 152, 152, 93, 1 }, + { 55, 197, 38, 151, 58, 211, 0 }, + { 221, 12, 140, 236, 143, 213, 0 }, }, + { { 106, 37, 233, 73, 31, 2, 1 }, + { 226, 2, 219, 107, 84, 131, 0 }, + { 160, 124, 73, 75, 210, 43, 0 }, + { 96, 149, 107, 109, 160, 35, 1 }, }, + { { 105, 154, 22, 139, 171, 78, 0 }, + { 12, 195, 56, 248, 186, 158, 0 }, + { 57, 106, 232, 180, 44, 203, 0 }, + { 60, 174, 143, 142, 97, 152, 0 }, }, + { { 104, 159, 185, 186, 26, 80, 0 }, + { 116, 227, 128, 171, 156, 203, 0 }, + { 5, 44, 46, 206, 252, 139, 0 }, + { 105, 156, 234, 128, 227, 151, 0 }, }, + { { 104, 172, 27, 248, 226, 27, 1 }, + { 122, 246, 106, 169, 58, 66, 0 }, + { 236, 35, 143, 236, 26, 139, 0 }, + { 33, 46, 74, 171, 55, 175, 0 }, }, + { { 107, 204, 220, 27, 224, 85, 0 }, + { 252, 214, 33, 94, 152, 86, 0 }, + { 85, 3, 236, 29, 153, 235, 0 }, + { 53, 12, 189, 66, 53, 159, 1 }, }, + { { 110, 70, 68, 174, 42, 27, 1 }, + { 203, 38, 9, 172, 177, 206, 0 }, + { 236, 42, 58, 145, 49, 59, 0 }, + { 57, 198, 154, 200, 50, 105, 1 }, }, + { { 110, 91, 221, 156, 85, 145, 1 }, + { 179, 223, 17, 143, 85, 79, 0 }, + { 196, 213, 28, 221, 237, 59, 0 }, + { 121, 85, 120, 196, 125, 230, 1 }, }, + { { 110, 114, 103, 14, 62, 109, 0 }, + { 141, 7, 131, 61, 243, 143, 1 }, + { 91, 62, 56, 115, 39, 59, 0 }, + { 248, 231, 222, 96, 240, 88, 1 }, }, + { { 111, 134, 147, 35, 45, 171, 0 }, + { 201, 110, 24, 75, 250, 152, 1 }, + { 106, 218, 98, 100, 176, 251, 0 }, + { 140, 175, 233, 12, 59, 73, 1 }, }, + { { 108, 137, 244, 64, 218, 114, 1 }, + { 7, 210, 233, 42, 28, 197, 1 }, + { 167, 45, 129, 23, 200, 155, 0 }, + { 209, 156, 42, 75, 165, 240, 0 }, }, + { { 111, 156, 82, 187, 178, 211, 1 }, + { 223, 239, 41, 232, 154, 83, 0 }, + { 229, 166, 238, 165, 28, 251, 0 }, + { 101, 44, 139, 202, 123, 253, 1 }, }, + { { 111, 148, 127, 90, 90, 175, 1 }, + { 243, 95, 201, 57, 186, 151, 1 }, + { 250, 173, 45, 127, 20, 251, 0 }, + { 244, 174, 206, 73, 253, 103, 1 }, }, + { { 110, 168, 22, 55, 188, 108, 1 }, + { 159, 226, 34, 88, 251, 133, 1 }, + { 155, 30, 246, 52, 10, 187, 0 }, + { 208, 239, 141, 34, 35, 252, 1 }, }, + { { 110, 177, 138, 108, 23, 81, 0 }, + { 165, 39, 82, 42, 95, 67, 0 }, + { 69, 116, 27, 40, 198, 187, 0 }, + { 97, 125, 42, 37, 114, 82, 1 }, }, + { { 111, 200, 142, 200, 247, 162, 0 }, + { 169, 154, 120, 174, 90, 23, 1 }, + { 34, 247, 137, 184, 137, 251, 0 }, + { 244, 45, 58, 143, 44, 202, 1 }, }, + { { 115, 81, 80, 190, 35, 239, 0 }, + { 156, 109, 29, 188, 181, 18, 1 }, + { 123, 226, 62, 133, 69, 103, 0 }, + { 164, 86, 158, 220, 91, 28, 1 }, }, + { { 112, 139, 71, 240, 27, 189, 0 }, + { 16, 172, 85, 185, 62, 205, 1 }, + { 94, 236, 7, 241, 104, 135, 0 }, + { 217, 190, 78, 213, 26, 132, 0 }, }, + { { 112, 149, 37, 251, 26, 110, 1 }, + { 86, 33, 204, 249, 188, 135, 1 }, + { 187, 44, 111, 210, 84, 135, 0 }, + { 240, 158, 207, 153, 194, 53, 0 }, }, + { { 114, 158, 255, 114, 99, 90, 0 }, + { 252, 241, 221, 43, 186, 76, 0 }, + { 45, 99, 39, 127, 188, 167, 0 }, + { 25, 46, 234, 93, 199, 159, 1 }, }, + { { 112, 171, 227, 124, 150, 208, 1 }, + { 22, 168, 231, 43, 95, 75, 0 }, + { 133, 180, 159, 99, 234, 135, 0 }, + { 105, 125, 106, 115, 138, 180, 0 }, }, + { { 114, 245, 151, 82, 179, 142, 0 }, + { 216, 73, 126, 63, 190, 5, 0 }, + { 56, 230, 165, 116, 215, 167, 0 }, + { 80, 62, 254, 63, 73, 13, 1 }, }, + { { 118, 5, 164, 230, 226, 221, 0 }, + { 205, 60, 228, 186, 181, 68, 0 }, + { 93, 163, 179, 146, 208, 55, 0 }, + { 17, 86, 174, 147, 158, 89, 1 }, }, + { { 116, 17, 6, 76, 105, 165, 0 }, + { 9, 29, 84, 24, 23, 134, 1 }, + { 82, 203, 25, 48, 68, 23, 0 }, + { 176, 244, 12, 21, 92, 72, 0 }, }, + { { 118, 36, 17, 60, 244, 246, 1 }, + { 223, 120, 46, 25, 81, 67, 1 }, + { 183, 151, 158, 68, 18, 55, 0 }, + { 225, 69, 76, 58, 15, 125, 1 }, }, + { { 116, 101, 193, 88, 187, 101, 1 }, + { 95, 4, 119, 63, 20, 131, 1 }, + { 211, 110, 141, 65, 211, 23, 0 }, + { 224, 148, 126, 119, 16, 125, 0 }, }, + { { 119, 125, 91, 58, 188, 225, 0 }, + { 253, 237, 39, 13, 214, 147, 1 }, + { 67, 158, 174, 109, 95, 119, 0 }, + { 228, 181, 216, 114, 91, 223, 1 }, }, + { { 119, 182, 15, 188, 123, 59, 0 }, + { 249, 53, 30, 169, 59, 223, 1 }, + { 110, 111, 30, 248, 54, 247, 0 }, + { 253, 238, 74, 188, 86, 79, 1 }, }, + { { 118, 180, 133, 0, 61, 168, 1 }, + { 203, 9, 22, 11, 120, 133, 1 }, + { 138, 222, 0, 80, 150, 183, 0 }, + { 208, 143, 104, 52, 72, 105, 1 }, }, + { { 117, 238, 187, 75, 14, 98, 1 }, + { 103, 192, 206, 111, 218, 154, 1 }, + { 163, 56, 105, 110, 187, 215, 0 }, + { 172, 173, 251, 57, 129, 243, 0 }, }, + { { 118, 250, 208, 216, 178, 184, 0 }, + { 153, 201, 103, 174, 56, 75, 1 }, + { 14, 166, 141, 133, 175, 183, 0 }, + { 233, 14, 58, 243, 73, 204, 1 }, }, + { { 120, 75, 97, 130, 218, 8, 1 }, + { 2, 146, 165, 173, 180, 137, 0 }, + { 136, 45, 160, 195, 105, 15, 0 }, + { 72, 150, 218, 210, 164, 160, 0 }, }, + { { 122, 187, 251, 47, 52, 31, 1 }, + { 170, 231, 143, 91, 255, 75, 0 }, + { 252, 22, 122, 111, 238, 175, 0 }, + { 105, 127, 237, 120, 243, 170, 1 }, }, + { { 127, 39, 5, 215, 84, 100, 0 }, + { 213, 18, 70, 217, 213, 29, 1 }, + { 19, 21, 117, 208, 114, 127, 0 }, + { 220, 85, 205, 177, 36, 85, 1 }, }, + { { 125, 227, 180, 31, 248, 152, 0 }, + { 25, 90, 166, 78, 189, 223, 0 }, + { 12, 143, 252, 22, 227, 223, 0 }, + { 125, 222, 185, 50, 173, 76, 0 }, }, + { { 127, 237, 141, 135, 251, 207, 1 }, + { 239, 158, 62, 255, 189, 149, 0 }, + { 249, 239, 240, 216, 219, 255, 0 }, + { 84, 222, 255, 190, 60, 251, 1 }, }, + { { 126, 246, 82, 55, 17, 197, 0 }, + { 213, 111, 23, 92, 155, 9, 0 }, + { 81, 196, 118, 37, 55, 191, 0 }, + { 72, 108, 157, 116, 123, 85, 1 }, }, + { { 129, 31, 240, 203, 185, 104, 1 }, + { 78, 193, 241, 194, 164, 187, 1 }, + { 139, 78, 233, 135, 252, 64, 1 }, + { 238, 146, 161, 199, 193, 185, 0 }, }, + { { 128, 46, 234, 6, 149, 52, 0 }, + { 96, 128, 179, 18, 195, 105, 1 }, + { 22, 84, 176, 43, 186, 0, 1 }, + { 203, 97, 164, 102, 128, 131, 0 }, }, + { { 130, 73, 255, 59, 138, 203, 1 }, + { 182, 236, 169, 103, 166, 166, 0 }, + { 233, 168, 238, 127, 201, 32, 1 }, + { 50, 178, 243, 74, 155, 182, 1 }, }, + { { 129, 200, 212, 49, 219, 37, 0 }, + { 16, 244, 49, 118, 8, 181, 1 }, + { 82, 109, 198, 21, 137, 192, 1 }, + { 214, 136, 55, 70, 23, 132, 0 }, }, + { { 132, 43, 15, 88, 160, 122, 0 }, + { 61, 128, 106, 1, 38, 110, 1 }, + { 47, 2, 141, 120, 106, 16, 1 }, + { 187, 50, 64, 43, 0, 222, 0 }, }, + { { 133, 43, 127, 179, 216, 14, 0 }, + { 49, 240, 171, 209, 166, 189, 0 }, + { 56, 13, 230, 255, 106, 80, 1 }, + { 94, 178, 197, 234, 135, 198, 0 }, }, + { { 134, 32, 167, 236, 235, 192, 1 }, + { 143, 56, 242, 163, 3, 166, 0 }, + { 129, 235, 155, 242, 130, 48, 1 }, + { 50, 224, 98, 167, 142, 120, 1 }, }, + { { 134, 89, 195, 75, 94, 119, 1 }, + { 135, 149, 73, 119, 198, 227, 1 }, + { 247, 61, 105, 97, 205, 48, 1 }, + { 227, 177, 247, 73, 84, 240, 1 }, }, + { { 135, 96, 6, 117, 87, 9, 0 }, + { 145, 52, 82, 100, 99, 53, 0 }, + { 72, 117, 87, 48, 3, 112, 1 }, + { 86, 99, 19, 37, 22, 68, 1 }, }, + { { 132, 219, 148, 26, 247, 200, 0 }, + { 29, 217, 48, 38, 236, 47, 0 }, + { 9, 247, 172, 20, 237, 144, 1 }, + { 122, 27, 178, 6, 77, 220, 0 }, }, + { { 138, 22, 181, 10, 57, 125, 0 }, + { 204, 71, 144, 19, 160, 239, 1 }, + { 95, 78, 40, 86, 180, 40, 1 }, + { 251, 130, 228, 4, 241, 25, 1 }, }, + { { 138, 63, 78, 17, 183, 8, 0 }, + { 248, 131, 51, 96, 102, 45, 0 }, + { 8, 118, 196, 57, 126, 40, 1 }, + { 90, 51, 3, 102, 96, 143, 1 }, }, + { { 137, 70, 134, 138, 98, 190, 0 }, + { 72, 26, 8, 182, 162, 126, 1 }, + { 62, 163, 40, 176, 177, 72, 1 }, + { 191, 34, 182, 136, 44, 9, 0 }, }, + { { 139, 122, 129, 163, 127, 219, 0 }, + { 140, 191, 26, 231, 224, 249, 0 }, + { 109, 255, 98, 192, 175, 104, 1 }, + { 79, 131, 243, 172, 126, 152, 1 }, }, + { { 137, 134, 220, 73, 174, 214, 0 }, + { 108, 74, 105, 114, 72, 254, 0 }, + { 53, 186, 201, 29, 176, 200, 1 }, + { 63, 137, 39, 75, 41, 27, 0 }, }, + { { 137, 153, 52, 70, 112, 178, 1 }, + { 10, 219, 200, 0, 141, 117, 1 }, + { 166, 135, 49, 22, 76, 200, 1 }, + { 215, 88, 128, 9, 237, 168, 0 }, }, + { { 139, 205, 202, 224, 85, 9, 1 }, + { 226, 182, 81, 134, 110, 49, 0 }, + { 200, 85, 3, 169, 217, 232, 1 }, + { 70, 59, 48, 197, 54, 163, 1 }, }, + { { 139, 222, 241, 118, 82, 104, 1 }, + { 214, 243, 193, 39, 169, 57, 1 }, + { 139, 37, 55, 71, 189, 232, 1 }, + { 206, 74, 242, 65, 231, 181, 1 }, }, + { { 142, 18, 98, 26, 198, 81, 1 }, + { 151, 23, 161, 32, 194, 106, 0 }, + { 197, 49, 172, 35, 36, 56, 1 }, + { 43, 33, 130, 66, 244, 116, 1 }, }, + { { 141, 46, 148, 181, 204, 168, 0 }, + { 81, 250, 34, 194, 97, 188, 1 }, + { 10, 153, 214, 148, 186, 88, 1 }, + { 158, 195, 33, 162, 47, 197, 0 }, }, + { { 140, 75, 185, 11, 82, 34, 0 }, + { 33, 210, 136, 103, 132, 43, 1 }, + { 34, 37, 104, 78, 233, 24, 1 }, + { 234, 16, 243, 8, 165, 194, 0 }, }, + { { 141, 153, 201, 208, 216, 171, 1 }, + { 51, 159, 105, 131, 44, 177, 1 }, + { 234, 141, 133, 201, 204, 216, 1 }, + { 198, 154, 96, 203, 124, 230, 0 }, }, + { { 144, 17, 129, 73, 60, 185, 1 }, + { 10, 13, 68, 67, 100, 227, 1 }, + { 206, 158, 73, 64, 196, 4, 1 }, + { 227, 147, 97, 17, 88, 40, 0 }, }, + { { 146, 45, 168, 84, 94, 84, 1 }, + { 246, 144, 198, 50, 69, 225, 0 }, + { 149, 61, 21, 10, 218, 36, 1 }, + { 67, 209, 38, 49, 132, 183, 1 }, }, + { { 144, 78, 120, 161, 56, 250, 1 }, + { 110, 232, 141, 196, 32, 233, 1 }, + { 175, 142, 66, 143, 57, 4, 1 }, + { 203, 130, 17, 216, 139, 187, 0 }, }, + { { 144, 151, 90, 16, 127, 241, 0 }, + { 124, 93, 21, 32, 78, 233, 1 }, + { 71, 255, 4, 45, 116, 132, 1 }, + { 203, 185, 2, 84, 93, 31, 0 }, }, + { { 144, 179, 61, 187, 100, 42, 0 }, + { 56, 113, 142, 193, 236, 46, 1 }, + { 42, 19, 110, 222, 102, 132, 1 }, + { 186, 27, 193, 184, 199, 14, 0 }, }, + { { 147, 192, 227, 79, 19, 241, 0 }, + { 132, 12, 213, 103, 139, 115, 1 }, + { 71, 228, 121, 99, 129, 228, 1 }, + { 231, 104, 243, 85, 152, 16, 1 }, }, + { { 151, 73, 34, 81, 12, 38, 0 }, + { 145, 128, 204, 84, 70, 176, 1 }, + { 50, 24, 69, 34, 73, 116, 1 }, + { 134, 177, 21, 25, 128, 196, 1 }, }, + { { 151, 74, 87, 164, 6, 195, 0 }, + { 133, 236, 13, 165, 67, 60, 0 }, + { 97, 176, 18, 245, 41, 116, 1 }, + { 30, 97, 82, 216, 27, 208, 1 }, }, + { { 150, 115, 104, 38, 246, 150, 1 }, + { 171, 57, 175, 52, 197, 105, 0 }, + { 180, 183, 178, 11, 103, 52, 1 }, + { 75, 81, 150, 122, 206, 106, 1 }, }, + { { 149, 138, 223, 218, 96, 3, 1 }, + { 59, 212, 77, 131, 138, 62, 0 }, + { 224, 3, 45, 253, 168, 212, 1 }, + { 62, 40, 224, 217, 21, 238, 0 }, }, + { { 150, 175, 46, 35, 58, 45, 0 }, + { 233, 164, 134, 112, 174, 173, 1 }, + { 90, 46, 98, 58, 122, 180, 1 }, + { 218, 186, 135, 48, 146, 203, 1 }, }, + { { 155, 7, 68, 229, 222, 47, 0 }, + { 192, 54, 109, 240, 101, 189, 1 }, + { 122, 61, 211, 145, 112, 108, 1 }, + { 222, 211, 7, 219, 54, 1, 1 }, }, + { { 152, 75, 88, 148, 206, 180, 0 }, + { 48, 218, 37, 180, 69, 232, 1 }, + { 22, 185, 148, 141, 105, 12, 1 }, + { 139, 209, 22, 210, 45, 134, 0 }, }, + { { 153, 122, 202, 55, 11, 119, 1 }, + { 54, 167, 31, 118, 131, 248, 1 }, + { 247, 104, 118, 41, 175, 76, 1 }, + { 143, 224, 183, 124, 114, 182, 0 }, }, + { { 156, 0, 144, 208, 133, 65, 1 }, + { 23, 70, 116, 130, 64, 32, 0 }, + { 193, 80, 133, 132, 128, 28, 1 }, + { 2, 1, 32, 151, 49, 116, 0 }, }, + { { 157, 117, 4, 69, 227, 242, 1 }, + { 79, 27, 126, 100, 5, 116, 1 }, + { 167, 227, 209, 16, 87, 92, 1 }, + { 151, 80, 19, 63, 108, 121, 0 }, }, + { { 160, 66, 244, 31, 20, 137, 1 }, + { 18, 76, 129, 78, 225, 47, 0 }, + { 200, 148, 124, 23, 161, 2, 1 }, + { 122, 67, 185, 64, 153, 36, 0 }, }, + { { 160, 154, 32, 41, 137, 97, 0 }, + { 4, 165, 176, 72, 8, 170, 1 }, + { 67, 72, 202, 2, 44, 130, 1 }, + { 170, 136, 9, 6, 210, 144, 0 }, }, + { { 161, 151, 65, 181, 6, 20, 0 }, + { 80, 33, 1, 249, 77, 120, 0 }, + { 20, 48, 86, 193, 116, 194, 1 }, + { 15, 89, 79, 192, 66, 5, 0 }, }, + { { 162, 199, 179, 237, 218, 219, 0 }, + { 196, 124, 232, 239, 47, 235, 0 }, + { 109, 173, 219, 230, 241, 162, 1 }, + { 107, 250, 123, 139, 159, 17, 1 }, }, + { { 163, 237, 115, 3, 171, 156, 0 }, + { 200, 200, 179, 125, 174, 240, 0 }, + { 28, 234, 224, 103, 91, 226, 1 }, + { 7, 186, 223, 102, 137, 137, 1 }, }, + { { 164, 14, 153, 123, 197, 24, 0 }, + { 113, 240, 112, 75, 224, 106, 0 }, + { 12, 81, 239, 76, 184, 18, 1 }, + { 43, 3, 233, 7, 7, 199, 0 }, }, + { { 166, 46, 211, 181, 170, 239, 0 }, + { 221, 236, 43, 251, 35, 168, 1 }, + { 123, 170, 214, 229, 186, 50, 1 }, + { 138, 226, 111, 234, 27, 221, 1 }, }, + { { 166, 72, 83, 81, 97, 91, 1 }, + { 159, 212, 89, 77, 34, 96, 0 }, + { 237, 67, 69, 101, 9, 50, 1 }, + { 3, 34, 89, 77, 21, 252, 1 }, }, + { { 166, 88, 46, 241, 183, 120, 1 }, + { 191, 161, 240, 236, 98, 101, 1 }, + { 143, 118, 199, 186, 13, 50, 1 }, + { 211, 35, 27, 135, 194, 254, 1 }, }, + { { 167, 125, 62, 203, 3, 105, 0 }, + { 229, 197, 210, 236, 166, 54, 1 }, + { 75, 96, 105, 190, 95, 114, 1 }, + { 182, 50, 155, 165, 209, 211, 1 }, }, + { { 165, 183, 40, 12, 100, 157, 0 }, + { 105, 29, 130, 24, 109, 122, 0 }, + { 92, 147, 24, 10, 118, 210, 1 }, + { 47, 91, 12, 32, 220, 75, 0 }, }, + { { 164, 207, 91, 78, 212, 244, 0 }, + { 101, 216, 97, 29, 207, 107, 1 }, + { 23, 149, 185, 109, 121, 146, 1 }, + { 235, 121, 220, 67, 13, 211, 0 }, }, + { { 171, 21, 211, 205, 48, 133, 0 }, + { 200, 79, 65, 219, 7, 51, 0 }, + { 80, 134, 89, 229, 212, 106, 1 }, + { 102, 112, 109, 193, 121, 9, 1 }, }, + { { 171, 47, 103, 30, 208, 8, 1 }, + { 210, 146, 163, 9, 167, 63, 0 }, + { 136, 5, 188, 115, 122, 106, 1 }, + { 126, 114, 200, 98, 164, 165, 1 }, }, + { { 168, 43, 177, 100, 80, 17, 1 }, + { 2, 246, 194, 11, 5, 105, 0 }, + { 196, 5, 19, 70, 234, 10, 1 }, + { 75, 80, 104, 33, 183, 160, 0 }, }, + { { 168, 67, 58, 121, 199, 250, 1 }, + { 54, 122, 248, 108, 102, 106, 1 }, + { 175, 241, 207, 46, 97, 10, 1 }, + { 171, 51, 27, 15, 175, 54, 0 }, }, + { { 168, 95, 145, 245, 169, 81, 0 }, + { 92, 231, 112, 207, 5, 232, 0 }, + { 69, 74, 215, 196, 253, 10, 1 }, + { 11, 208, 121, 135, 115, 157, 0 }, }, + { { 168, 126, 123, 132, 128, 94, 1 }, + { 102, 195, 171, 157, 35, 104, 0 }, + { 189, 0, 144, 239, 63, 10, 1 }, + { 11, 98, 92, 234, 225, 179, 0 }, }, + { { 168, 132, 121, 195, 96, 56, 1 }, + { 106, 82, 193, 201, 168, 96, 1 }, + { 142, 3, 97, 207, 16, 138, 1 }, + { 131, 10, 201, 193, 165, 43, 0 }, }, + { { 171, 163, 68, 207, 130, 112, 0 }, + { 132, 2, 99, 232, 141, 126, 1 }, + { 7, 32, 249, 145, 98, 234, 1 }, + { 191, 88, 139, 227, 32, 16, 1 }, }, + { { 171, 230, 49, 247, 179, 118, 0 }, + { 220, 98, 250, 253, 137, 121, 1 }, + { 55, 102, 247, 198, 51, 234, 1 }, + { 207, 72, 223, 175, 163, 29, 1 }, }, + { { 172, 22, 16, 22, 73, 151, 0 }, + { 81, 95, 24, 24, 129, 232, 0 }, + { 116, 201, 52, 4, 52, 26, 1 }, + { 11, 192, 140, 12, 125, 69, 0 }, }, + { { 174, 39, 216, 18, 235, 36, 1 }, + { 251, 82, 51, 58, 132, 168, 1 }, + { 146, 107, 164, 13, 242, 58, 1 }, + { 138, 144, 174, 102, 37, 111, 1 }, }, + { { 175, 59, 230, 2, 186, 159, 1 }, + { 139, 143, 171, 58, 166, 253, 0 }, + { 252, 174, 160, 51, 238, 122, 1 }, + { 95, 178, 174, 106, 248, 232, 1 }, }, + { { 174, 70, 26, 234, 137, 71, 1 }, + { 231, 102, 120, 156, 130, 170, 0 }, + { 241, 72, 171, 172, 49, 58, 1 }, + { 42, 160, 156, 143, 51, 115, 1 }, }, + { { 173, 66, 113, 218, 119, 159, 1 }, + { 27, 94, 217, 189, 224, 123, 0 }, + { 252, 247, 45, 199, 33, 90, 1 }, + { 111, 3, 222, 205, 189, 108, 0 }, }, + { { 173, 88, 23, 206, 43, 193, 1 }, + { 15, 207, 80, 173, 131, 182, 0 }, + { 193, 234, 57, 244, 13, 90, 1 }, + { 54, 224, 218, 133, 121, 248, 0 }, }, + { { 172, 151, 210, 208, 179, 77, 0 }, + { 93, 71, 113, 186, 46, 41, 0 }, + { 89, 102, 133, 165, 244, 154, 1 }, + { 74, 58, 46, 199, 113, 93, 0 }, }, + { { 173, 190, 197, 66, 11, 202, 0 }, + { 69, 139, 91, 43, 168, 188, 0 }, + { 41, 232, 33, 81, 190, 218, 1 }, + { 30, 138, 234, 109, 104, 209, 0 }, }, + { { 175, 250, 80, 85, 120, 101, 0 }, + { 157, 215, 67, 92, 9, 185, 1 }, + { 83, 15, 85, 5, 47, 250, 1 }, + { 206, 200, 29, 97, 117, 220, 1 }, }, + { { 176, 31, 206, 26, 12, 146, 1 }, + { 114, 137, 13, 10, 198, 238, 0 }, + { 164, 152, 44, 57, 252, 6, 1 }, + { 59, 177, 168, 88, 72, 167, 0 }, }, + { { 177, 112, 134, 190, 143, 137, 0 }, + { 16, 45, 54, 174, 227, 182, 0 }, + { 72, 248, 190, 176, 135, 70, 1 }, + { 54, 227, 186, 182, 90, 4, 0 }, }, + { { 183, 222, 54, 61, 41, 2, 0 }, + { 217, 225, 156, 76, 11, 190, 0 }, + { 32, 74, 94, 54, 61, 246, 1 }, + { 62, 232, 25, 28, 195, 205, 1 }, }, + { { 180, 229, 14, 234, 89, 126, 0 }, + { 101, 48, 94, 156, 174, 231, 1 }, + { 63, 77, 43, 184, 83, 150, 1 }, + { 243, 186, 156, 189, 6, 83, 0 }, }, + { { 186, 59, 44, 235, 91, 138, 0 }, + { 160, 187, 222, 232, 164, 175, 0 }, + { 40, 237, 107, 154, 110, 46, 1 }, + { 122, 146, 139, 189, 238, 130, 1 }, }, + { { 187, 123, 82, 243, 209, 20, 0 }, + { 144, 243, 119, 220, 134, 121, 0 }, + { 20, 69, 231, 165, 111, 110, 1 }, + { 79, 48, 157, 247, 103, 132, 1 }, }, + { { 186, 116, 182, 71, 89, 86, 1 }, + { 198, 83, 222, 94, 131, 229, 0 }, + { 181, 77, 113, 54, 151, 46, 1 }, + { 83, 224, 189, 61, 229, 49, 1 }, }, + { { 186, 139, 73, 97, 25, 111, 1 }, + { 166, 166, 93, 89, 44, 169, 1 }, + { 251, 76, 67, 73, 104, 174, 1 }, + { 202, 154, 77, 93, 50, 178, 1 }, }, + { { 187, 152, 233, 200, 28, 89, 0 }, + { 164, 135, 197, 139, 104, 243, 0 }, + { 77, 28, 9, 203, 140, 238, 1 }, + { 103, 139, 104, 209, 240, 146, 1 }, }, + { { 188, 30, 177, 81, 156, 163, 1 }, + { 83, 207, 236, 75, 64, 169, 1 }, + { 226, 156, 197, 70, 188, 30, 1 }, + { 202, 129, 105, 27, 249, 229, 0 }, }, + { { 188, 42, 52, 17, 27, 68, 1 }, + { 23, 194, 150, 120, 0, 173, 0 }, + { 145, 108, 68, 22, 42, 30, 1 }, + { 90, 128, 15, 52, 161, 244, 0 }, }, + { { 189, 81, 101, 27, 30, 213, 0 }, + { 21, 15, 133, 125, 196, 247, 0 }, + { 85, 188, 108, 83, 69, 94, 1 }, + { 119, 145, 223, 80, 248, 84, 0 }, }, + { { 190, 112, 117, 173, 51, 180, 1 }, + { 139, 107, 151, 253, 1, 103, 1 }, + { 150, 230, 90, 215, 7, 62, 1 }, + { 243, 64, 95, 244, 235, 104, 1 }, }, + { { 189, 255, 164, 161, 70, 198, 1 }, + { 71, 187, 142, 254, 76, 60, 0 }, + { 177, 177, 66, 146, 255, 222, 1 }, + { 30, 25, 63, 184, 238, 241, 0 }, }, + { { 193, 4, 151, 159, 155, 192, 0 }, + { 84, 72, 48, 227, 147, 183, 0 }, + { 1, 236, 252, 244, 144, 65, 1 }, + { 118, 228, 227, 134, 9, 21, 0 }, }, + { { 193, 30, 33, 36, 254, 38, 1 }, + { 74, 177, 168, 49, 81, 185, 1 }, + { 178, 63, 146, 66, 60, 65, 1 }, + { 206, 197, 70, 10, 198, 169, 0 }, }, + { { 192, 237, 72, 64, 106, 165, 0 }, + { 104, 156, 67, 52, 28, 160, 1 }, + { 82, 171, 1, 9, 91, 129, 1 }, + { 130, 156, 22, 97, 28, 139, 0 }, }, + { { 192, 234, 184, 166, 4, 229, 0 }, + { 36, 236, 130, 150, 217, 40, 1 }, + { 83, 144, 50, 142, 171, 129, 1 }, + { 138, 77, 180, 160, 155, 146, 0 }, }, + { { 198, 87, 250, 41, 77, 82, 1 }, + { 231, 113, 153, 70, 86, 234, 0 }, + { 165, 89, 74, 47, 245, 49, 1 }, + { 43, 181, 49, 76, 199, 115, 1 }, }, + { { 200, 111, 63, 193, 206, 104, 0 }, + { 100, 210, 226, 229, 118, 172, 1 }, + { 11, 57, 193, 254, 123, 9, 1 }, + { 154, 183, 83, 163, 165, 147, 0 }, }, + { { 200, 99, 86, 204, 12, 4, 1 }, + { 2, 66, 67, 148, 87, 174, 0 }, + { 144, 24, 25, 181, 99, 9, 1 }, + { 58, 245, 20, 225, 33, 32, 0 }, }, + { { 201, 215, 21, 97, 151, 171, 1 }, + { 66, 111, 120, 101, 124, 61, 1 }, + { 234, 244, 195, 84, 117, 201, 1 }, + { 222, 31, 83, 15, 123, 33, 0 }, }, + { { 203, 227, 118, 18, 26, 242, 1 }, + { 150, 74, 139, 36, 158, 253, 1 }, + { 167, 172, 36, 55, 99, 233, 1 }, + { 223, 188, 146, 104, 169, 52, 1 }, }, + { { 204, 23, 188, 202, 55, 164, 1 }, + { 107, 75, 208, 178, 212, 47, 1 }, + { 146, 246, 41, 158, 244, 25, 1 }, + { 250, 21, 166, 133, 233, 107, 0 }, }, + { { 204, 223, 166, 39, 145, 190, 0 }, + { 65, 171, 184, 86, 191, 109, 1 }, + { 62, 196, 242, 50, 253, 153, 1 }, + { 219, 126, 181, 14, 234, 193, 0 }, }, + { { 204, 235, 235, 67, 36, 93, 0 }, + { 45, 134, 195, 87, 254, 104, 0 }, + { 93, 18, 97, 107, 235, 153, 1 }, + { 11, 63, 245, 97, 176, 218, 0 }, }, + { { 209, 53, 155, 82, 5, 119, 0 }, + { 116, 69, 94, 19, 214, 112, 1 }, + { 119, 80, 37, 108, 214, 69, 1 }, + { 135, 53, 228, 61, 81, 23, 0 }, }, + { { 209, 91, 212, 112, 60, 106, 0 }, + { 28, 225, 77, 6, 116, 189, 1 }, + { 43, 30, 7, 21, 237, 69, 1 }, + { 222, 151, 48, 89, 67, 156, 0 }, }, + { { 215, 26, 117, 66, 163, 79, 1 }, + { 143, 197, 253, 49, 176, 60, 0 }, + { 249, 98, 161, 87, 44, 117, 1 }, + { 30, 6, 198, 95, 209, 248, 1 }, }, + { { 215, 230, 123, 63, 234, 215, 1 }, + { 255, 124, 175, 117, 155, 250, 0 }, + { 245, 171, 254, 111, 51, 245, 1 }, + { 47, 236, 215, 122, 159, 127, 1 }, }, + { { 219, 2, 186, 238, 81, 189, 1 }, + { 162, 126, 212, 146, 179, 123, 1 }, + { 222, 197, 59, 174, 160, 109, 1 }, + { 239, 102, 164, 149, 191, 34, 1 }, }, + { { 216, 126, 223, 70, 165, 187, 1 }, + { 106, 207, 127, 7, 243, 108, 1 }, + { 238, 210, 177, 125, 191, 13, 1 }, + { 155, 103, 240, 127, 121, 171, 0 }, }, + { { 216, 131, 133, 71, 132, 38, 1 }, + { 2, 2, 108, 83, 221, 44, 1 }, + { 178, 16, 241, 80, 224, 141, 1 }, + { 154, 93, 229, 27, 32, 32, 0 }, }, + { { 218, 189, 22, 192, 207, 231, 0 }, + { 196, 223, 126, 176, 94, 164, 1 }, + { 115, 249, 129, 180, 94, 173, 1 }, + { 146, 189, 6, 191, 125, 145, 1 }, }, + { { 219, 226, 77, 128, 236, 124, 1 }, + { 174, 18, 39, 149, 120, 252, 1 }, + { 159, 27, 128, 217, 35, 237, 1 }, + { 159, 143, 84, 242, 36, 58, 1 }, }, + { { 217, 244, 154, 237, 44, 69, 0 }, + { 108, 103, 70, 214, 91, 178, 0 }, + { 81, 26, 91, 172, 151, 205, 1 }, + { 38, 237, 53, 177, 115, 27, 0 }, }, + { { 222, 24, 187, 178, 78, 206, 1 }, + { 183, 251, 140, 179, 242, 160, 0 }, + { 185, 185, 38, 238, 140, 61, 1 }, + { 2, 167, 230, 152, 239, 246, 1 }, }, + { { 221, 25, 226, 31, 178, 162, 1 }, + { 27, 139, 173, 98, 151, 51, 1 }, + { 162, 166, 252, 35, 204, 93, 1 }, + { 230, 116, 163, 90, 232, 236, 0 }, }, + { { 222, 44, 65, 181, 149, 43, 0 }, + { 209, 166, 63, 193, 113, 33, 1 }, + { 106, 84, 214, 193, 26, 61, 1 }, + { 194, 71, 65, 254, 50, 197, 1 }, }, + { { 220, 75, 143, 150, 13, 97, 0 }, + { 53, 134, 20, 135, 215, 172, 1 }, + { 67, 88, 52, 248, 233, 29, 1 }, + { 154, 245, 240, 148, 48, 214, 0 }, }, + { { 221, 148, 173, 142, 18, 115, 1 }, + { 103, 7, 140, 163, 153, 119, 1 }, + { 231, 36, 56, 218, 148, 221, 1 }, + { 247, 76, 226, 152, 240, 115, 0 }, }, + { { 221, 209, 152, 178, 105, 248, 1 }, + { 63, 123, 20, 134, 188, 240, 1 }, + { 143, 203, 38, 140, 197, 221, 1 }, + { 135, 158, 176, 148, 111, 126, 0 }, }, + { { 225, 16, 129, 210, 125, 199, 0 }, + { 28, 29, 88, 155, 208, 177, 0 }, + { 113, 223, 37, 192, 132, 67, 1 }, + { 70, 133, 236, 141, 92, 28, 0 }, }, + { { 226, 29, 244, 60, 230, 69, 1 }, + { 222, 245, 161, 58, 85, 38, 0 }, + { 209, 51, 158, 23, 220, 35, 1 }, + { 50, 85, 46, 66, 215, 189, 1 }, }, + { { 227, 42, 142, 26, 117, 17, 0 }, + { 184, 148, 18, 10, 210, 127, 0 }, + { 68, 87, 44, 56, 170, 99, 1 }, + { 127, 37, 168, 36, 20, 142, 1 }, }, + { { 230, 127, 34, 189, 228, 63, 1 }, + { 219, 181, 170, 220, 119, 106, 1 }, + { 254, 19, 222, 162, 127, 51, 1 }, + { 171, 119, 29, 170, 214, 237, 1 }, }, + { { 230, 201, 244, 50, 47, 175, 1 }, + { 155, 236, 153, 62, 252, 164, 1 }, + { 250, 250, 38, 23, 201, 179, 1 }, + { 146, 159, 190, 76, 155, 236, 1 }, }, + { { 233, 65, 95, 6, 96, 115, 0 }, + { 44, 86, 9, 13, 151, 116, 1 }, + { 103, 3, 48, 125, 65, 75, 1 }, + { 151, 116, 216, 72, 53, 26, 0 }, }, + { { 232, 162, 221, 84, 147, 113, 0 }, + { 52, 70, 115, 43, 25, 109, 1 }, + { 71, 100, 149, 93, 162, 139, 1 }, + { 219, 76, 106, 103, 49, 22, 0 }, }, + { { 232, 216, 146, 243, 185, 171, 1 }, + { 26, 239, 120, 206, 186, 161, 1 }, + { 234, 206, 231, 164, 141, 139, 1 }, + { 194, 174, 185, 143, 123, 172, 0 }, }, + { { 234, 224, 242, 13, 25, 46, 0 }, + { 128, 66, 155, 94, 59, 163, 1 }, + { 58, 76, 88, 39, 131, 171, 1 }, + { 226, 238, 61, 108, 161, 0, 1 }, }, + { { 237, 183, 135, 120, 221, 244, 0 }, + { 85, 59, 114, 27, 94, 255, 1 }, + { 23, 221, 143, 112, 246, 219, 1 }, + { 255, 189, 108, 39, 110, 85, 0 }, }, + { { 237, 243, 41, 3, 202, 39, 1 }, + { 35, 23, 170, 125, 156, 184, 1 }, + { 242, 41, 224, 74, 103, 219, 1 }, + { 142, 156, 223, 42, 244, 98, 0 }, }, + { { 242, 9, 23, 170, 178, 81, 0 }, + { 140, 228, 36, 169, 150, 103, 0 }, + { 69, 38, 170, 244, 72, 39, 1 }, + { 115, 52, 202, 146, 19, 152, 1 }, }, + { { 242, 87, 191, 49, 98, 230, 1 }, + { 254, 121, 140, 127, 22, 44, 1 }, + { 179, 163, 70, 126, 245, 39, 1 }, + { 154, 52, 127, 24, 207, 63, 1 }, }, + { { 240, 161, 59, 114, 216, 194, 0 }, + { 52, 120, 238, 9, 158, 161, 0 }, + { 33, 141, 167, 110, 66, 135, 1 }, + { 66, 188, 200, 59, 143, 22, 0 }, }, + { { 242, 204, 10, 145, 3, 251, 0 }, + { 244, 140, 28, 236, 58, 96, 1 }, + { 111, 224, 68, 168, 25, 167, 1 }, + { 131, 46, 27, 156, 24, 151, 1 }, }, + { { 240, 197, 80, 127, 169, 160, 1 }, + { 90, 104, 117, 76, 157, 162, 1 }, + { 130, 202, 255, 5, 81, 135, 1 }, + { 162, 220, 153, 87, 11, 45, 0 }, }, + { { 241, 197, 159, 71, 93, 153, 1 }, + { 98, 92, 84, 79, 255, 245, 0 }, + { 204, 221, 113, 124, 209, 199, 1 }, + { 87, 255, 249, 21, 29, 35, 0 }, }, + { { 240, 244, 101, 147, 182, 176, 1 }, + { 90, 9, 167, 237, 216, 101, 1 }, + { 134, 182, 228, 211, 23, 135, 1 }, + { 211, 13, 219, 242, 200, 45, 0 }, }, + { { 244, 0, 37, 19, 37, 72, 0 }, + { 29, 0, 148, 73, 240, 36, 0 }, + { 9, 82, 100, 82, 0, 23, 1 }, + { 18, 7, 201, 20, 128, 92, 0 }, }, + { { 244, 111, 108, 190, 223, 216, 1 }, + { 119, 184, 183, 172, 245, 239, 0 }, + { 141, 253, 190, 155, 123, 23, 1 }, + { 123, 215, 154, 246, 142, 247, 0 }, }, + { { 247, 104, 114, 223, 78, 225, 0 }, + { 149, 220, 199, 236, 211, 178, 1 }, + { 67, 185, 125, 167, 11, 119, 1 }, + { 166, 229, 155, 241, 157, 212, 1 }, }, + { { 251, 140, 211, 46, 207, 23, 0 }, + { 192, 246, 61, 59, 219, 242, 0 }, + { 116, 121, 186, 101, 152, 239, 1 }, + { 39, 237, 238, 94, 55, 129, 1 }, }, + { { 249, 145, 73, 232, 47, 176, 0 }, + { 40, 43, 85, 169, 92, 242, 1 }, + { 6, 250, 11, 201, 68, 207, 1 }, + { 167, 157, 74, 213, 106, 10, 0 }, }, + { { 251, 204, 144, 194, 16, 228, 0 }, + { 196, 202, 68, 158, 152, 49, 1 }, + { 19, 132, 33, 132, 153, 239, 1 }, + { 198, 12, 188, 145, 41, 145, 1 }, }, + { { 248, 252, 106, 92, 170, 113, 1 }, + { 126, 135, 231, 44, 27, 226, 1 }, + { 199, 42, 157, 43, 31, 143, 1 }, + { 163, 236, 26, 115, 240, 191, 0 }, }, + { { 254, 15, 171, 22, 40, 191, 1 }, + { 251, 142, 140, 27, 183, 232, 1 }, + { 254, 138, 52, 106, 248, 63, 1 }, + { 139, 246, 236, 24, 184, 239, 1 }, }, + { { 252, 66, 14, 134, 208, 220, 0 }, + { 37, 26, 36, 156, 179, 109, 0 }, + { 29, 133, 176, 184, 33, 31, 1 }, + { 91, 102, 156, 146, 44, 82, 0 }, }, + { { 252, 124, 113, 120, 44, 31, 0 }, + { 89, 231, 207, 29, 112, 226, 0 }, + { 124, 26, 15, 71, 31, 31, 1 }, + { 35, 135, 92, 121, 243, 205, 0 }, }, + { { 255, 159, 183, 150, 227, 134, 1 }, + { 219, 219, 188, 187, 159, 60, 0 }, + { 176, 227, 180, 246, 252, 255, 1 }, + { 30, 124, 238, 158, 237, 237, 1 }, }, + { { 112, 127, 0, 71, 111, 110, 0 }, + { 76, 145, 94, 124, 245, 136, 1 }, + { 59, 123, 113, 0, 127, 7, 0 }, + { 136, 215, 159, 61, 68, 153, 0 }, }, + { { 131, 238, 196, 68, 55, 228, 0 }, + { 204, 136, 83, 54, 73, 61, 1 }, + { 19, 246, 17, 17, 187, 224, 1 }, + { 222, 73, 54, 101, 8, 153, 1 }, }, + { { 1, 32, 180, 5, 180, 95, 1 }, + { 14, 68, 170, 82, 97, 85, 0 }, + { 253, 22, 208, 22, 130, 64, 0 }, + { 85, 67, 37, 42, 145, 56, 0 }, }, + { { 0, 59, 124, 20, 69, 167, 0 }, + { 48, 221, 155, 16, 69, 12, 1 }, + { 114, 209, 20, 31, 110, 0, 0 }, + { 152, 81, 4, 108, 221, 134, 0 }, }, + { { 3, 79, 186, 190, 163, 12, 0 }, + { 248, 224, 176, 182, 167, 26, 0 }, + { 24, 98, 190, 174, 249, 96, 0 }, + { 44, 114, 182, 134, 131, 143, 1 }, }, + { { 1, 86, 37, 1, 21, 142, 0 }, + { 64, 9, 152, 85, 96, 29, 0 }, + { 56, 212, 64, 82, 53, 64, 0 }, + { 92, 3, 85, 12, 200, 1, 0 }, }, + { { 1, 143, 230, 168, 33, 42, 0 }, + { 72, 160, 153, 130, 46, 30, 1 }, + { 42, 66, 10, 179, 248, 192, 0 }, + { 188, 58, 32, 204, 130, 137, 0 }, }, + { { 0, 175, 103, 1, 210, 134, 0 }, + { 64, 152, 171, 113, 14, 13, 0 }, + { 48, 165, 192, 115, 122, 128, 0 }, + { 88, 56, 71, 106, 140, 129, 0 }, }, + { { 0, 217, 16, 247, 55, 249, 0 }, + { 28, 237, 80, 228, 237, 65, 1 }, + { 79, 246, 119, 132, 77, 128, 0 }, + { 193, 91, 147, 133, 91, 156, 0 }, }, + { { 3, 240, 9, 255, 186, 56, 0 }, + { 184, 33, 98, 229, 169, 211, 1 }, + { 14, 46, 255, 200, 7, 224, 0 }, + { 229, 202, 211, 163, 66, 14, 1 }, }, + { { 5, 92, 205, 107, 57, 177, 1 }, + { 107, 173, 81, 71, 128, 215, 1 }, + { 198, 206, 107, 89, 157, 80, 0 }, + { 245, 128, 241, 69, 90, 235, 0 }, }, + { { 6, 109, 146, 36, 128, 158, 1 }, + { 195, 232, 42, 22, 39, 64, 0 }, + { 188, 128, 146, 36, 219, 48, 0 }, + { 1, 114, 52, 42, 11, 225, 1 }, }, + { { 4, 166, 38, 247, 185, 66, 1 }, + { 95, 32, 250, 192, 139, 141, 0 }, + { 161, 78, 247, 178, 50, 144, 0 }, + { 88, 232, 129, 175, 130, 125, 0 }, }, + { { 4, 196, 160, 187, 5, 176, 0 }, + { 81, 40, 144, 198, 200, 66, 1 }, + { 6, 208, 110, 130, 145, 144, 0 }, + { 161, 9, 177, 132, 138, 69, 0 }, }, + { { 9, 4, 240, 102, 157, 166, 0 }, + { 64, 106, 249, 18, 193, 145, 1 }, + { 50, 220, 179, 7, 144, 72, 0 }, + { 196, 193, 164, 79, 171, 1, 0 }, }, + { { 8, 63, 162, 23, 251, 204, 1 }, + { 94, 155, 178, 114, 167, 137, 0 }, + { 153, 239, 244, 34, 254, 8, 0 }, + { 72, 242, 167, 38, 236, 189, 0 }, }, + { { 10, 107, 164, 59, 63, 71, 1 }, + { 158, 166, 154, 118, 196, 143, 0 }, + { 241, 126, 110, 18, 235, 40, 0 }, + { 120, 145, 183, 44, 178, 188, 1 }, }, + { { 9, 147, 68, 160, 208, 252, 0 }, + { 4, 59, 33, 144, 44, 93, 1 }, + { 31, 133, 130, 145, 100, 200, 0 }, + { 221, 26, 4, 194, 110, 16, 0 }, }, + { { 8, 210, 94, 4, 114, 98, 1 }, + { 46, 83, 9, 36, 11, 13, 1 }, + { 163, 39, 16, 61, 37, 136, 0 }, + { 216, 104, 18, 72, 101, 58, 0 }, }, + { { 14, 39, 43, 135, 201, 75, 1 }, + { 231, 22, 186, 193, 167, 136, 0 }, + { 233, 73, 240, 234, 114, 56, 0 }, + { 8, 242, 193, 174, 180, 115, 1 }, }, + { { 15, 42, 254, 150, 218, 227, 0 }, + { 181, 222, 171, 162, 131, 157, 1 }, + { 99, 173, 180, 191, 170, 120, 0 }, + { 220, 224, 162, 234, 189, 214, 1 }, }, + { { 13, 94, 114, 56, 152, 3, 0 }, + { 81, 231, 169, 4, 2, 155, 0 }, + { 96, 12, 142, 39, 61, 88, 0 }, + { 108, 160, 16, 74, 243, 197, 0 }, }, + { { 14, 87, 112, 167, 99, 99, 1 }, + { 207, 119, 153, 228, 133, 8, 1 }, + { 227, 99, 114, 135, 117, 56, 0 }, + { 136, 80, 147, 204, 247, 121, 1 }, }, + { { 13, 106, 169, 220, 27, 63, 0 }, + { 49, 134, 218, 183, 33, 219, 1 }, + { 126, 108, 29, 202, 171, 88, 0 }, + { 237, 194, 118, 173, 176, 198, 0 }, }, + { { 13, 118, 154, 128, 113, 208, 1 }, + { 111, 91, 18, 134, 2, 89, 0 }, + { 133, 199, 0, 172, 183, 88, 0 }, + { 77, 32, 48, 164, 109, 123, 0 }, }, + { { 14, 124, 197, 35, 226, 204, 1 }, + { 207, 187, 35, 119, 160, 4, 0 }, + { 153, 163, 226, 81, 159, 56, 0 }, + { 16, 2, 247, 98, 110, 249, 1 }, }, + { { 15, 139, 106, 20, 246, 21, 0 }, + { 185, 150, 161, 48, 79, 89, 0 }, + { 84, 55, 148, 43, 104, 248, 0 }, + { 77, 121, 6, 66, 180, 206, 1 }, }, + { { 13, 171, 134, 215, 206, 17, 1 }, + { 19, 150, 98, 226, 207, 220, 0 }, + { 196, 57, 245, 176, 234, 216, 0 }, + { 29, 249, 163, 163, 52, 228, 0 }, }, + { { 15, 203, 199, 97, 60, 150, 1 }, + { 139, 170, 73, 87, 78, 221, 0 }, + { 180, 158, 67, 113, 233, 248, 0 }, + { 93, 185, 117, 73, 42, 232, 1 }, }, + { { 14, 236, 63, 51, 174, 17, 1 }, + { 251, 230, 162, 101, 202, 196, 0 }, + { 196, 58, 230, 126, 27, 184, 0 }, + { 17, 169, 211, 34, 179, 239, 1 }, }, + { { 17, 15, 152, 165, 71, 82, 0 }, + { 100, 240, 28, 226, 69, 88, 0 }, + { 37, 113, 82, 140, 248, 68, 0 }, + { 13, 81, 35, 156, 7, 147, 0 }, }, + { { 17, 119, 210, 131, 226, 153, 0 }, + { 72, 93, 39, 230, 166, 88, 0 }, + { 76, 163, 224, 165, 247, 68, 0 }, + { 13, 50, 179, 242, 93, 9, 0 }, }, + { { 17, 151, 181, 190, 81, 161, 0 }, + { 80, 125, 148, 131, 141, 31, 1 }, + { 66, 197, 62, 214, 244, 196, 0 }, + { 252, 88, 224, 148, 223, 5, 0 }, }, + { { 18, 155, 152, 58, 105, 111, 1 }, + { 190, 245, 28, 18, 172, 138, 1 }, + { 251, 75, 46, 12, 236, 164, 0 }, + { 168, 154, 164, 28, 87, 190, 1 }, }, + { { 19, 233, 65, 189, 245, 218, 0 }, + { 156, 184, 63, 197, 109, 83, 0 }, + { 45, 215, 222, 193, 75, 228, 0 }, + { 101, 91, 81, 254, 14, 156, 1 }, }, + { { 22, 13, 83, 212, 169, 87, 0 }, + { 221, 196, 125, 145, 7, 192, 0 }, + { 117, 74, 149, 229, 88, 52, 0 }, + { 1, 240, 68, 223, 17, 221, 1 }, }, + { { 22, 61, 21, 111, 231, 73, 0 }, + { 205, 245, 118, 97, 229, 6, 0 }, + { 73, 115, 251, 84, 94, 52, 0 }, + { 48, 83, 195, 55, 87, 217, 1 }, }, + { { 20, 130, 100, 0, 95, 151, 1 }, + { 3, 28, 157, 48, 72, 205, 0 }, + { 244, 253, 0, 19, 32, 148, 0 }, + { 89, 137, 6, 92, 156, 96, 0 }, }, + { { 21, 130, 161, 143, 42, 56, 1 }, + { 11, 0, 132, 227, 169, 218, 1 }, + { 142, 42, 120, 194, 160, 212, 0 }, + { 173, 202, 227, 144, 128, 104, 0 }, }, + { { 21, 153, 34, 53, 74, 250, 0 }, + { 21, 185, 140, 96, 47, 208, 1 }, + { 47, 169, 86, 34, 76, 212, 0 }, + { 133, 250, 3, 24, 206, 212, 0 }, }, + { { 23, 156, 89, 25, 214, 46, 0 }, + { 241, 209, 45, 113, 104, 19, 1 }, + { 58, 53, 204, 77, 28, 244, 0 }, + { 228, 11, 71, 90, 69, 199, 1 }, }, + { { 21, 165, 87, 175, 25, 244, 1 }, + { 71, 104, 23, 209, 143, 215, 1 }, + { 151, 204, 122, 245, 82, 212, 0 }, + { 245, 248, 197, 244, 11, 113, 0 }, }, + { { 22, 185, 129, 147, 151, 243, 0 }, + { 149, 141, 62, 227, 204, 65, 1 }, + { 103, 244, 228, 192, 206, 180, 0 }, + { 193, 25, 227, 190, 88, 212, 1 }, }, + { { 20, 212, 174, 31, 83, 198, 0 }, + { 117, 25, 156, 118, 139, 7, 0 }, + { 49, 229, 124, 58, 149, 148, 0 }, + { 112, 104, 183, 28, 204, 87, 0 }, }, + { { 22, 222, 188, 166, 177, 46, 1 }, + { 235, 225, 188, 150, 169, 13, 1 }, + { 186, 70, 178, 158, 189, 180, 0 }, + { 216, 74, 180, 158, 195, 235, 1 }, }, + { { 21, 215, 231, 50, 136, 117, 0 }, + { 85, 37, 165, 23, 142, 220, 1 }, + { 87, 8, 166, 115, 245, 212, 0 }, + { 157, 184, 244, 82, 210, 85, 0 }, }, + { { 27, 5, 137, 184, 14, 199, 1 }, + { 246, 46, 12, 179, 68, 146, 0 }, + { 241, 184, 14, 200, 208, 108, 0 }, + { 36, 145, 102, 152, 58, 55, 1 }, }, + { { 24, 76, 13, 17, 16, 216, 1 }, + { 118, 138, 4, 69, 32, 69, 0 }, + { 141, 132, 68, 88, 25, 12, 0 }, + { 81, 2, 81, 16, 40, 183, 0 }, }, + { { 27, 80, 19, 3, 222, 147, 1 }, + { 130, 95, 44, 101, 194, 209, 0 }, + { 228, 189, 224, 100, 5, 108, 0 }, + { 69, 161, 211, 26, 125, 32, 1 }, }, + { { 26, 117, 67, 117, 230, 58, 0 }, + { 216, 51, 111, 101, 103, 64, 1 }, + { 46, 51, 215, 97, 87, 44, 0 }, + { 129, 115, 83, 123, 102, 13, 1 }, }, + { { 25, 139, 44, 109, 101, 206, 0 }, + { 44, 186, 220, 80, 109, 30, 0 }, + { 57, 211, 91, 26, 104, 204, 0 }, + { 60, 91, 5, 29, 174, 154, 0 }, }, + { { 24, 157, 237, 154, 191, 3, 0 }, + { 120, 135, 189, 163, 204, 135, 0 }, + { 96, 126, 172, 219, 220, 140, 0 }, + { 112, 153, 226, 222, 240, 143, 0 }, }, + { { 26, 163, 189, 21, 22, 74, 1 }, + { 182, 66, 142, 99, 109, 13, 0 }, + { 169, 52, 84, 94, 226, 172, 0 }, + { 88, 91, 99, 56, 161, 54, 1 }, }, + { { 24, 191, 221, 217, 136, 80, 1 }, + { 118, 195, 103, 195, 12, 206, 0 }, + { 133, 8, 205, 221, 254, 140, 0 }, + { 57, 152, 97, 243, 97, 183, 0 }, }, + { { 26, 187, 197, 128, 248, 166, 1 }, + { 138, 155, 47, 147, 12, 141, 1 }, + { 178, 143, 128, 209, 238, 172, 0 }, + { 216, 152, 100, 250, 108, 168, 1 }, }, + { { 24, 202, 200, 115, 174, 12, 1 }, + { 58, 162, 101, 118, 232, 136, 0 }, + { 152, 58, 231, 9, 169, 140, 0 }, + { 8, 139, 183, 83, 34, 174, 0 }, }, + { { 26, 251, 85, 224, 195, 153, 1 }, + { 130, 255, 119, 165, 44, 76, 0 }, + { 204, 225, 131, 213, 111, 172, 0 }, + { 25, 26, 82, 247, 127, 160, 1 }, }, + { { 25, 252, 172, 250, 123, 235, 0 }, + { 124, 191, 222, 166, 168, 151, 1 }, + { 107, 239, 47, 154, 159, 204, 0 }, + { 244, 138, 178, 189, 254, 159, 0 }, }, + { { 31, 33, 77, 51, 43, 129, 0 }, + { 185, 46, 23, 97, 132, 148, 0 }, + { 64, 234, 102, 89, 66, 124, 0 }, + { 20, 144, 195, 116, 58, 78, 1 }, }, + { { 29, 104, 97, 97, 80, 45, 1 }, + { 3, 182, 199, 85, 32, 17, 1 }, + { 218, 5, 67, 67, 11, 92, 0 }, + { 196, 2, 85, 113, 182, 224, 0 }, }, + { { 29, 113, 17, 105, 159, 205, 1 }, + { 7, 111, 118, 117, 100, 147, 0 }, + { 217, 252, 203, 68, 71, 92, 0 }, + { 100, 147, 87, 55, 123, 112, 0 }, }, + { { 28, 115, 52, 93, 58, 61, 1 }, + { 27, 71, 198, 116, 37, 207, 1 }, + { 222, 46, 93, 22, 103, 28, 0 }, + { 249, 210, 23, 49, 241, 108, 0 }, }, + { { 30, 167, 7, 22, 61, 22, 1 }, + { 219, 2, 30, 17, 207, 205, 0 }, + { 180, 94, 52, 112, 114, 188, 0 }, + { 89, 249, 196, 60, 32, 109, 1 }, }, + { { 32, 22, 241, 69, 239, 16, 1 }, + { 74, 81, 241, 107, 65, 200, 0 }, + { 132, 123, 209, 71, 180, 2, 0 }, + { 9, 193, 107, 71, 197, 41, 0 }, }, + { { 33, 37, 79, 144, 49, 77, 0 }, + { 124, 4, 19, 153, 38, 21, 0 }, + { 89, 70, 4, 249, 82, 66, 0 }, + { 84, 50, 76, 228, 16, 31, 0 }, }, + { { 35, 45, 115, 235, 220, 62, 1 }, + { 194, 240, 235, 217, 230, 211, 1 }, + { 190, 29, 235, 231, 90, 98, 0 }, + { 229, 179, 205, 235, 135, 161, 1 }, }, + { { 35, 72, 62, 43, 100, 184, 1 }, + { 170, 248, 128, 76, 226, 86, 1 }, + { 142, 147, 106, 62, 9, 98, 0 }, + { 181, 35, 153, 0, 143, 170, 1 }, }, + { { 35, 86, 58, 73, 248, 94, 1 }, + { 238, 81, 232, 92, 34, 219, 0 }, + { 189, 15, 201, 46, 53, 98, 0 }, + { 109, 162, 29, 11, 197, 59, 1 }, }, + { { 34, 138, 184, 17, 107, 194, 0 }, + { 188, 216, 152, 106, 8, 136, 0 }, + { 33, 235, 68, 14, 168, 162, 0 }, + { 8, 136, 43, 12, 141, 158, 1 }, }, + { { 32, 167, 72, 150, 66, 82, 1 }, + { 118, 16, 11, 168, 141, 72, 0 }, + { 165, 33, 52, 137, 114, 130, 0 }, + { 9, 88, 138, 232, 4, 55, 0 }, }, + { { 34, 164, 248, 245, 231, 39, 1 }, + { 250, 116, 251, 250, 73, 0, 1 }, + { 242, 115, 215, 143, 146, 162, 0 }, + { 128, 73, 47, 239, 151, 47, 1 }, }, + { { 33, 239, 227, 53, 220, 27, 1 }, + { 82, 180, 171, 79, 111, 217, 0 }, + { 236, 29, 214, 99, 251, 194, 0 }, + { 77, 251, 121, 106, 150, 165, 0 }, }, + { { 33, 255, 170, 148, 240, 254, 0 }, + { 124, 153, 170, 158, 47, 89, 1 }, + { 63, 135, 148, 170, 255, 194, 0 }, + { 205, 122, 60, 170, 204, 159, 0 }, }, + { { 37, 48, 68, 183, 73, 106, 0 }, + { 21, 49, 27, 200, 161, 148, 1 }, + { 43, 73, 118, 145, 6, 82, 0 }, + { 148, 194, 137, 236, 70, 84, 0 }, }, + { { 38, 55, 142, 202, 57, 200, 0 }, + { 237, 9, 82, 138, 166, 143, 0 }, + { 9, 206, 41, 184, 246, 50, 0 }, + { 120, 178, 168, 165, 72, 91, 1 }, }, + { { 39, 67, 65, 27, 226, 165, 0 }, + { 153, 28, 33, 125, 132, 26, 1 }, + { 82, 163, 236, 65, 97, 114, 0 }, + { 172, 16, 223, 66, 28, 76, 1 }, }, + { { 39, 119, 180, 37, 12, 229, 0 }, + { 197, 109, 130, 94, 69, 156, 1 }, + { 83, 152, 82, 22, 247, 114, 0 }, + { 156, 209, 61, 32, 219, 81, 1 }, }, + { { 36, 140, 97, 44, 211, 5, 0 }, + { 65, 180, 177, 57, 9, 3, 0 }, + { 80, 101, 154, 67, 24, 146, 0 }, + { 96, 72, 78, 70, 150, 193, 0 }, }, + { { 39, 181, 134, 58, 242, 110, 1 }, + { 223, 49, 42, 58, 174, 23, 1 }, + { 187, 39, 174, 48, 214, 242, 0 }, + { 244, 58, 174, 42, 70, 125, 1 }, }, + { { 37, 192, 47, 169, 170, 233, 1 }, + { 47, 44, 160, 237, 42, 150, 1 }, + { 203, 170, 202, 250, 1, 210, 0 }, + { 180, 170, 91, 130, 154, 122, 0 }, }, + { { 37, 193, 244, 243, 24, 172, 1 }, + { 19, 104, 193, 222, 172, 149, 1 }, + { 154, 140, 103, 151, 193, 210, 0 }, + { 212, 154, 189, 193, 139, 100, 0 }, }, + { { 38, 223, 180, 89, 168, 216, 1 }, + { 223, 201, 224, 78, 44, 206, 0 }, + { 141, 138, 205, 22, 253, 178, 0 }, + { 57, 154, 57, 3, 201, 253, 1 }, }, + { { 36, 229, 181, 254, 134, 140, 0 }, + { 81, 104, 226, 191, 237, 6, 0 }, + { 24, 176, 191, 214, 211, 146, 0 }, + { 48, 91, 254, 163, 139, 69, 0 }, }, + { { 36, 250, 147, 106, 49, 84, 1 }, + { 15, 225, 82, 31, 138, 75, 0 }, + { 149, 70, 43, 100, 175, 146, 0 }, + { 105, 40, 252, 37, 67, 248, 0 }, }, + { { 41, 33, 120, 64, 98, 223, 1 }, + { 46, 94, 203, 56, 36, 80, 0 }, + { 253, 163, 1, 15, 66, 74, 0 }, + { 5, 18, 14, 105, 189, 58, 0 }, }, + { { 42, 47, 218, 50, 70, 201, 1 }, + { 246, 254, 3, 42, 230, 8, 0 }, + { 201, 177, 38, 45, 250, 42, 0 }, + { 8, 51, 170, 96, 63, 183, 1 }, }, + { { 40, 62, 63, 42, 103, 4, 0 }, + { 104, 243, 146, 57, 194, 14, 0 }, + { 16, 115, 42, 126, 62, 10, 0 }, + { 56, 33, 206, 36, 231, 139, 0 }, }, + { { 43, 205, 185, 118, 1, 186, 0 }, + { 240, 234, 216, 15, 173, 80, 1 }, + { 46, 192, 55, 78, 217, 234, 0 }, + { 133, 90, 248, 13, 171, 135, 1 }, }, + { { 40, 234, 218, 205, 222, 126, 1 }, + { 38, 210, 107, 254, 107, 203, 1 }, + { 191, 61, 217, 173, 171, 138, 0 }, + { 233, 235, 63, 235, 37, 178, 0 }, }, + { { 44, 77, 140, 242, 231, 58, 1 }, + { 123, 178, 120, 174, 228, 68, 1 }, + { 174, 115, 167, 152, 217, 26, 0 }, + { 145, 19, 186, 143, 38, 239, 0 }, }, + { { 46, 88, 0, 228, 62, 206, 0 }, + { 141, 171, 72, 188, 97, 129, 0 }, + { 57, 190, 19, 128, 13, 58, 0 }, + { 64, 195, 30, 137, 106, 216, 1 }, }, + { { 45, 129, 187, 116, 79, 227, 1 }, + { 55, 126, 216, 43, 79, 144, 1 }, + { 227, 249, 23, 110, 192, 218, 0 }, + { 132, 249, 106, 13, 191, 118, 0 }, }, + { { 44, 145, 67, 129, 237, 29, 0 }, + { 9, 23, 49, 217, 110, 192, 0 }, + { 92, 91, 192, 225, 68, 154, 0 }, + { 1, 187, 77, 198, 116, 72, 0 }, }, + { { 45, 180, 8, 215, 116, 177, 1 }, + { 123, 31, 66, 200, 201, 81, 1 }, + { 198, 151, 117, 136, 22, 218, 0 }, + { 197, 73, 137, 161, 124, 111, 0 }, }, + { { 44, 224, 23, 61, 89, 3, 1 }, + { 19, 118, 26, 77, 11, 135, 0 }, + { 224, 77, 94, 116, 3, 154, 0 }, + { 112, 232, 89, 44, 55, 100, 0 }, }, + { { 48, 9, 23, 86, 236, 208, 1 }, + { 30, 216, 100, 9, 199, 196, 0 }, + { 133, 155, 181, 116, 72, 6, 0 }, + { 17, 241, 200, 19, 13, 188, 0 }, }, + { { 51, 70, 13, 15, 126, 150, 0 }, + { 232, 24, 12, 125, 193, 223, 0 }, + { 52, 191, 120, 88, 49, 102, 0 }, + { 125, 193, 223, 24, 12, 11, 1 }, }, + { { 50, 113, 60, 116, 164, 89, 0 }, + { 188, 101, 230, 12, 101, 68, 0 }, + { 77, 18, 151, 30, 71, 38, 0 }, + { 17, 83, 24, 51, 211, 30, 1 }, }, + { { 48, 167, 239, 179, 52, 231, 1 }, + { 126, 44, 143, 219, 206, 13, 1 }, + { 243, 150, 102, 251, 242, 134, 0 }, + { 216, 57, 237, 248, 154, 63, 0 }, }, + { { 48, 183, 244, 73, 95, 202, 0 }, + { 68, 89, 223, 106, 108, 143, 0 }, + { 41, 253, 73, 23, 246, 134, 0 }, + { 120, 155, 43, 125, 205, 17, 0 }, }, + { { 51, 236, 16, 56, 154, 82, 0 }, + { 212, 224, 46, 44, 8, 211, 0 }, + { 37, 44, 142, 4, 27, 230, 0 }, + { 101, 136, 26, 58, 3, 149, 1 }, }, + { { 53, 106, 133, 153, 65, 91, 0 }, + { 21, 148, 30, 207, 32, 94, 0 }, + { 109, 65, 76, 208, 171, 86, 0 }, + { 61, 2, 121, 188, 20, 212, 0 }, }, + { { 54, 125, 199, 249, 16, 137, 1 }, + { 211, 173, 71, 207, 38, 7, 0 }, + { 200, 132, 79, 241, 223, 54, 0 }, + { 112, 50, 121, 241, 90, 229, 1 }, }, + { { 53, 145, 236, 15, 52, 5, 1 }, + { 43, 5, 133, 90, 205, 23, 0 }, + { 208, 22, 120, 27, 196, 214, 0 }, + { 116, 89, 173, 80, 208, 106, 0 }, }, + { { 58, 31, 122, 71, 185, 141, 0 }, + { 232, 207, 245, 88, 167, 137, 0 }, + { 88, 206, 241, 47, 124, 46, 0 }, + { 72, 242, 141, 87, 249, 139, 1 }, }, + { { 58, 86, 77, 209, 166, 203, 1 }, + { 254, 15, 109, 237, 96, 12, 0 }, + { 233, 178, 197, 217, 53, 46, 0 }, + { 24, 3, 91, 219, 120, 63, 1 }, }, + { { 57, 105, 99, 159, 57, 176, 1 }, + { 26, 138, 151, 205, 135, 211, 1 }, + { 134, 206, 124, 227, 75, 78, 0 }, + { 229, 240, 217, 244, 168, 172, 0 }, }, + { { 59, 96, 103, 17, 227, 243, 0 }, + { 156, 30, 191, 109, 2, 84, 1 }, + { 103, 227, 196, 115, 3, 110, 0 }, + { 149, 32, 91, 126, 188, 28, 1 }, }, + { { 57, 204, 214, 65, 214, 205, 1 }, + { 70, 222, 101, 126, 106, 21, 0 }, + { 217, 181, 193, 53, 153, 206, 0 }, + { 84, 43, 63, 83, 61, 177, 0 }, }, + { { 60, 51, 21, 240, 128, 231, 0 }, + { 21, 111, 110, 153, 4, 12, 1 }, + { 115, 128, 135, 212, 102, 30, 0 }, + { 152, 16, 76, 187, 123, 84, 0 }, }, + { { 60, 51, 197, 75, 3, 115, 1 }, + { 7, 7, 95, 107, 132, 78, 1 }, + { 231, 96, 105, 81, 230, 30, 0 }, + { 185, 16, 235, 125, 112, 112, 0 }, }, + { { 61, 98, 186, 75, 99, 200, 0 }, + { 45, 90, 214, 110, 162, 26, 0 }, + { 9, 227, 105, 46, 163, 94, 0 }, + { 44, 34, 187, 53, 173, 90, 0 }, }, + { { 61, 117, 9, 162, 109, 95, 1 }, + { 111, 55, 30, 157, 228, 208, 0 }, + { 253, 91, 34, 200, 87, 94, 0 }, + { 5, 147, 220, 188, 118, 123, 0 }, }, + { { 63, 152, 179, 12, 62, 51, 1 }, + { 139, 199, 140, 43, 75, 211, 1 }, + { 230, 62, 24, 102, 140, 254, 0 }, + { 229, 233, 106, 24, 241, 232, 1 }, }, + { { 62, 205, 175, 189, 212, 43, 0 }, + { 241, 182, 172, 207, 111, 7, 1 }, + { 106, 21, 222, 250, 217, 190, 0 }, + { 240, 123, 121, 154, 182, 199, 1 }, }, + { { 63, 213, 180, 83, 119, 231, 1 }, + { 223, 95, 220, 126, 204, 21, 1 }, + { 243, 247, 101, 22, 213, 254, 0 }, + { 212, 25, 191, 29, 253, 125, 1 }, }, + { { 60, 233, 181, 48, 105, 180, 0 }, + { 25, 250, 150, 31, 12, 196, 1 }, + { 22, 203, 6, 86, 203, 158, 0 }, + { 145, 152, 124, 52, 175, 204, 0 }, }, + { { 64, 3, 93, 254, 66, 40, 1 }, + { 50, 112, 65, 161, 181, 14, 1 }, + { 138, 33, 63, 221, 96, 1, 0 }, + { 184, 86, 194, 193, 7, 38, 0 }, }, + { { 67, 106, 171, 8, 205, 110, 1 }, + { 166, 144, 186, 23, 114, 154, 1 }, + { 187, 89, 136, 106, 171, 97, 0 }, + { 172, 167, 116, 46, 132, 178, 1 }, }, + { { 67, 113, 229, 169, 206, 30, 1 }, + { 130, 49, 171, 247, 116, 214, 0 }, + { 188, 57, 202, 211, 199, 97, 0 }, + { 53, 151, 119, 234, 198, 32, 1 }, }, + { { 65, 168, 233, 191, 52, 145, 0 }, + { 56, 172, 131, 195, 217, 83, 0 }, + { 68, 150, 126, 203, 138, 193, 0 }, + { 101, 77, 225, 224, 154, 142, 0 }, }, + { { 65, 202, 197, 166, 227, 19, 0 }, + { 8, 180, 57, 167, 153, 92, 0 }, + { 100, 99, 178, 209, 169, 193, 0 }, + { 29, 76, 242, 206, 22, 136, 0 }, }, + { { 66, 247, 60, 189, 2, 52, 0 }, + { 240, 97, 130, 244, 29, 78, 1 }, + { 22, 32, 94, 158, 119, 161, 0 }, + { 185, 92, 23, 160, 195, 7, 1 }, }, + { { 68, 39, 29, 179, 182, 219, 1 }, + { 127, 108, 42, 225, 244, 77, 0 }, + { 237, 182, 230, 220, 114, 17, 0 }, + { 89, 23, 195, 170, 27, 127, 0 }, }, + { { 68, 76, 27, 64, 24, 97, 1 }, + { 103, 196, 64, 5, 18, 129, 1 }, + { 195, 12, 1, 108, 25, 17, 0 }, + { 192, 164, 80, 1, 17, 243, 0 }, }, + { { 70, 74, 152, 175, 201, 220, 0 }, + { 165, 248, 48, 214, 177, 202, 0 }, + { 29, 201, 250, 140, 169, 49, 0 }, + { 41, 198, 181, 134, 15, 210, 1 }, }, + { { 70, 131, 6, 249, 199, 224, 1 }, + { 151, 56, 112, 224, 94, 14, 1 }, + { 131, 241, 207, 176, 96, 177, 0 }, + { 184, 61, 3, 135, 14, 116, 1 }, }, + { { 68, 140, 202, 221, 114, 250, 1 }, + { 127, 152, 73, 226, 59, 67, 1 }, + { 175, 167, 93, 169, 152, 145, 0 }, + { 225, 110, 35, 201, 12, 255, 0 }, }, + { { 70, 158, 58, 59, 116, 65, 1 }, + { 255, 245, 128, 64, 218, 11, 0 }, + { 193, 23, 110, 46, 60, 177, 0 }, + { 104, 45, 129, 0, 215, 255, 1 }, }, + { { 73, 31, 143, 197, 190, 227, 0 }, + { 108, 143, 104, 227, 87, 157, 1 }, + { 99, 190, 209, 248, 252, 73, 0 }, + { 220, 245, 99, 139, 120, 155, 0 }, }, + { { 73, 75, 34, 203, 253, 35, 1 }, + { 10, 150, 248, 196, 214, 155, 1 }, + { 226, 95, 233, 162, 105, 73, 0 }, + { 236, 181, 145, 143, 180, 168, 0 }, }, + { { 73, 91, 8, 111, 19, 202, 1 }, + { 38, 171, 88, 100, 181, 27, 0 }, + { 169, 228, 123, 8, 109, 73, 0 }, + { 108, 86, 147, 13, 106, 178, 0 }, }, + { { 74, 177, 68, 70, 10, 33, 1 }, + { 130, 7, 67, 32, 157, 132, 1 }, + { 194, 40, 49, 17, 70, 169, 0 }, + { 144, 220, 130, 97, 112, 32, 1 }, }, + { { 75, 195, 24, 213, 28, 201, 0 }, + { 180, 78, 64, 196, 125, 153, 0 }, + { 73, 156, 85, 140, 97, 233, 0 }, + { 76, 223, 17, 129, 57, 22, 1 }, }, + { { 76, 116, 244, 77, 39, 23, 1 }, + { 75, 71, 219, 118, 81, 70, 0 }, + { 244, 114, 89, 23, 151, 25, 0 }, + { 49, 69, 55, 109, 241, 105, 0 }, }, + { { 78, 157, 120, 2, 250, 136, 1 }, + { 235, 219, 161, 32, 188, 129, 0 }, + { 136, 175, 160, 15, 92, 185, 0 }, + { 64, 158, 130, 66, 237, 235, 1 }, }, + { { 79, 168, 131, 36, 88, 72, 0 }, + { 133, 178, 2, 3, 59, 145, 0 }, + { 9, 13, 18, 96, 138, 249, 0 }, + { 68, 238, 96, 32, 38, 208, 1 }, }, + { { 77, 204, 62, 66, 47, 44, 0 }, + { 105, 194, 208, 52, 250, 148, 1 }, + { 26, 122, 33, 62, 25, 217, 0 }, + { 148, 175, 150, 5, 161, 203, 0 }, }, + { { 77, 205, 182, 47, 105, 192, 1 }, + { 79, 250, 144, 70, 159, 150, 0 }, + { 129, 203, 122, 54, 217, 217, 0 }, + { 52, 252, 177, 4, 175, 249, 0 }, }, + { { 76, 252, 81, 246, 35, 2, 0 }, + { 89, 227, 91, 165, 153, 0, 0 }, + { 32, 98, 55, 197, 31, 153, 0 }, + { 0, 76, 210, 237, 99, 205, 0 }, }, + { { 77, 248, 189, 181, 78, 179, 0 }, + { 49, 255, 138, 231, 89, 212, 1 }, + { 102, 185, 86, 222, 143, 217, 0 }, + { 149, 205, 115, 168, 255, 198, 0 }, }, + { { 78, 254, 206, 197, 81, 2, 1 }, + { 227, 147, 91, 198, 27, 13, 0 }, + { 160, 69, 81, 185, 191, 185, 0 }, + { 88, 108, 49, 237, 100, 227, 1 }, }, + { { 83, 45, 244, 148, 190, 11, 1 }, + { 218, 196, 175, 162, 117, 149, 0 }, + { 232, 62, 148, 151, 218, 101, 0 }, + { 84, 215, 34, 250, 145, 173, 1 }, }, + { { 83, 55, 48, 177, 237, 76, 1 }, + { 222, 113, 182, 208, 116, 152, 0 }, + { 153, 91, 198, 134, 118, 101, 0 }, + { 12, 151, 5, 182, 199, 61, 1 }, }, + { { 81, 138, 115, 76, 78, 26, 0 }, + { 0, 208, 205, 33, 123, 218, 0 }, + { 44, 57, 25, 103, 40, 197, 0 }, + { 45, 239, 66, 89, 133, 128, 0 }, }, + { { 81, 173, 41, 83, 250, 51, 0 }, + { 120, 148, 238, 97, 156, 209, 1 }, + { 102, 47, 229, 74, 90, 197, 0 }, + { 197, 156, 195, 59, 148, 143, 0 }, }, + { { 80, 244, 139, 181, 87, 23, 1 }, + { 114, 53, 30, 247, 91, 65, 0 }, + { 244, 117, 86, 232, 151, 133, 0 }, + { 65, 109, 119, 188, 86, 39, 0 }, }, + { { 83, 242, 216, 169, 125, 63, 0 }, + { 168, 117, 31, 214, 120, 219, 1 }, + { 126, 95, 74, 141, 167, 229, 0 }, + { 237, 143, 53, 252, 87, 10, 1 }, }, + { { 87, 46, 51, 92, 65, 229, 1 }, + { 215, 220, 214, 17, 19, 26, 1 }, + { 211, 193, 29, 102, 58, 117, 0 }, + { 172, 100, 68, 53, 157, 245, 1 }, }, + { { 85, 97, 138, 153, 243, 43, 1 }, + { 59, 20, 62, 230, 54, 19, 1 }, + { 234, 103, 204, 168, 195, 85, 0 }, + { 228, 54, 51, 190, 20, 110, 0 }, }, + { { 84, 114, 176, 221, 66, 144, 1 }, + { 19, 89, 198, 230, 17, 74, 0 }, + { 132, 161, 93, 134, 167, 21, 0 }, + { 41, 68, 51, 177, 205, 100, 0 }, }, + { { 87, 120, 188, 172, 178, 210, 1 }, + { 175, 233, 174, 166, 17, 87, 0 }, + { 165, 166, 154, 158, 143, 117, 0 }, + { 117, 68, 50, 186, 203, 250, 1 }, }, + { { 85, 178, 111, 85, 255, 238, 1 }, + { 63, 25, 255, 113, 123, 157, 1 }, + { 187, 255, 213, 123, 38, 213, 0 }, + { 220, 239, 71, 127, 204, 126, 0 }, }, + { { 87, 215, 193, 128, 15, 12, 1 }, + { 195, 1, 21, 183, 124, 152, 0 }, + { 152, 120, 0, 193, 245, 245, 0 }, + { 12, 159, 118, 212, 64, 97, 1 }, }, + { { 85, 243, 37, 159, 247, 53, 0 }, + { 25, 21, 182, 245, 221, 95, 1 }, + { 86, 119, 252, 210, 103, 213, 0 }, + { 253, 93, 215, 182, 212, 76, 0 }, }, + { { 90, 0, 169, 237, 210, 158, 0 }, + { 160, 58, 236, 243, 49, 67, 0 }, + { 60, 165, 219, 202, 128, 45, 0 }, + { 97, 70, 103, 155, 174, 2, 1 }, }, + { { 89, 39, 61, 104, 89, 78, 1 }, + { 102, 114, 222, 17, 52, 159, 0 }, + { 185, 77, 11, 94, 114, 77, 0 }, + { 124, 150, 68, 61, 167, 51, 0 }, }, + { { 89, 73, 179, 211, 30, 79, 0 }, + { 20, 198, 204, 247, 246, 145, 0 }, + { 121, 60, 101, 230, 201, 77, 0 }, + { 68, 183, 247, 153, 177, 148, 0 }, }, + { { 91, 92, 30, 37, 185, 241, 1 }, + { 238, 239, 52, 68, 19, 213, 1 }, + { 199, 206, 210, 60, 29, 109, 0 }, + { 213, 228, 17, 22, 123, 187, 1 }, }, + { { 89, 81, 133, 145, 139, 249, 0 }, + { 20, 15, 52, 231, 52, 212, 1 }, + { 79, 232, 196, 208, 197, 77, 0 }, + { 149, 150, 115, 150, 120, 20, 0 }, }, + { { 91, 156, 92, 115, 202, 248, 1 }, + { 246, 251, 101, 96, 184, 212, 1 }, + { 143, 169, 231, 29, 28, 237, 0 }, + { 149, 142, 131, 83, 111, 183, 1 }, }, + { { 90, 148, 209, 233, 34, 152, 1 }, + { 202, 107, 69, 227, 56, 66, 0 }, + { 140, 162, 75, 197, 148, 173, 0 }, + { 33, 14, 99, 209, 107, 41, 1 }, }, + { { 88, 170, 155, 107, 27, 52, 0 }, + { 32, 226, 86, 115, 154, 203, 1 }, + { 22, 108, 107, 108, 170, 141, 0 }, + { 233, 172, 231, 53, 35, 130, 0 }, }, + { { 90, 202, 163, 22, 250, 87, 1 }, + { 158, 150, 172, 55, 155, 201, 0 }, + { 245, 47, 180, 98, 169, 173, 0 }, + { 73, 236, 246, 26, 180, 188, 1 }, }, + { { 88, 216, 190, 3, 241, 57, 0 }, + { 40, 215, 180, 70, 186, 69, 1 }, + { 78, 71, 224, 62, 141, 141, 0 }, + { 209, 46, 177, 22, 245, 138, 0 }, }, + { { 90, 253, 27, 96, 216, 252, 0 }, + { 228, 251, 102, 21, 62, 193, 1 }, + { 31, 141, 131, 108, 95, 173, 0 }, + { 193, 190, 84, 51, 111, 147, 1 }, }, + { { 93, 40, 12, 244, 83, 170, 1 }, + { 51, 186, 94, 160, 49, 21, 1 }, + { 170, 229, 23, 152, 10, 93, 0 }, + { 212, 70, 2, 189, 46, 230, 0 }, }, + { { 94, 52, 215, 24, 145, 91, 0 }, + { 213, 71, 63, 3, 50, 71, 0 }, + { 109, 68, 140, 117, 150, 61, 0 }, + { 113, 38, 96, 126, 113, 85, 1 }, }, + { { 94, 140, 231, 129, 51, 103, 0 }, + { 205, 134, 157, 243, 26, 5, 1 }, + { 115, 102, 64, 243, 152, 189, 0 }, + { 208, 44, 103, 220, 176, 217, 1 }, }, + { { 92, 240, 52, 170, 32, 5, 0 }, + { 9, 103, 134, 148, 152, 6, 0 }, + { 80, 2, 42, 150, 7, 157, 0 }, + { 48, 12, 148, 176, 243, 72, 0 }, }, + { { 99, 47, 8, 198, 1, 227, 0 }, + { 228, 140, 90, 136, 149, 24, 1 }, + { 99, 192, 49, 136, 122, 99, 0 }, + { 140, 84, 136, 173, 24, 147, 1 }, }, + { { 97, 34, 240, 131, 154, 222, 0 }, + { 4, 72, 171, 250, 176, 217, 0 }, + { 61, 172, 224, 135, 162, 67, 0 }, + { 77, 134, 175, 234, 137, 16, 0 }, }, + { { 97, 64, 6, 28, 66, 91, 1 }, + { 22, 20, 8, 44, 51, 86, 0 }, + { 237, 33, 28, 48, 1, 67, 0 }, + { 53, 102, 26, 8, 20, 52, 0 }, }, + { { 99, 104, 252, 248, 31, 16, 0 }, + { 176, 224, 211, 174, 80, 215, 0 }, + { 4, 124, 15, 159, 139, 99, 0 }, + { 117, 133, 58, 229, 131, 134, 1 }, }, + { { 98, 163, 80, 160, 5, 206, 1 }, + { 134, 104, 27, 152, 124, 8, 0 }, + { 185, 208, 2, 133, 98, 163, 0 }, + { 8, 31, 12, 236, 11, 48, 1 }, }, + { { 96, 207, 8, 137, 207, 134, 1 }, + { 98, 152, 56, 252, 92, 138, 0 }, + { 176, 249, 200, 136, 121, 131, 0 }, + { 40, 157, 31, 142, 12, 163, 0 }, }, + { { 100, 31, 102, 147, 38, 250, 0 }, + { 93, 137, 137, 232, 246, 76, 1 }, + { 47, 178, 100, 179, 124, 19, 0 }, + { 153, 55, 139, 200, 200, 221, 0 }, }, + { { 103, 84, 36, 66, 155, 194, 1 }, + { 199, 9, 248, 44, 144, 149, 0 }, + { 161, 236, 161, 18, 21, 115, 0 }, + { 84, 132, 154, 15, 200, 113, 1 }, }, + { { 103, 94, 109, 245, 161, 167, 0 }, + { 249, 173, 249, 221, 17, 28, 1 }, + { 114, 194, 215, 219, 61, 115, 0 }, + { 156, 68, 93, 207, 218, 207, 1 }, }, + { { 100, 112, 128, 63, 112, 33, 0 }, + { 25, 53, 2, 78, 145, 3, 1 }, + { 66, 7, 126, 0, 135, 19, 0 }, + { 224, 68, 185, 32, 86, 76, 0 }, }, + { { 101, 113, 143, 62, 145, 182, 1 }, + { 51, 41, 58, 31, 151, 87, 1 }, + { 182, 196, 190, 120, 199, 83, 0 }, + { 245, 116, 252, 46, 74, 102, 0 }, }, + { { 103, 153, 192, 236, 194, 232, 1 }, + { 135, 185, 97, 170, 61, 18, 1 }, + { 139, 161, 155, 129, 204, 243, 0 }, + { 164, 94, 42, 195, 78, 240, 1 }, }, + { { 102, 181, 191, 1, 79, 214, 0 }, + { 229, 89, 154, 123, 94, 196, 0 }, + { 53, 249, 64, 126, 214, 179, 0 }, + { 17, 189, 111, 44, 205, 83, 1 }, }, + { { 103, 209, 57, 91, 135, 184, 0 }, + { 177, 73, 240, 109, 252, 82, 1 }, + { 14, 240, 237, 78, 69, 243, 0 }, + { 165, 31, 219, 7, 201, 70, 1 }, }, + { { 101, 214, 185, 83, 116, 18, 0 }, + { 121, 81, 200, 79, 216, 89, 0 }, + { 36, 23, 101, 78, 181, 211, 0 }, + { 77, 13, 249, 9, 197, 79, 0 }, }, + { { 107, 53, 69, 174, 190, 183, 0 }, + { 200, 47, 43, 185, 213, 215, 1 }, + { 118, 190, 186, 209, 86, 107, 0 }, + { 245, 213, 206, 234, 122, 9, 1 }, }, + { { 106, 85, 31, 116, 156, 15, 1 }, + { 242, 103, 104, 29, 119, 133, 0 }, + { 248, 28, 151, 124, 85, 43, 0 }, + { 80, 247, 92, 11, 115, 39, 1 }, }, + { { 105, 214, 240, 35, 1, 52, 0 }, + { 64, 99, 145, 94, 152, 88, 1 }, + { 22, 64, 98, 7, 181, 203, 0 }, + { 141, 12, 189, 68, 227, 1, 0 }, }, + { { 107, 214, 206, 166, 84, 232, 1 }, + { 230, 59, 1, 142, 251, 29, 1 }, + { 139, 149, 50, 185, 181, 235, 0 }, + { 220, 111, 184, 192, 110, 51, 1 }, }, + { { 108, 83, 126, 168, 65, 235, 0 }, + { 37, 127, 153, 140, 54, 14, 1 }, + { 107, 193, 10, 191, 101, 27, 0 }, + { 184, 54, 24, 204, 255, 82, 0 }, }, + { { 110, 99, 164, 20, 64, 36, 1 }, + { 147, 18, 130, 30, 21, 12, 1 }, + { 146, 1, 20, 18, 227, 59, 0 }, + { 152, 84, 60, 32, 164, 100, 1 }, }, + { { 108, 120, 204, 22, 132, 88, 1 }, + { 55, 131, 35, 14, 241, 68, 0 }, + { 141, 16, 180, 25, 143, 27, 0 }, + { 17, 71, 184, 98, 96, 246, 0 }, }, + { { 108, 206, 226, 122, 51, 47, 0 }, + { 89, 166, 217, 62, 186, 11, 1 }, + { 122, 102, 47, 35, 185, 155, 0 }, + { 232, 46, 190, 77, 178, 205, 0 }, }, + { { 111, 226, 98, 8, 71, 155, 0 }, + { 129, 30, 155, 44, 122, 90, 0 }, + { 108, 241, 8, 35, 35, 251, 0 }, + { 45, 47, 26, 108, 188, 64, 1 }, }, + { { 113, 2, 15, 225, 198, 243, 0 }, + { 36, 60, 108, 233, 82, 92, 1 }, + { 103, 177, 195, 248, 32, 71, 0 }, + { 157, 37, 75, 155, 30, 18, 0 }, }, + { { 113, 1, 66, 223, 219, 44, 0 }, + { 16, 16, 117, 248, 183, 147, 1 }, + { 26, 109, 253, 161, 64, 71, 0 }, + { 228, 246, 143, 215, 4, 4, 0 }, }, + { { 114, 10, 186, 206, 130, 96, 0 }, + { 164, 192, 228, 170, 147, 10, 1 }, + { 3, 32, 185, 174, 168, 39, 0 }, + { 168, 100, 170, 147, 129, 146, 1 }, }, + { { 114, 76, 255, 13, 215, 153, 0 }, + { 224, 220, 181, 111, 115, 71, 0 }, + { 76, 245, 216, 127, 153, 39, 0 }, + { 113, 103, 123, 86, 157, 131, 1 }, }, + { { 112, 148, 200, 167, 175, 228, 0 }, + { 108, 41, 53, 250, 217, 128, 1 }, + { 19, 250, 242, 137, 148, 135, 0 }, + { 128, 205, 175, 214, 74, 27, 0 }, }, + { { 115, 219, 92, 88, 31, 37, 1 }, + { 178, 197, 85, 60, 92, 159, 1 }, + { 210, 124, 13, 29, 109, 231, 0 }, + { 252, 157, 30, 85, 81, 166, 1 }, }, + { { 113, 220, 240, 226, 63, 213, 1 }, + { 78, 237, 213, 190, 216, 209, 0 }, + { 213, 254, 35, 135, 157, 199, 0 }, + { 69, 141, 190, 213, 219, 185, 0 }, }, + { { 115, 240, 217, 222, 113, 35, 1 }, + { 186, 85, 95, 143, 153, 19, 1 }, + { 226, 71, 61, 205, 135, 231, 0 }, + { 228, 76, 248, 253, 85, 46, 1 }, }, + { { 117, 16, 178, 106, 60, 109, 0 }, + { 13, 101, 196, 26, 242, 147, 1 }, + { 91, 30, 43, 38, 132, 87, 0 }, + { 228, 167, 172, 17, 211, 88, 0 }, }, + { { 117, 125, 232, 146, 6, 63, 0 }, + { 113, 133, 143, 190, 244, 80, 1 }, + { 126, 48, 36, 139, 223, 87, 0 }, + { 133, 23, 190, 248, 208, 199, 0 }, }, + { { 116, 159, 153, 133, 17, 253, 0 }, + { 101, 205, 20, 219, 61, 73, 1 }, + { 95, 196, 80, 204, 252, 151, 0 }, + { 201, 94, 109, 148, 89, 211, 0 }, }, + { { 122, 14, 219, 24, 154, 237, 0 }, + { 244, 206, 37, 59, 50, 139, 1 }, + { 91, 172, 140, 109, 184, 47, 0 }, + { 232, 166, 110, 82, 57, 151, 1 }, }, + { { 121, 22, 143, 58, 72, 165, 1 }, + { 114, 63, 4, 27, 146, 158, 1 }, + { 210, 137, 46, 120, 180, 79, 0 }, + { 188, 164, 236, 16, 126, 39, 0 }, }, + { { 121, 188, 129, 6, 112, 53, 0 }, + { 72, 151, 6, 27, 153, 81, 1 }, + { 86, 7, 48, 64, 158, 207, 0 }, + { 197, 76, 236, 48, 116, 137, 0 }, }, + { { 125, 77, 194, 163, 143, 129, 1 }, + { 67, 174, 53, 238, 214, 144, 0 }, + { 192, 248, 226, 161, 217, 95, 0 }, + { 4, 181, 187, 214, 58, 225, 0 }, }, + { { 124, 135, 26, 99, 181, 78, 0 }, + { 109, 98, 124, 88, 254, 9, 0 }, + { 57, 86, 227, 44, 112, 159, 0 }, + { 72, 63, 141, 31, 35, 91, 0 }, }, + { { 127, 162, 123, 214, 2, 146, 1 }, + { 179, 74, 207, 169, 155, 88, 0 }, + { 164, 160, 53, 239, 34, 255, 0 }, + { 13, 108, 202, 249, 169, 102, 1 }, }, + { { 125, 171, 223, 59, 221, 197, 1 }, + { 55, 254, 55, 91, 222, 159, 0 }, + { 209, 221, 238, 125, 234, 223, 0 }, + { 124, 189, 237, 118, 63, 246, 0 }, }, + { { 125, 187, 65, 173, 85, 124, 1 }, + { 7, 179, 23, 217, 125, 91, 1 }, + { 159, 85, 90, 193, 110, 223, 0 }, + { 237, 95, 77, 244, 102, 240, 0 }, }, + { { 131, 4, 66, 138, 233, 199, 0 }, + { 204, 28, 57, 144, 130, 178, 0 }, + { 113, 203, 168, 161, 16, 96, 1 }, + { 38, 160, 132, 206, 28, 25, 1 }, }, + { { 131, 43, 42, 216, 218, 107, 1 }, + { 182, 148, 234, 160, 38, 187, 1 }, + { 235, 45, 141, 170, 106, 96, 1 }, + { 238, 178, 2, 171, 148, 182, 1 }, }, + { { 129, 42, 82, 146, 228, 127, 0 }, + { 28, 212, 43, 144, 226, 120, 1 }, + { 127, 19, 164, 165, 42, 64, 1 }, + { 143, 35, 132, 234, 21, 156, 0 }, }, + { { 129, 56, 22, 164, 149, 242, 0 }, + { 4, 233, 58, 128, 67, 117, 1 }, + { 39, 212, 146, 180, 14, 64, 1 }, + { 215, 97, 0, 174, 75, 144, 0 }, }, + { { 129, 100, 221, 191, 156, 110, 1 }, + { 118, 96, 43, 215, 225, 183, 1 }, + { 187, 28, 254, 221, 147, 64, 1 }, + { 246, 195, 245, 234, 3, 55, 0 }, }, + { { 129, 123, 93, 252, 187, 253, 1 }, + { 62, 237, 115, 181, 37, 255, 1 }, + { 223, 238, 159, 221, 111, 64, 1 }, + { 255, 210, 86, 231, 91, 190, 0 }, }, + { { 130, 178, 225, 68, 80, 78, 0 }, + { 132, 17, 203, 19, 41, 41, 0 }, + { 57, 5, 17, 67, 166, 160, 1 }, + { 74, 74, 100, 105, 196, 16, 1 }, }, + { { 128, 247, 188, 97, 25, 21, 0 }, + { 96, 101, 210, 86, 12, 237, 0 }, + { 84, 76, 67, 30, 247, 128, 1 }, + { 91, 152, 53, 37, 211, 3, 0 }, }, + { { 135, 52, 106, 165, 125, 200, 0 }, + { 237, 57, 147, 192, 99, 177, 0 }, + { 9, 223, 82, 171, 22, 112, 1 }, + { 70, 227, 1, 228, 206, 91, 1 }, }, + { { 132, 140, 23, 210, 149, 239, 1 }, + { 87, 204, 120, 145, 234, 37, 1 }, + { 251, 212, 165, 244, 24, 144, 1 }, + { 210, 43, 196, 143, 25, 245, 0 }, }, + { { 132, 150, 182, 186, 170, 44, 0 }, + { 89, 97, 160, 178, 170, 174, 1 }, + { 26, 42, 174, 182, 180, 144, 1 }, + { 186, 170, 166, 130, 195, 77, 0 }, }, + { { 135, 156, 185, 242, 219, 180, 0 }, + { 241, 249, 240, 179, 136, 241, 1 }, + { 22, 237, 167, 206, 156, 240, 1 }, + { 199, 136, 230, 135, 207, 199, 1 }, }, + { { 132, 196, 118, 79, 240, 21, 1 }, + { 75, 84, 225, 84, 139, 103, 0 }, + { 212, 7, 249, 55, 17, 144, 1 }, + { 115, 104, 149, 67, 149, 105, 0 }, }, + { { 135, 196, 142, 129, 238, 18, 1 }, + { 235, 16, 40, 230, 74, 244, 0 }, + { 164, 59, 192, 184, 145, 240, 1 }, + { 23, 169, 51, 138, 4, 107, 1 }, }, + { { 133, 225, 245, 45, 64, 89, 1 }, + { 7, 116, 131, 71, 45, 118, 0 }, + { 205, 1, 90, 87, 195, 208, 1 }, + { 55, 90, 113, 96, 151, 112, 0 }, }, + { { 137, 14, 35, 66, 210, 193, 1 }, + { 70, 158, 224, 33, 130, 57, 0 }, + { 193, 165, 161, 98, 56, 72, 1 }, + { 78, 32, 194, 3, 188, 177, 0 }, }, + { { 138, 56, 218, 197, 237, 134, 1 }, + { 170, 219, 123, 210, 67, 160, 0 }, + { 176, 219, 209, 173, 142, 40, 1 }, + { 2, 225, 37, 239, 109, 170, 1 }, }, + { { 138, 149, 152, 168, 110, 21, 0 }, + { 232, 119, 0, 178, 76, 226, 0 }, + { 84, 59, 10, 140, 212, 168, 1 }, + { 35, 153, 38, 128, 119, 11, 1 }, }, + { { 141, 29, 47, 20, 46, 123, 1 }, + { 127, 135, 136, 33, 103, 244, 1 }, + { 239, 58, 20, 122, 92, 88, 1 }, + { 151, 243, 66, 8, 240, 255, 0 }, }, + { { 141, 111, 99, 41, 14, 170, 1 }, + { 67, 170, 139, 101, 102, 186, 1 }, + { 170, 184, 74, 99, 123, 88, 1 }, + { 174, 179, 83, 104, 170, 225, 0 }, }, + { { 142, 178, 140, 83, 176, 124, 1 }, + { 191, 3, 98, 82, 168, 109, 1 }, + { 159, 6, 229, 24, 166, 184, 1 }, + { 219, 10, 165, 35, 96, 126, 1 }, }, + { { 142, 222, 235, 43, 219, 30, 1 }, + { 227, 179, 185, 119, 170, 235, 0 }, + { 188, 109, 234, 107, 189, 184, 1 }, + { 107, 170, 247, 78, 230, 227, 1 }, }, + { { 141, 245, 6, 243, 158, 250, 0 }, + { 85, 43, 106, 228, 238, 245, 1 }, + { 47, 188, 231, 176, 87, 216, 1 }, + { 215, 187, 147, 171, 106, 85, 0 }, }, + { { 144, 23, 149, 47, 152, 143, 0 }, + { 64, 109, 44, 83, 165, 175, 0 }, + { 120, 140, 250, 84, 244, 4, 1 }, + { 122, 210, 229, 26, 91, 1, 0 }, }, + { { 145, 35, 7, 27, 254, 232, 0 }, + { 28, 24, 38, 97, 230, 191, 1 }, + { 11, 191, 236, 112, 98, 68, 1 }, + { 254, 179, 195, 50, 12, 28, 0 }, }, + { { 144, 37, 85, 177, 206, 2, 1 }, + { 82, 112, 47, 225, 68, 164, 0 }, + { 160, 57, 198, 213, 82, 4, 1 }, + { 18, 145, 67, 250, 7, 37, 0 }, }, + { { 144, 40, 188, 66, 77, 232, 0 }, + { 36, 216, 214, 2, 224, 164, 1 }, + { 11, 217, 33, 30, 138, 4, 1 }, + { 146, 131, 160, 53, 141, 146, 0 }, }, + { { 144, 57, 208, 138, 32, 54, 1 }, + { 10, 193, 15, 146, 132, 98, 1 }, + { 182, 2, 40, 133, 206, 4, 1 }, + { 163, 16, 164, 248, 65, 168, 0 }, }, + { { 147, 79, 149, 211, 130, 90, 0 }, + { 212, 192, 108, 231, 164, 124, 0 }, + { 45, 32, 229, 212, 249, 100, 1 }, + { 31, 18, 243, 155, 1, 149, 1 }, }, + { { 146, 164, 210, 234, 226, 74, 0 }, + { 204, 112, 111, 162, 170, 34, 0 }, + { 41, 35, 171, 165, 146, 164, 1 }, + { 34, 42, 162, 251, 7, 25, 1 }, }, + { { 145, 180, 112, 9, 138, 92, 1 }, + { 70, 65, 167, 112, 40, 242, 0 }, + { 157, 40, 200, 7, 22, 196, 1 }, + { 39, 138, 7, 114, 193, 49, 0 }, }, + { { 147, 222, 25, 219, 41, 219, 1 }, + { 254, 205, 92, 197, 168, 250, 0 }, + { 237, 202, 109, 204, 61, 228, 1 }, + { 47, 138, 209, 157, 89, 191, 1 }, }, + { { 145, 229, 150, 87, 118, 92, 0 }, + { 92, 80, 70, 118, 239, 117, 0 }, + { 29, 55, 117, 52, 211, 196, 1 }, + { 87, 123, 183, 49, 5, 29, 0 }, }, + { { 146, 236, 144, 218, 63, 135, 0 }, + { 216, 204, 94, 182, 200, 163, 0 }, + { 112, 254, 45, 132, 155, 164, 1 }, + { 98, 137, 182, 189, 25, 141, 1 }, }, + { { 146, 252, 112, 168, 201, 176, 0 }, + { 192, 249, 183, 132, 8, 226, 1 }, + { 6, 201, 138, 135, 31, 164, 1 }, + { 163, 136, 16, 246, 207, 129, 1 }, }, + { { 149, 25, 170, 136, 106, 158, 1 }, + { 43, 153, 140, 178, 38, 242, 0 }, + { 188, 171, 8, 170, 204, 84, 1 }, + { 39, 178, 38, 152, 204, 234, 0 }, }, + { { 148, 82, 41, 183, 65, 127, 0 }, + { 53, 53, 156, 213, 161, 104, 1 }, + { 127, 65, 118, 202, 37, 20, 1 }, + { 139, 66, 213, 156, 214, 86, 0 }, }, + { { 148, 87, 59, 92, 68, 38, 1 }, + { 115, 81, 204, 21, 71, 42, 1 }, + { 178, 17, 29, 110, 117, 20, 1 }, + { 170, 113, 84, 25, 197, 103, 0 }, }, + { { 151, 82, 5, 249, 168, 20, 0 }, + { 153, 33, 100, 213, 0, 254, 0 }, + { 20, 10, 207, 208, 37, 116, 1 }, + { 63, 128, 85, 147, 66, 76, 1 }, }, + { { 150, 156, 251, 61, 143, 160, 1 }, + { 243, 233, 181, 99, 75, 162, 1 }, + { 130, 248, 222, 111, 156, 180, 1 }, + { 162, 233, 99, 86, 203, 231, 1 }, }, + { { 151, 163, 111, 26, 69, 144, 1 }, + { 179, 24, 151, 1, 206, 126, 0 }, + { 132, 209, 44, 123, 98, 244, 1 }, + { 63, 57, 192, 116, 140, 102, 1 }, }, + { { 151, 170, 229, 229, 236, 98, 0 }, + { 141, 176, 239, 195, 73, 188, 1 }, + { 35, 27, 211, 211, 170, 244, 1 }, + { 158, 201, 97, 251, 134, 216, 1 }, }, + { { 151, 229, 170, 59, 113, 88, 1 }, + { 255, 48, 150, 70, 174, 115, 0 }, + { 141, 71, 110, 42, 211, 244, 1 }, + { 103, 58, 177, 52, 134, 127, 1 }, }, + { { 154, 32, 49, 130, 105, 146, 1 }, + { 138, 90, 158, 129, 128, 224, 0 }, + { 164, 203, 32, 198, 2, 44, 1 }, + { 3, 128, 192, 188, 173, 40, 1 }, }, + { { 153, 69, 56, 75, 61, 53, 1 }, + { 106, 70, 212, 84, 196, 243, 1 }, + { 214, 94, 105, 14, 81, 76, 1 }, + { 231, 145, 149, 21, 177, 43, 0 }, }, + { { 154, 177, 236, 143, 208, 111, 1 }, + { 166, 23, 175, 210, 173, 39, 1 }, + { 251, 5, 248, 155, 198, 172, 1 }, + { 242, 90, 165, 250, 244, 50, 1 }, }, + { { 154, 213, 151, 178, 63, 108, 1 }, + { 222, 99, 20, 183, 238, 165, 1 }, + { 155, 126, 38, 244, 213, 172, 1 }, + { 210, 187, 246, 148, 99, 61, 1 }, }, + { { 153, 249, 19, 79, 121, 230, 0 }, + { 12, 219, 94, 85, 143, 179, 1 }, + { 51, 207, 121, 100, 79, 204, 1 }, + { 230, 248, 213, 61, 109, 152, 0 }, }, + { { 158, 24, 232, 0, 191, 106, 1 }, + { 175, 131, 189, 34, 96, 161, 1 }, + { 171, 126, 128, 11, 140, 60, 1 }, + { 194, 131, 34, 94, 224, 250, 1 }, }, + { { 159, 72, 93, 163, 29, 40, 1 }, + { 163, 226, 21, 197, 224, 181, 1 }, + { 138, 92, 98, 221, 9, 124, 1 }, + { 214, 131, 209, 212, 35, 226, 1 }, }, + { { 159, 84, 21, 142, 137, 140, 0 }, + { 193, 75, 52, 149, 161, 182, 0 }, + { 24, 200, 184, 212, 21, 124, 1 }, + { 54, 194, 212, 150, 105, 65, 1 }, }, + { { 157, 106, 155, 82, 57, 78, 1 }, + { 63, 194, 94, 23, 162, 185, 0 }, + { 185, 78, 37, 108, 171, 92, 1 }, + { 78, 162, 244, 61, 33, 254, 0 }, }, + { { 158, 111, 156, 10, 159, 118, 0 }, + { 229, 194, 62, 54, 196, 239, 1 }, + { 55, 124, 168, 28, 251, 60, 1 }, + { 251, 145, 182, 62, 33, 211, 1 }, }, + { { 160, 9, 100, 5, 91, 91, 0 }, + { 4, 148, 153, 104, 37, 229, 0 }, + { 109, 109, 80, 19, 72, 2, 1 }, + { 83, 210, 11, 76, 148, 144, 0 }, }, + { { 162, 60, 219, 252, 54, 178, 0 }, + { 248, 233, 75, 171, 67, 99, 1 }, + { 38, 182, 31, 237, 158, 34, 1 }, + { 227, 97, 106, 233, 75, 143, 1 }, }, + { { 163, 91, 247, 118, 247, 143, 0 }, + { 152, 253, 249, 63, 231, 61, 0 }, + { 120, 247, 183, 119, 237, 98, 1 }, + { 94, 115, 254, 79, 223, 140, 1 }, }, + { { 162, 157, 115, 18, 132, 59, 0 }, + { 208, 197, 169, 9, 238, 96, 1 }, + { 110, 16, 164, 103, 92, 162, 1 }, + { 131, 59, 200, 74, 209, 133, 1 }, }, + { { 160, 206, 10, 169, 116, 87, 0 }, + { 108, 180, 8, 220, 74, 107, 0 }, + { 117, 23, 74, 168, 57, 130, 1 }, + { 107, 41, 29, 136, 22, 155, 0 }, }, + { { 160, 220, 172, 38, 43, 179, 1 }, + { 106, 173, 152, 46, 137, 228, 1 }, + { 230, 234, 50, 26, 157, 130, 1 }, + { 147, 200, 186, 12, 218, 171, 0 }, }, + { { 162, 245, 118, 116, 106, 43, 1 }, + { 218, 117, 203, 44, 47, 164, 1 }, + { 234, 43, 23, 55, 87, 162, 1 }, + { 146, 250, 26, 105, 215, 45, 1 }, }, + { { 166, 47, 214, 238, 159, 142, 1 }, + { 195, 232, 123, 186, 231, 175, 0 }, + { 184, 252, 187, 181, 250, 50, 1 }, + { 122, 243, 174, 239, 11, 225, 1 }, }, + { { 165, 56, 116, 248, 96, 115, 1 }, + { 31, 245, 203, 136, 0, 118, 1 }, + { 231, 3, 15, 151, 14, 82, 1 }, + { 183, 0, 8, 233, 215, 252, 0 }, }, + { { 164, 94, 71, 141, 231, 74, 0 }, + { 77, 145, 57, 237, 99, 46, 0 }, + { 41, 115, 216, 241, 61, 18, 1 }, + { 58, 99, 91, 206, 68, 217, 0 }, }, + { { 167, 93, 87, 124, 48, 116, 1 }, + { 223, 225, 65, 29, 7, 119, 1 }, + { 151, 6, 31, 117, 93, 114, 1 }, + { 247, 112, 92, 65, 67, 253, 1 }, }, + { { 166, 114, 149, 34, 69, 134, 0 }, + { 129, 121, 26, 31, 192, 44, 0 }, + { 48, 209, 34, 84, 167, 50, 1 }, + { 26, 1, 252, 44, 79, 64, 1 }, }, + { { 165, 131, 104, 82, 20, 90, 0 }, + { 53, 0, 201, 8, 236, 121, 0 }, + { 45, 20, 37, 11, 96, 210, 1 }, + { 79, 27, 136, 73, 128, 86, 0 }, }, + { { 164, 155, 84, 147, 194, 3, 0 }, + { 17, 213, 41, 232, 140, 44, 0 }, + { 96, 33, 228, 149, 108, 146, 1 }, + { 26, 24, 139, 202, 85, 196, 0 }, }, + { { 166, 189, 42, 131, 213, 14, 0 }, + { 225, 145, 186, 216, 238, 33, 0 }, + { 56, 85, 224, 170, 94, 178, 1 }, + { 66, 59, 141, 174, 196, 195, 1 }, }, + { { 169, 7, 89, 5, 251, 125, 0 }, + { 108, 86, 49, 121, 37, 249, 1 }, + { 95, 111, 208, 77, 112, 74, 1 }, + { 207, 210, 79, 70, 53, 27, 0 }, }, + { { 169, 21, 217, 154, 37, 235, 1 }, + { 126, 79, 25, 139, 228, 50, 1 }, + { 235, 210, 44, 205, 212, 74, 1 }, + { 166, 19, 232, 204, 121, 63, 0 }, }, + { { 171, 34, 146, 112, 50, 2, 0 }, + { 152, 98, 74, 42, 2, 57, 0 }, + { 32, 38, 7, 36, 162, 106, 1 }, + { 78, 32, 42, 41, 35, 12, 1 }, }, + { { 169, 55, 228, 166, 33, 245, 1 }, + { 78, 47, 147, 154, 133, 124, 1 }, + { 215, 194, 50, 147, 246, 74, 1 }, + { 159, 80, 172, 228, 250, 57, 0 }, }, + { { 170, 114, 189, 81, 74, 123, 0 }, + { 180, 87, 202, 111, 32, 236, 1 }, + { 111, 41, 69, 94, 167, 42, 1 }, + { 155, 130, 123, 41, 245, 22, 1 }, }, + { { 168, 137, 199, 107, 240, 9, 0 }, + { 8, 182, 97, 75, 174, 39, 0 }, + { 72, 7, 235, 113, 200, 138, 1 }, + { 114, 58, 233, 67, 54, 136, 0 }, }, + { { 170, 213, 171, 110, 131, 212, 1 }, + { 230, 43, 240, 63, 143, 98, 0 }, + { 149, 224, 187, 106, 213, 170, 1 }, + { 35, 120, 254, 7, 234, 51, 1 }, }, + { { 168, 211, 200, 181, 190, 175, 0 }, + { 56, 47, 41, 254, 109, 169, 1 }, + { 122, 190, 214, 137, 229, 138, 1 }, + { 202, 219, 63, 202, 122, 14, 0 }, }, + { { 174, 20, 19, 54, 167, 164, 0 }, + { 217, 107, 48, 57, 195, 32, 1 }, + { 18, 242, 182, 100, 20, 58, 1 }, + { 130, 97, 206, 6, 107, 77, 1 }, }, + { { 173, 100, 135, 249, 112, 220, 1 }, + { 95, 58, 66, 223, 34, 119, 0 }, + { 157, 135, 79, 240, 147, 90, 1 }, + { 119, 34, 125, 161, 46, 125, 0 }, }, + { { 175, 121, 76, 217, 234, 209, 1 }, + { 191, 159, 99, 236, 4, 246, 0 }, + { 197, 171, 205, 153, 79, 122, 1 }, + { 55, 144, 27, 227, 124, 254, 1 }, }, + { { 173, 154, 157, 184, 0, 254, 1 }, + { 55, 235, 8, 155, 40, 126, 1 }, + { 191, 128, 14, 220, 172, 218, 1 }, + { 191, 10, 108, 136, 107, 246, 0 }, }, + { { 175, 156, 136, 245, 213, 82, 0 }, + { 245, 179, 120, 202, 73, 113, 0 }, + { 37, 85, 215, 136, 156, 250, 1 }, + { 71, 73, 41, 143, 102, 215, 1 }, }, + { { 173, 207, 238, 79, 108, 35, 0 }, + { 105, 150, 201, 78, 207, 190, 1 }, + { 98, 27, 121, 59, 249, 218, 1 }, + { 190, 249, 185, 73, 180, 203, 0 }, }, + { { 173, 247, 163, 231, 58, 61, 1 }, + { 75, 39, 194, 255, 175, 249, 1 }, + { 222, 46, 115, 226, 247, 218, 1 }, + { 207, 250, 255, 161, 242, 105, 0 }, }, + { { 178, 65, 252, 215, 31, 187, 0 }, + { 176, 76, 221, 238, 229, 229, 1 }, + { 110, 252, 117, 159, 193, 38, 1 }, + { 211, 211, 187, 221, 153, 6, 1 }, }, + { { 179, 66, 216, 120, 217, 241, 1 }, + { 182, 124, 117, 14, 0, 251, 1 }, + { 199, 205, 143, 13, 161, 102, 1 }, + { 239, 128, 56, 87, 31, 54, 1 }, }, + { { 178, 97, 81, 226, 141, 99, 0 }, + { 132, 100, 127, 141, 196, 160, 1 }, + { 99, 88, 163, 197, 67, 38, 1 }, + { 130, 145, 216, 255, 19, 16, 1 }, }, + { { 179, 99, 173, 194, 60, 10, 1 }, + { 170, 0, 206, 143, 228, 189, 0 }, + { 168, 30, 33, 218, 227, 102, 1 }, + { 94, 147, 248, 185, 128, 42, 1 }, }, + { { 179, 122, 143, 78, 111, 189, 1 }, + { 170, 157, 86, 63, 227, 254, 1 }, + { 222, 251, 57, 120, 175, 102, 1 }, + { 191, 227, 254, 53, 92, 170, 1 }, }, + { { 176, 177, 13, 218, 203, 9, 1 }, + { 50, 21, 118, 169, 172, 166, 0 }, + { 200, 105, 173, 216, 70, 134, 1 }, + { 50, 154, 202, 183, 84, 38, 0 }, }, + { { 176, 187, 235, 133, 79, 183, 0 }, + { 32, 157, 159, 251, 79, 232, 1 }, + { 118, 249, 80, 235, 238, 134, 1 }, + { 139, 249, 111, 252, 220, 130, 0 }, }, + { { 177, 195, 46, 176, 79, 2, 0 }, + { 48, 48, 156, 172, 78, 188, 0 }, + { 32, 121, 6, 186, 97, 198, 1 }, + { 30, 185, 26, 156, 134, 6, 0 }, }, + { { 177, 202, 162, 122, 198, 206, 0 }, + { 20, 184, 236, 62, 234, 58, 0 }, + { 57, 177, 175, 34, 169, 198, 1 }, + { 46, 43, 190, 27, 142, 148, 0 }, }, + { { 180, 119, 47, 73, 43, 245, 0 }, + { 109, 13, 214, 125, 6, 238, 1 }, + { 87, 234, 73, 122, 119, 22, 1 }, + { 187, 176, 95, 53, 216, 91, 0 }, }, + { { 181, 174, 77, 39, 89, 131, 1 }, + { 99, 188, 31, 73, 137, 189, 0 }, + { 224, 205, 114, 89, 58, 214, 1 }, + { 94, 200, 201, 124, 30, 227, 0 }, }, + { { 183, 210, 205, 142, 206, 219, 0 }, + { 165, 29, 45, 175, 233, 254, 0 }, + { 109, 185, 184, 217, 165, 246, 1 }, + { 63, 203, 250, 218, 92, 82, 1 }, }, + { { 182, 235, 156, 93, 104, 151, 0 }, + { 185, 220, 78, 94, 13, 238, 0 }, + { 116, 139, 93, 28, 235, 182, 1 }, + { 59, 216, 61, 57, 29, 206, 1 }, }, + { { 180, 244, 103, 243, 78, 87, 0 }, + { 85, 53, 207, 253, 202, 228, 0 }, + { 117, 57, 103, 243, 23, 150, 1 }, + { 19, 169, 223, 249, 214, 85, 0 }, }, + { { 184, 15, 212, 222, 49, 7, 1 }, + { 90, 198, 93, 154, 133, 47, 0 }, + { 240, 70, 61, 149, 248, 14, 1 }, + { 122, 80, 172, 221, 49, 173, 0 }, }, + { { 184, 57, 126, 65, 128, 2, 0 }, + { 32, 195, 239, 72, 6, 36, 0 }, + { 32, 0, 193, 63, 78, 14, 1 }, + { 18, 48, 9, 123, 225, 130, 0 }, }, + { { 184, 56, 131, 182, 233, 190, 0 }, + { 24, 187, 62, 155, 163, 224, 1 }, + { 62, 203, 182, 224, 142, 14, 1 }, + { 131, 226, 236, 190, 110, 140, 0 }, }, + { { 184, 108, 6, 172, 40, 232, 0 }, + { 76, 170, 6, 140, 35, 166, 1 }, + { 11, 138, 26, 176, 27, 14, 1 }, + { 178, 226, 24, 176, 42, 153, 0 }, }, + { { 185, 136, 183, 154, 126, 192, 1 }, + { 30, 218, 132, 171, 202, 183, 0 }, + { 129, 191, 44, 246, 136, 206, 1 }, + { 118, 169, 234, 144, 173, 188, 0 }, }, + { { 184, 156, 165, 183, 60, 206, 0 }, + { 92, 171, 140, 219, 233, 165, 0 }, + { 57, 158, 118, 210, 156, 142, 1 }, + { 82, 203, 237, 152, 234, 157, 0 }, }, + { { 186, 207, 131, 167, 229, 28, 1 }, + { 202, 178, 52, 223, 239, 104, 0 }, + { 156, 83, 242, 224, 249, 174, 1 }, + { 11, 123, 253, 150, 38, 169, 1 }, }, + { { 184, 231, 17, 147, 88, 172, 1 }, + { 82, 90, 6, 221, 172, 169, 1 }, + { 154, 141, 100, 196, 115, 142, 1 }, + { 202, 154, 221, 176, 45, 37, 0 }, }, + { { 189, 114, 116, 100, 13, 206, 1 }, + { 7, 107, 223, 28, 97, 188, 0 }, + { 185, 216, 19, 23, 39, 94, 1 }, + { 30, 195, 28, 125, 235, 112, 0 }, }, + { { 188, 125, 134, 57, 75, 186, 1 }, + { 83, 187, 30, 110, 38, 230, 1 }, + { 174, 233, 78, 48, 223, 30, 1 }, + { 179, 178, 59, 60, 110, 229, 0 }, }, + { { 195, 30, 73, 175, 1, 169, 1 }, + { 226, 173, 17, 193, 177, 58, 1 }, + { 202, 192, 122, 201, 60, 97, 1 }, + { 174, 70, 193, 196, 90, 163, 1 }, }, + { { 192, 46, 91, 239, 41, 95, 0 }, + { 108, 228, 91, 209, 179, 234, 0 }, + { 125, 74, 123, 237, 58, 1, 1 }, + { 43, 230, 197, 237, 19, 155, 0 }, }, + { { 193, 78, 242, 15, 34, 121, 1 }, + { 78, 196, 129, 102, 179, 122, 1 }, + { 207, 34, 120, 39, 185, 65, 1 }, + { 175, 102, 179, 64, 145, 185, 0 }, }, + { { 193, 129, 72, 108, 154, 70, 1 }, + { 38, 32, 105, 48, 29, 179, 0 }, + { 177, 44, 155, 9, 64, 193, 1 }, + { 102, 220, 6, 75, 2, 50, 0 }, }, + { { 193, 153, 177, 237, 205, 191, 1 }, + { 2, 253, 248, 211, 125, 242, 1 }, + { 254, 217, 219, 198, 204, 193, 1 }, + { 167, 223, 101, 143, 223, 160, 0 }, }, + { { 193, 146, 177, 38, 19, 223, 1 }, + { 6, 109, 152, 51, 185, 121, 0 }, + { 253, 228, 50, 70, 164, 193, 1 }, + { 79, 78, 230, 12, 219, 48, 0 }, }, + { { 195, 183, 213, 56, 210, 51, 1 }, + { 210, 117, 43, 35, 28, 127, 1 }, + { 230, 37, 142, 85, 246, 225, 1 }, + { 255, 28, 98, 106, 87, 37, 1 }, }, + { { 193, 215, 25, 188, 235, 129, 1 }, + { 122, 125, 48, 165, 29, 186, 0 }, + { 192, 235, 158, 204, 117, 193, 1 }, + { 46, 220, 82, 134, 95, 47, 0 }, }, + { { 193, 246, 67, 78, 186, 232, 1 }, + { 78, 9, 99, 37, 187, 187, 1 }, + { 139, 174, 185, 97, 55, 193, 1 }, + { 238, 238, 210, 99, 72, 57, 0 }, }, + { { 196, 56, 145, 7, 35, 108, 1 }, + { 15, 193, 18, 115, 177, 32, 1 }, + { 155, 98, 112, 68, 142, 17, 1 }, + { 130, 70, 231, 36, 65, 248, 0 }, }, + { { 198, 77, 74, 227, 205, 183, 0 }, + { 225, 188, 121, 212, 214, 224, 1 }, + { 118, 217, 227, 169, 89, 49, 1 }, + { 131, 181, 149, 207, 30, 195, 1 }, }, + { { 199, 83, 172, 246, 95, 75, 1 }, + { 183, 53, 216, 166, 245, 189, 0 }, + { 233, 125, 55, 154, 229, 113, 1 }, + { 94, 215, 178, 141, 214, 118, 1 }, }, + { { 197, 126, 74, 113, 2, 75, 0 }, + { 117, 165, 75, 100, 50, 56, 0 }, + { 105, 32, 71, 41, 63, 81, 1 }, + { 14, 38, 19, 105, 82, 215, 0 }, }, + { { 198, 116, 217, 128, 138, 199, 1 }, + { 231, 77, 43, 183, 16, 160, 0 }, + { 241, 168, 128, 205, 151, 49, 1 }, + { 2, 132, 118, 234, 89, 115, 1 }, }, + { { 197, 136, 152, 47, 230, 202, 1 }, + { 47, 248, 40, 98, 249, 50, 0 }, + { 169, 179, 250, 12, 136, 209, 1 }, + { 38, 79, 163, 10, 15, 250, 0 }, }, + { { 197, 174, 39, 8, 53, 94, 1 }, + { 79, 128, 154, 17, 122, 127, 0 }, + { 189, 86, 8, 114, 58, 209, 1 }, + { 127, 47, 68, 44, 128, 249, 0 }, }, + { { 199, 188, 103, 248, 187, 33, 1 }, + { 219, 165, 243, 161, 26, 183, 1 }, + { 194, 110, 143, 243, 30, 241, 1 }, + { 246, 172, 66, 231, 210, 237, 1 }, }, + { { 196, 190, 140, 164, 243, 99, 0 }, + { 109, 181, 58, 162, 25, 45, 1 }, + { 99, 103, 146, 152, 190, 145, 1 }, + { 218, 76, 34, 174, 86, 219, 0 }, }, + { { 203, 15, 36, 114, 82, 53, 0 }, + { 208, 182, 192, 48, 148, 125, 1 }, + { 86, 37, 39, 18, 120, 105, 1 }, + { 223, 20, 134, 1, 182, 133, 1 }, }, + { { 201, 72, 89, 111, 111, 39, 1 }, + { 42, 246, 89, 117, 209, 178, 1 }, + { 242, 123, 123, 77, 9, 73, 1 }, + { 166, 197, 215, 77, 55, 170, 0 }, }, + { { 200, 85, 15, 64, 161, 129, 0 }, + { 104, 15, 112, 5, 22, 36, 0 }, + { 64, 194, 129, 120, 85, 9, 1 }, + { 18, 52, 80, 7, 120, 11, 0 }, }, + { { 203, 90, 139, 223, 86, 169, 0 }, + { 176, 159, 64, 231, 243, 59, 1 }, + { 74, 181, 125, 232, 173, 105, 1 }, + { 238, 103, 243, 129, 124, 134, 1 }, }, + { { 200, 115, 24, 175, 214, 16, 0 }, + { 32, 115, 34, 228, 213, 107, 0 }, + { 4, 53, 250, 140, 103, 9, 1 }, + { 107, 85, 147, 162, 103, 2, 0 }, }, + { { 201, 159, 158, 95, 56, 56, 0 }, + { 120, 195, 64, 66, 191, 255, 1 }, + { 14, 14, 125, 60, 252, 201, 1 }, + { 255, 254, 161, 1, 97, 143, 0 }, }, + { { 202, 191, 51, 44, 49, 240, 0 }, + { 204, 235, 146, 1, 31, 107, 1 }, + { 7, 198, 26, 102, 126, 169, 1 }, + { 235, 124, 64, 36, 235, 153, 1 }, }, + { { 201, 233, 181, 7, 221, 84, 0 }, + { 4, 210, 178, 87, 221, 245, 0 }, + { 21, 93, 240, 86, 203, 201, 1 }, + { 87, 221, 245, 38, 165, 144, 0 }, }, + { { 203, 240, 30, 59, 146, 129, 0 }, + { 176, 111, 34, 100, 154, 55, 0 }, + { 64, 164, 238, 60, 7, 233, 1 }, + { 118, 44, 147, 34, 123, 6, 1 }, }, + { { 204, 2, 72, 129, 217, 10, 0 }, + { 33, 18, 57, 192, 48, 169, 0 }, + { 40, 77, 192, 137, 32, 25, 1 }, + { 74, 134, 1, 206, 36, 66, 0 }, }, + { { 206, 44, 78, 124, 45, 3, 1 }, + { 251, 166, 91, 0, 83, 166, 0 }, + { 224, 90, 31, 57, 26, 57, 1 }, + { 50, 229, 0, 109, 50, 239, 1 }, }, + { { 205, 131, 179, 65, 153, 204, 1 }, + { 7, 74, 240, 83, 62, 185, 0 }, + { 153, 204, 193, 102, 224, 217, 1 }, + { 78, 190, 101, 7, 169, 112, 0 }, }, + { { 206, 128, 171, 157, 219, 17, 1 }, + { 179, 22, 176, 227, 27, 227, 0 }, + { 196, 109, 220, 234, 128, 185, 1 }, + { 99, 236, 99, 134, 180, 102, 1 }, }, + { { 204, 183, 70, 234, 75, 14, 1 }, + { 67, 51, 91, 176, 190, 174, 0 }, + { 184, 105, 43, 177, 118, 153, 1 }, + { 58, 190, 134, 237, 102, 97, 0 }, }, + { { 207, 228, 35, 113, 192, 123, 1 }, + { 215, 54, 234, 69, 58, 112, 1 }, + { 239, 1, 199, 98, 19, 249, 1 }, + { 135, 46, 81, 43, 182, 117, 1 }, }, + { { 205, 246, 59, 148, 188, 171, 1 }, + { 123, 79, 170, 133, 123, 185, 1 }, + { 234, 158, 148, 238, 55, 217, 1 }, + { 206, 239, 80, 170, 249, 111, 0 }, }, + { { 211, 23, 149, 186, 69, 222, 1 }, + { 214, 121, 28, 147, 244, 126, 0 }, + { 189, 209, 46, 212, 244, 101, 1 }, + { 63, 23, 228, 156, 79, 53, 1 }, }, + { { 208, 58, 34, 105, 80, 179, 0 }, + { 0, 189, 206, 64, 18, 107, 1 }, + { 102, 133, 75, 34, 46, 5, 1 }, + { 235, 36, 1, 57, 222, 128, 0 }, }, + { { 211, 54, 150, 219, 2, 36, 1 }, + { 210, 65, 70, 242, 146, 62, 1 }, + { 146, 32, 109, 180, 182, 101, 1 }, + { 190, 36, 167, 177, 65, 37, 1 }, }, + { { 210, 86, 224, 221, 145, 27, 0 }, + { 208, 5, 253, 198, 49, 107, 0 }, + { 108, 68, 221, 131, 181, 37, 1 }, + { 107, 70, 49, 223, 208, 5, 1 }, }, + { { 210, 188, 170, 228, 3, 129, 0 }, + { 224, 173, 214, 162, 27, 32, 0 }, + { 64, 224, 19, 170, 158, 165, 1 }, + { 2, 108, 34, 181, 218, 131, 1 }, }, + { { 211, 196, 238, 80, 249, 166, 0 }, + { 248, 24, 253, 22, 26, 181, 1 }, + { 50, 207, 133, 59, 145, 229, 1 }, + { 214, 172, 52, 95, 140, 15, 1 }, }, + { { 211, 228, 7, 252, 5, 226, 1 }, + { 214, 40, 94, 133, 91, 54, 1 }, + { 163, 208, 31, 240, 19, 229, 1 }, + { 182, 109, 80, 189, 10, 53, 1 }, }, + { { 211, 252, 28, 167, 191, 2, 0 }, + { 232, 225, 62, 228, 217, 181, 0 }, + { 32, 126, 242, 156, 31, 229, 1 }, + { 86, 205, 147, 190, 67, 139, 1 }, }, + { { 215, 68, 167, 168, 172, 223, 0 }, + { 205, 44, 172, 151, 114, 246, 0 }, + { 125, 154, 138, 242, 145, 117, 1 }, + { 55, 167, 116, 154, 154, 89, 1 }, }, + { { 214, 159, 244, 68, 89, 83, 0 }, + { 197, 213, 221, 2, 29, 237, 0 }, + { 101, 77, 17, 23, 252, 181, 1 }, + { 91, 220, 32, 93, 213, 209, 1 }, }, + { { 212, 175, 147, 230, 193, 68, 0 }, + { 69, 240, 118, 147, 159, 40, 0 }, + { 17, 65, 179, 228, 250, 149, 1 }, + { 10, 124, 228, 183, 7, 209, 0 }, }, + { { 217, 18, 102, 139, 15, 210, 1 }, + { 6, 11, 157, 224, 210, 254, 0 }, + { 165, 248, 104, 179, 36, 77, 1 }, + { 63, 165, 131, 220, 232, 48, 0 }, }, + { { 216, 81, 88, 45, 224, 190, 1 }, + { 42, 123, 45, 84, 53, 98, 1 }, + { 190, 131, 218, 13, 69, 13, 1 }, + { 163, 86, 21, 90, 111, 42, 0 }, }, + { { 216, 99, 102, 115, 129, 229, 0 }, + { 20, 46, 247, 84, 150, 44, 1 }, + { 83, 192, 231, 51, 99, 13, 1 }, + { 154, 52, 149, 119, 186, 20, 0 }, }, + { { 219, 108, 88, 112, 132, 206, 1 }, + { 246, 234, 111, 20, 112, 48, 0 }, + { 185, 144, 135, 13, 27, 109, 1 }, + { 6, 7, 20, 123, 43, 183, 1 }, }, + { { 217, 99, 155, 60, 255, 211, 0 }, + { 60, 126, 62, 39, 87, 251, 0 }, + { 101, 255, 158, 108, 227, 77, 1 }, + { 111, 245, 114, 62, 63, 30, 0 }, }, + { { 216, 138, 98, 229, 71, 244, 0 }, + { 4, 186, 213, 240, 91, 104, 1 }, + { 23, 241, 83, 163, 40, 141, 1 }, + { 139, 109, 7, 213, 174, 144, 0 }, }, + { { 217, 153, 210, 99, 213, 126, 1 }, + { 6, 243, 125, 82, 254, 113, 1 }, + { 191, 85, 227, 37, 204, 205, 1 }, + { 199, 63, 165, 95, 103, 176, 0 }, }, + { { 217, 162, 40, 145, 224, 209, 0 }, + { 60, 30, 166, 192, 24, 120, 0 }, + { 69, 131, 196, 138, 34, 205, 1 }, + { 15, 12, 1, 178, 188, 30, 0 }, }, + { { 219, 244, 50, 154, 65, 205, 0 }, + { 212, 95, 150, 148, 186, 50, 0 }, + { 89, 193, 44, 166, 23, 237, 1 }, + { 38, 46, 148, 180, 253, 21, 1 }, }, + { { 220, 29, 107, 96, 189, 237, 0 }, + { 109, 175, 245, 17, 118, 161, 1 }, + { 91, 222, 131, 107, 92, 29, 1 }, + { 194, 183, 68, 87, 250, 219, 0 }, }, + { { 222, 26, 217, 103, 197, 109, 0 }, + { 165, 247, 117, 83, 241, 40, 1 }, + { 91, 81, 243, 77, 172, 61, 1 }, + { 138, 71, 229, 87, 119, 210, 1 }, }, + { { 221, 114, 161, 214, 214, 204, 1 }, + { 23, 27, 230, 183, 241, 57, 0 }, + { 153, 181, 181, 194, 167, 93, 1 }, + { 78, 71, 246, 179, 236, 116, 0 }, }, + { { 221, 116, 213, 91, 237, 200, 1 }, + { 95, 91, 119, 71, 240, 182, 0 }, + { 137, 219, 237, 85, 151, 93, 1 }, + { 54, 135, 241, 119, 109, 125, 0 }, }, + { { 221, 134, 26, 161, 4, 19, 1 }, + { 99, 102, 12, 192, 90, 120, 0 }, + { 228, 16, 66, 172, 48, 221, 1 }, + { 15, 45, 1, 152, 51, 99, 0 }, }, + { { 223, 175, 27, 5, 181, 189, 1 }, + { 235, 206, 54, 81, 127, 121, 1 }, + { 222, 214, 208, 108, 122, 253, 1 }, + { 207, 127, 69, 54, 57, 235, 1 }, }, + { { 222, 176, 237, 214, 204, 22, 1 }, + { 179, 19, 239, 147, 217, 228, 0 }, + { 180, 25, 181, 219, 134, 189, 1 }, + { 19, 205, 228, 251, 228, 102, 1 }, }, + { { 223, 230, 10, 66, 93, 241, 0 }, + { 229, 30, 86, 4, 218, 249, 1 }, + { 71, 221, 33, 40, 51, 253, 1 }, + { 207, 173, 144, 53, 60, 83, 1 }, }, + { { 221, 251, 88, 81, 243, 143, 0 }, + { 57, 223, 127, 116, 60, 57, 0 }, + { 120, 231, 197, 13, 111, 221, 1 }, + { 78, 30, 23, 127, 125, 206, 0 }, }, + { { 227, 22, 68, 69, 140, 160, 0 }, + { 192, 9, 97, 72, 81, 188, 1 }, + { 2, 152, 209, 17, 52, 99, 1 }, + { 158, 197, 9, 67, 72, 1, 1 }, }, + { { 226, 26, 171, 99, 248, 21, 1 }, + { 170, 181, 224, 91, 146, 233, 0 }, + { 212, 15, 227, 106, 172, 35, 1 }, + { 75, 164, 237, 3, 214, 170, 1 }, }, + { { 226, 90, 168, 144, 16, 221, 1 }, + { 182, 141, 128, 158, 48, 105, 0 }, + { 221, 132, 4, 138, 173, 35, 1 }, + { 75, 6, 60, 128, 216, 182, 1 }, }, + { { 224, 126, 198, 224, 106, 118, 1 }, + { 78, 177, 75, 190, 18, 236, 1 }, + { 183, 43, 3, 177, 191, 3, 1 }, + { 155, 164, 62, 233, 70, 185, 0 }, }, + { { 225, 117, 204, 107, 230, 146, 0 }, + { 104, 57, 107, 110, 212, 118, 0 }, + { 36, 179, 235, 25, 215, 67, 1 }, + { 55, 21, 187, 107, 78, 11, 0 }, }, + { { 224, 221, 231, 62, 185, 106, 0 }, + { 92, 161, 185, 15, 191, 167, 1 }, + { 43, 78, 190, 115, 221, 131, 1 }, + { 242, 254, 248, 78, 194, 157, 0 }, }, + { { 231, 26, 89, 61, 43, 68, 0 }, + { 189, 225, 17, 121, 17, 186, 0 }, + { 17, 106, 94, 77, 44, 115, 1 }, + { 46, 196, 79, 68, 67, 222, 1 }, }, + { { 230, 61, 186, 78, 33, 23, 0 }, + { 233, 197, 218, 26, 151, 98, 0 }, + { 116, 66, 57, 46, 222, 51, 1 }, + { 35, 116, 172, 45, 209, 203, 1 }, }, + { { 231, 80, 110, 107, 241, 111, 0 }, + { 173, 53, 249, 92, 178, 55, 1 }, + { 123, 71, 235, 59, 5, 115, 1 }, + { 246, 38, 157, 79, 214, 90, 1 }, }, + { { 229, 83, 212, 72, 59, 179, 0 }, + { 9, 77, 89, 46, 20, 255, 1 }, + { 102, 238, 9, 21, 229, 83, 1 }, + { 255, 148, 58, 77, 89, 72, 0 }, }, + { { 228, 120, 9, 177, 15, 166, 1 }, + { 51, 169, 26, 253, 80, 160, 1 }, + { 178, 248, 70, 200, 15, 19, 1 }, + { 130, 133, 95, 172, 74, 230, 0 }, }, + { { 229, 122, 161, 185, 186, 74, 1 }, + { 31, 161, 170, 239, 48, 187, 0 }, + { 169, 46, 206, 194, 175, 83, 1 }, + { 110, 134, 123, 170, 194, 252, 0 }, }, + { { 231, 124, 180, 146, 88, 174, 1 }, + { 211, 217, 138, 158, 176, 181, 1 }, + { 186, 141, 36, 150, 159, 115, 1 }, + { 214, 134, 188, 168, 205, 229, 1 }, }, + { { 230, 164, 245, 240, 132, 66, 1 }, + { 215, 96, 235, 139, 88, 36, 0 }, + { 161, 16, 135, 215, 146, 179, 1 }, + { 18, 13, 104, 235, 131, 117, 1 }, }, + { { 229, 213, 42, 118, 142, 25, 1 }, + { 115, 37, 224, 44, 255, 240, 0 }, + { 204, 56, 183, 42, 85, 211, 1 }, + { 7, 255, 154, 3, 210, 103, 0 }, }, + { { 231, 215, 202, 210, 228, 123, 1 }, + { 255, 21, 105, 142, 254, 120, 1 }, + { 239, 19, 165, 169, 245, 243, 1 }, + { 143, 63, 184, 203, 84, 127, 1 }, }, + { { 228, 239, 89, 211, 239, 61, 1 }, + { 123, 212, 115, 253, 252, 232, 1 }, + { 222, 123, 229, 205, 123, 147, 1 }, + { 139, 159, 223, 231, 21, 239, 0 }, }, + { { 235, 27, 228, 239, 142, 205, 0 }, + { 132, 175, 225, 250, 245, 190, 0 }, + { 89, 184, 251, 147, 236, 107, 1 }, + { 62, 215, 175, 195, 250, 144, 1 }, }, + { { 233, 50, 9, 4, 85, 129, 1 }, + { 34, 31, 18, 9, 81, 57, 0 }, + { 192, 213, 16, 72, 38, 75, 1 }, + { 78, 69, 72, 36, 124, 34, 0 }, }, + { { 235, 52, 254, 217, 172, 58, 1 }, + { 250, 67, 235, 202, 114, 246, 1 }, + { 174, 26, 205, 191, 150, 107, 1 }, + { 183, 167, 41, 235, 225, 47, 1 }, }, + { { 234, 78, 19, 245, 78, 30, 0 }, + { 208, 242, 72, 253, 115, 232, 0 }, + { 60, 57, 87, 228, 57, 43, 1 }, + { 11, 231, 95, 137, 39, 133, 1 }, }, + { { 233, 176, 103, 32, 6, 254, 0 }, + { 4, 43, 139, 57, 122, 116, 1 }, + { 63, 176, 2, 115, 6, 203, 1 }, + { 151, 47, 78, 104, 234, 16, 0 }, }, + { { 234, 213, 177, 80, 109, 191, 0 }, + { 216, 95, 216, 31, 124, 224, 1 }, + { 126, 219, 5, 70, 213, 171, 1 }, + { 131, 159, 124, 13, 253, 13, 1 }, }, + { { 239, 10, 161, 134, 54, 174, 0 }, + { 137, 138, 136, 187, 241, 57, 1 }, + { 58, 182, 48, 194, 168, 123, 1 }, + { 206, 71, 238, 136, 168, 200, 1 }, }, + { { 237, 54, 239, 15, 39, 238, 1 }, + { 111, 11, 155, 123, 243, 62, 1 }, + { 187, 242, 120, 123, 182, 91, 1 }, + { 190, 103, 239, 108, 232, 123, 0 }, }, + { { 237, 74, 130, 29, 33, 213, 0 }, + { 29, 142, 16, 94, 19, 122, 0 }, + { 85, 194, 92, 32, 169, 91, 1 }, + { 47, 100, 61, 4, 56, 220, 0 }, }, + { { 236, 96, 189, 109, 140, 6, 0 }, + { 33, 98, 234, 95, 81, 166, 0 }, + { 48, 24, 219, 94, 131, 27, 1 }, + { 50, 197, 125, 43, 163, 66, 0 }, }, + { { 239, 130, 202, 100, 252, 71, 0 }, + { 173, 54, 105, 26, 91, 185, 0 }, + { 113, 31, 147, 41, 160, 251, 1 }, + { 78, 237, 44, 75, 54, 90, 1 }, }, + { { 238, 161, 127, 189, 151, 92, 0 }, + { 181, 98, 179, 249, 127, 103, 0 }, + { 29, 116, 222, 255, 66, 187, 1 }, + { 115, 127, 79, 230, 163, 86, 1 }, }, + { { 238, 209, 152, 137, 82, 229, 1 }, + { 167, 95, 0, 254, 28, 35, 1 }, + { 211, 165, 72, 140, 197, 187, 1 }, + { 226, 28, 63, 128, 125, 114, 1 }, }, + { { 241, 9, 85, 233, 4, 138, 1 }, + { 2, 232, 77, 201, 116, 54, 0 }, + { 168, 144, 75, 213, 72, 71, 1 }, + { 54, 23, 73, 217, 11, 160, 0 }, }, + { { 243, 42, 138, 178, 12, 104, 1 }, + { 182, 160, 6, 138, 242, 184, 1 }, + { 139, 24, 38, 168, 170, 103, 1 }, + { 142, 167, 168, 176, 2, 182, 1 }, }, + { { 243, 71, 148, 129, 100, 1, 1 }, + { 202, 84, 4, 206, 84, 60, 0 }, + { 192, 19, 64, 148, 241, 103, 1 }, + { 30, 21, 57, 144, 21, 41, 1 }, }, + { { 243, 136, 79, 37, 132, 41, 1 }, + { 162, 164, 37, 73, 123, 52, 1 }, + { 202, 16, 210, 121, 8, 231, 1 }, + { 150, 111, 73, 82, 18, 162, 1 }, }, + { { 240, 162, 31, 6, 126, 77, 1 }, + { 46, 84, 6, 57, 251, 173, 0 }, + { 217, 63, 48, 124, 34, 135, 1 }, + { 90, 239, 206, 48, 21, 58, 0 }, }, + { { 243, 170, 11, 255, 165, 205, 0 }, + { 188, 172, 118, 217, 251, 58, 0 }, + { 89, 210, 255, 232, 42, 231, 1 }, + { 46, 111, 205, 183, 26, 158, 1 }, }, + { { 240, 175, 124, 121, 142, 228, 0 }, + { 116, 232, 231, 120, 92, 174, 1 }, + { 19, 184, 207, 31, 122, 135, 1 }, + { 186, 157, 15, 115, 139, 151, 0 }, }, + { { 240, 217, 16, 212, 202, 31, 1 }, + { 18, 213, 108, 188, 61, 224, 0 }, + { 252, 41, 149, 132, 77, 135, 1 }, + { 3, 222, 30, 155, 85, 164, 0 }, }, + { { 245, 75, 179, 178, 85, 245, 1 }, + { 23, 252, 148, 159, 214, 121, 1 }, + { 215, 213, 38, 230, 233, 87, 1 }, + { 207, 53, 252, 148, 159, 244, 0 }, }, + { { 246, 81, 96, 41, 26, 234, 1 }, + { 135, 41, 141, 108, 52, 163, 1 }, + { 171, 172, 74, 3, 69, 55, 1 }, + { 226, 150, 27, 88, 202, 112, 1 }, }, + { { 246, 117, 82, 145, 217, 185, 1 }, + { 211, 93, 55, 204, 54, 225, 1 }, + { 206, 205, 196, 165, 87, 55, 1 }, + { 195, 182, 25, 246, 93, 101, 1 }, }, + { { 245, 114, 198, 173, 192, 65, 0 }, + { 5, 53, 39, 206, 19, 62, 0 }, + { 65, 1, 218, 177, 167, 87, 1 }, + { 62, 100, 57, 242, 86, 80, 0 }, }, + { { 247, 124, 210, 85, 227, 70, 0 }, + { 221, 209, 127, 126, 19, 48, 0 }, + { 49, 99, 213, 37, 159, 119, 1 }, + { 6, 100, 63, 127, 69, 221, 1 }, }, + { { 246, 132, 78, 31, 171, 28, 0 }, + { 249, 0, 53, 120, 187, 230, 0 }, + { 28, 106, 252, 57, 16, 183, 1 }, + { 51, 238, 143, 86, 0, 79, 1 }, }, + { { 248, 61, 136, 57, 165, 1, 1 }, + { 122, 167, 54, 74, 84, 34, 0 }, + { 192, 82, 206, 8, 222, 15, 1 }, + { 34, 21, 41, 54, 114, 175, 0 }, }, + { { 251, 54, 195, 223, 220, 215, 1 }, + { 214, 31, 111, 219, 211, 251, 0 }, + { 245, 157, 253, 225, 182, 111, 1 }, + { 111, 229, 237, 251, 124, 53, 1 }, }, + { { 248, 76, 172, 3, 76, 20, 1 }, + { 98, 146, 132, 94, 208, 228, 0 }, + { 148, 25, 96, 26, 153, 15, 1 }, + { 19, 133, 189, 16, 164, 163, 0 }, }, + { { 251, 160, 208, 185, 211, 75, 1 }, + { 150, 118, 63, 234, 56, 51, 0 }, + { 233, 101, 206, 133, 130, 239, 1 }, + { 102, 14, 43, 254, 55, 52, 1 }, }, + { { 250, 195, 132, 204, 125, 54, 1 }, + { 138, 18, 92, 158, 93, 239, 1 }, + { 182, 95, 25, 144, 225, 175, 1 }, + { 251, 221, 60, 157, 36, 40, 1 }, }, + { { 249, 231, 61, 60, 30, 97, 1 }, + { 118, 102, 134, 45, 93, 191, 1 }, + { 195, 60, 30, 94, 115, 207, 1 }, + { 254, 221, 90, 48, 179, 55, 0 }, }, + { { 248, 255, 88, 101, 6, 119, 1 }, + { 102, 231, 79, 124, 93, 104, 1 }, + { 247, 48, 83, 13, 127, 143, 1 }, + { 139, 93, 31, 121, 115, 179, 0 }, }, + { { 253, 11, 219, 85, 100, 56, 1 }, + { 59, 210, 69, 75, 119, 120, 1 }, + { 142, 19, 85, 109, 232, 95, 1 }, + { 143, 119, 105, 81, 37, 238, 0 }, }, + { { 253, 22, 174, 228, 229, 62, 1 }, + { 107, 51, 252, 154, 115, 124, 1 }, + { 190, 83, 147, 186, 180, 95, 1 }, + { 159, 103, 44, 159, 230, 107, 0 }, }, + { { 254, 73, 163, 120, 194, 52, 0 }, + { 145, 178, 228, 63, 22, 98, 1 }, + { 22, 33, 143, 98, 201, 63, 1 }, + { 163, 52, 126, 19, 166, 196, 1 }, }, + { { 253, 132, 226, 198, 107, 168, 1 }, + { 75, 26, 213, 170, 187, 176, 1 }, + { 138, 235, 49, 163, 144, 223, 1 }, + { 134, 238, 170, 213, 172, 105, 0 }, }, + { { 252, 149, 79, 148, 230, 207, 0 }, + { 125, 31, 45, 185, 127, 36, 0 }, + { 121, 179, 148, 249, 84, 159, 1 }, + { 18, 127, 78, 218, 124, 95, 0 }, }, + { { 254, 165, 35, 88, 48, 164, 1 }, + { 219, 10, 198, 25, 30, 35, 1 }, + { 146, 134, 13, 98, 82, 191, 1 }, + { 226, 60, 76, 49, 168, 109, 1 }, }, + { { 254, 168, 217, 255, 176, 118, 0 }, + { 189, 226, 111, 219, 153, 99, 1 }, + { 55, 6, 255, 205, 138, 191, 1 }, + { 227, 76, 237, 251, 35, 222, 1 }, }, + { { 255, 195, 135, 255, 162, 252, 1 }, + { 159, 42, 100, 255, 191, 126, 1 }, + { 159, 162, 255, 240, 225, 255, 1 }, + { 191, 126, 255, 147, 42, 124, 1 }, }, + { { 95, 114, 124, 241, 17, 248, 0 }, + { 181, 107, 215, 196, 48, 93, 1 }, + { 15, 196, 71, 159, 39, 125, 0 }, + { 221, 6, 17, 245, 235, 86, 1 }, }, + { { 135, 81, 63, 164, 61, 190, 1 }, + { 171, 105, 152, 149, 103, 245, 1 }, + { 190, 222, 18, 254, 69, 112, 1 }, + { 215, 243, 84, 140, 203, 106, 1 }, }, + { { 198, 147, 77, 53, 31, 189, 0 }, + { 177, 45, 17, 113, 125, 237, 1 }, + { 94, 252, 86, 89, 100, 177, 1 }, + { 219, 223, 71, 68, 90, 70, 1 }, }, + { { 231, 230, 150, 226, 6, 159, 1 }, + { 195, 108, 74, 190, 250, 124, 0 }, + { 252, 176, 35, 180, 179, 243, 1 }, + { 31, 47, 190, 169, 27, 97, 1 }, }, }; +} diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index 9cd0a26fc0..924c88b58c 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -8,6 +8,7 @@ #include "precomp.hpp" #include "opencv2/objdetect.hpp" #include "opencv2/3d.hpp" +#include #ifdef HAVE_QUIRC #include "quirc.h" @@ -15,7 +16,6 @@ #include #include -#include #include #include #include @@ -23,6 +23,7 @@ namespace cv { using std::vector; +using std::pair; static bool checkQRInputImage(InputArray img, Mat& gray) { @@ -136,7 +137,7 @@ void QRDetect::init(const Mat& src, double eps_vertical_, double eps_horizontal_ const int width = cvRound(src.size().width * coeff_expansion); const int height = cvRound(src.size().height * coeff_expansion); Size new_size(width, height); - resize(src, barcode, new_size, 0, 0, INTER_LINEAR); + resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); } else if (min_side > 512.0) { @@ -524,7 +525,7 @@ bool QRDetect::localization() const int height = cvRound(bin_barcode.size().height * coeff_expansion); Size new_size(width, height); Mat intermediate; - resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR); + resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT); bin_barcode = intermediate.clone(); for (size_t i = 0; i < localization_points.size(); i++) { @@ -537,7 +538,7 @@ bool QRDetect::localization() const int height = cvRound(bin_barcode.size().height / coeff_expansion); Size new_size(width, height); Mat intermediate; - resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR); + resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT); bin_barcode = intermediate.clone(); for (size_t i = 0; i < localization_points.size(); i++) { @@ -948,6 +949,7 @@ vector QRDetect::getQuadrilateral(vector angle_list) return result_angle_list; } + struct QRCodeDetector::Impl { public: @@ -955,9 +957,13 @@ public: ~Impl() {} double epsX, epsY; + vector> alignmentMarkers; + vector updateQrCorners; + bool useAlignmentMarkers = true; }; QRCodeDetector::QRCodeDetector() : p(new Impl) {} + QRCodeDetector::~QRCodeDetector() {} void QRCodeDetector::setEpsX(double epsX) { p->epsX = epsX; } @@ -981,6 +987,7 @@ bool QRCodeDetector::detect(InputArray in, OutputArray points) const class QRDecode { public: + QRDecode(bool useAlignmentMarkers); void init(const Mat &src, const vector &points); Mat getIntermediateBarcode() { return intermediate; } Mat getStraightBarcode() { return straight; } @@ -988,9 +995,23 @@ public: std::string getDecodeInformation() { return result_info; } bool straightDecodingProcess(); bool curvedDecodingProcess(); + vector alignment_coords; + float coeff_expansion = 1.f; + vector getOriginalPoints() {return original_points;} + bool useAlignmentMarkers; protected: - bool updatePerspective(); + double getNumModules(); + Mat getHomography() { + CV_TRACE_FUNCTION(); + vector perspective_points = {{0.f, 0.f}, {test_perspective_size, 0.f}, + {test_perspective_size, test_perspective_size}, + {0.f, test_perspective_size}}; + vector pts = original_points; + return findHomography(pts, perspective_points); + } + bool updatePerspective(const Mat& H); bool versionDefinition(); + void detectAlignment(); bool samplingForVersion(); bool decodingProcess(); inline double pointPosition(Point2f a, Point2f b , Point2f c); @@ -1019,6 +1040,7 @@ protected: const static int NUM_SIDES = 2; Mat original, bin_barcode, no_border_intermediate, intermediate, straight, curved_to_straight, test_image; vector original_points; + Mat homography; vector original_curved_points; vector qrcode_locations; vector > closest_points; @@ -1070,6 +1092,7 @@ float static getMinSideLen(const vector &points) { return static_cast(res); } + void QRDecode::init(const Mat &src, const vector &points) { CV_TRACE_FUNCTION(); @@ -2180,6 +2203,8 @@ bool QRDecode::straightenQRCodeInParts() pts.push_back(center_point); Mat H = findHomography(pts, perspective_points); + if (H.empty()) + return false; Mat temp_intermediate(temporary_size, CV_8UC1); warpPerspective(test_mask, temp_intermediate, H, temporary_size, INTER_NEAREST); perspective_result += temp_intermediate; @@ -2212,6 +2237,8 @@ bool QRDecode::straightenQRCodeInParts() perspective_straight_points.push_back(Point2f(perspective_curved_size * 0.5f, perspective_curved_size * 0.5f)); Mat H = findHomography(original_curved_points, perspective_straight_points); + if (H.empty()) + return false; warpPerspective(inversion, temp_result, H, temporary_size, INTER_NEAREST, BORDER_REPLICATE); no_border_intermediate = temp_result(Range(1, temp_result.rows), Range(1, temp_result.cols)); @@ -2250,33 +2277,124 @@ bool QRDecode::preparingCurvedQRCodes() return true; } -bool QRDecode::updatePerspective() -{ - CV_TRACE_FUNCTION(); - const Point2f centerPt = intersectionLines(original_points[0], original_points[2], - original_points[1], original_points[3]); - if (cvIsNaN(centerPt.x) || cvIsNaN(centerPt.y)) +/** + * @param finderPattern 4 points of finder pattern markers, calculated by findPatternsVerticesPoints() + * @return true if the pattern has the correct side lengths + */ +static inline bool checkFinderPatternByAspect(const vector &finderPattern) { + if (finderPattern.size() != 4ull) return false; + float sidesLen[4]; + for (size_t i = 0; i < finderPattern.size(); i++) { + sidesLen[i] = (sqrt(normL2Sqr(Point2f(finderPattern[i] - finderPattern[(i+1ull)%finderPattern.size()])))); + } + const float maxSide = max(max(sidesLen[0], sidesLen[1]), max(sidesLen[2], sidesLen[3])); + const float minSide = min(min(sidesLen[0], sidesLen[1]), min(sidesLen[2], sidesLen[3])); - const Size temporary_size(cvRound(test_perspective_size), cvRound(test_perspective_size)); + const float patternMaxRelativeLen = .3f; + if (1.f - minSide / maxSide > patternMaxRelativeLen) + return false; + return true; +} - vector perspective_points; - perspective_points.push_back(Point2f(0.f, 0.f)); - perspective_points.push_back(Point2f(test_perspective_size, 0.f)); +/** + * @param finderPattern - 4 points of finder pattern markers, calculated by findPatternsVerticesPoints() + * @param cornerPointsQR - 4 corner points of QR code + * @return pair first - the index in points of finderPattern closest to the corner of the QR code, + * second - the index in points of cornerPointsQR closest to the corner of finderPattern + * + * This function matches finder patterns to the corners of the QR code. Points of finder pattern calculated by + * findPatternsVerticesPoints() may be erroneous, so they are checked. + */ +static inline std::pair matchPatternPoints(const vector &finderPattern, + const vector& cornerPointsQR) { + if (!checkFinderPatternByAspect(finderPattern)) + return std::make_pair(-1, -1); - perspective_points.push_back(Point2f(test_perspective_size, test_perspective_size)); - perspective_points.push_back(Point2f(0.f, test_perspective_size)); + float distanceToOrig = normL2Sqr(Point2f(finderPattern[0]) - cornerPointsQR[0]); + int closestFinderPatternV = 0; + int closetOriginalV = 0; - perspective_points.push_back(Point2f(test_perspective_size * 0.5f, test_perspective_size * 0.5f)); + for (size_t i = 0ull; i < finderPattern.size(); i++) { + for (size_t j = 0ull; j < cornerPointsQR.size(); j++) { + const float tmp = normL2Sqr(Point2f(finderPattern[i]) - cornerPointsQR[j]); + if (tmp < distanceToOrig) { + distanceToOrig = tmp; + closestFinderPatternV = (int)i; + closetOriginalV = (int)j; + } + } + } + distanceToOrig = sqrt(distanceToOrig); - vector pts = original_points; - pts.push_back(centerPt); + // check that the distance from the QR pattern to the corners of the QR code is small + const float originalQrSide = sqrt(normL2Sqr(cornerPointsQR[0] - cornerPointsQR[1]))*0.5f + + sqrt(normL2Sqr(cornerPointsQR[0] - cornerPointsQR[3]))*0.5f; + const float maxRelativeDistance = .1f; - Mat H = findHomography(pts, perspective_points); - Mat bin_original; - adaptiveThreshold(original, bin_original, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 83, 2); + if (distanceToOrig/originalQrSide > maxRelativeDistance) + return std::make_pair(-1, -1); + return std::make_pair(closestFinderPatternV, closetOriginalV); +} + +double QRDecode::getNumModules() { + vector> finderPatterns; + double numModulesX = 0., numModulesY = 0.; + if (findPatternsVerticesPoints(finderPatterns)) { + double pattern_distance[4] = {0.,0.,0.,0.}; + for (auto& pattern : finderPatterns) { + auto indexes = matchPatternPoints(pattern, original_points); + if (indexes == std::make_pair(-1, -1)) + return 0.; + Point2f vf[4] = {pattern[indexes.first % 4], pattern[(1+indexes.first) % 4], + pattern[(2+indexes.first) % 4], pattern[(3+indexes.first) % 4]}; + for (int i = 1; i < 4; i++) { + pattern_distance[indexes.second] += (norm(vf[i] - vf[i-1])); + } + pattern_distance[indexes.second] += norm(vf[3] - vf[0]); + pattern_distance[indexes.second] /= 4.; + } + const double moduleSizeX = (pattern_distance[0] + pattern_distance[1])/(2.*7.); + const double moduleSizeY = (pattern_distance[0] + pattern_distance[3])/(2.*7.); + numModulesX = norm(original_points[1] - original_points[0])/moduleSizeX; + numModulesY = norm(original_points[3] - original_points[0])/moduleSizeY; + } + return (numModulesX + numModulesY)/2.; +} + +// use code from https://stackoverflow.com/questions/13238704/calculating-the-position-of-qr-code-alignment-patterns +static inline vector> getAlignmentCoordinates(int version) { + if (version <= 1) return {}; + int intervals = (version / 7) + 1; // Number of gaps between alignment patterns + int distance = 4 * version + 4; // Distance between first and last alignment pattern + int step = cvRound((double)distance / (double)intervals); // Round equal spacing to nearest integer + step += step & 0b1; // Round step to next even number + vector coordinates((size_t)intervals + 1ull); + coordinates[0] = 6; // First coordinate is always 6 (can't be calculated with step) + for (int i = 1; i <= intervals; i++) { + coordinates[i] = (6 + distance - step * (intervals - i)); // Start right/bottom and go left/up by step*k + } + if (version >= 7) { + return {std::make_pair(coordinates.back(), coordinates.back()), + std::make_pair(coordinates.back(), coordinates[coordinates.size()-2]), + std::make_pair(coordinates[coordinates.size()-2], coordinates.back()), + std::make_pair(coordinates[coordinates.size()-2], coordinates[coordinates.size()-2]), + std::make_pair(coordinates[0], coordinates[1]), + std::make_pair(coordinates[1], coordinates[0]), + }; + } + return {std::make_pair(coordinates.back(), coordinates.back())}; +} + + +bool QRDecode::updatePerspective(const Mat& H) +{ + if (H.empty()) + return false; + homography = H; Mat temp_intermediate; - warpPerspective(bin_original, temp_intermediate, H, temporary_size, INTER_NEAREST); + const Size temporary_size(cvRound(test_perspective_size), cvRound(test_perspective_size)); + warpPerspective(bin_barcode, temp_intermediate, H, temporary_size, INTER_NEAREST); no_border_intermediate = temp_intermediate(Range(1, temp_intermediate.rows), Range(1, temp_intermediate.cols)); const int border = cvRound(0.1 * test_perspective_size); @@ -2285,7 +2403,7 @@ bool QRDecode::updatePerspective() return true; } -inline Point computeOffset(const vector& v) +static inline Point computeOffset(const vector& v) { // compute the width/height of convex hull Rect areaBox = boundingRect(v); @@ -2299,9 +2417,80 @@ inline Point computeOffset(const vector& v) return offset; } +// QR code with version 7 or higher has a special 18 bit version number code. +// @return std::pair first - distance to estimatedVersion, second - version +/** + * @param numModules - estimated numModules + * @param estimatedVersion + * @return pair, first - Hamming distance to 18 bit code, second - closest version + * + * QR code with version 7 or higher has a special 18 bit version number code: + * https://www.thonky.com/qr-code-tutorial/format-version-information + */ +static inline std::pair getVersionByCode(double numModules, Mat qr, int estimatedVersion) { + const double moduleSize = qr.rows / numModules; + Point2d startVersionInfo1 = Point2d((numModules-8.-3.)*moduleSize, 0.); + Point2d endVersionInfo1 = Point2d((numModules-8.)*moduleSize, moduleSize*6.); + Point2d startVersionInfo2 = Point2d(0., (numModules-8.-3.)*moduleSize); + Point2d endVersionInfo2 = Point2d(moduleSize*6., (numModules-8.)*moduleSize); + Mat v1(qr, Rect2d(startVersionInfo1, endVersionInfo1)); + Mat v2(qr, Rect2d(startVersionInfo2, endVersionInfo2)); + const double thresh = 127.; + resize(v1, v1, Size(3, 6), 0., 0., INTER_AREA); + threshold(v1, v1, thresh, 255, THRESH_BINARY); + resize(v2, v2, Size(6, 3), 0., 0., INTER_AREA); + threshold(v2, v2, thresh, 255, THRESH_BINARY); + + Mat version1, version2; + // convert version1 (top right version information block) and + // version2 (bottom left version information block) to version table format + // https://www.thonky.com/qr-code-tutorial/format-version-tables + rotate((255-v1)/255, version1, ROTATE_180), rotate(((255-v2)/255).t(), version2, ROTATE_180); + + static uint8_t versionCodes[][18] = {{0,0,0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0},{0,0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0}, + {0,0,1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1},{0,0,1,0,1,0,0,1,0,0,1,1,0,1,0,0,1,1}, + {0,0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,0},{0,0,1,1,0,0,0,1,1,1,0,1,1,0,0,0,1,0}, + {0,0,1,1,0,1,1,0,0,0,0,1,0,0,0,1,1,1},{0,0,1,1,1,0,0,1,1,0,0,0,0,0,1,1,0,1}, + {0,0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0,0},{0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0,0}, + {0,1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,1},{0,1,0,0,1,0,1,0,1,0,0,0,0,1,0,1,1,1}, + {0,1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1,0},{0,1,0,1,0,0,1,0,0,1,1,0,1,0,0,1,1,0}, + {0,1,0,1,0,1,0,1,1,0,1,0,0,0,0,0,1,1},{0,1,0,1,1,0,1,0,0,0,1,1,0,0,1,0,0,1}, + {0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,1,0,0},{0,1,1,0,0,0,1,1,1,0,1,1,0,0,0,1,0,0}, + {0,1,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,1},{0,1,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1}, + {0,1,1,0,1,1,0,0,0,0,1,0,0,0,1,1,1,0},{0,1,1,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0}, + {0,1,1,1,0,1,0,0,1,1,0,0,1,1,1,1,1,1},{0,1,1,1,1,0,1,1,0,1,0,1,1,1,0,1,0,1}, + {0,1,1,1,1,1,0,0,1,0,0,1,0,1,0,0,0,0},{1,0,0,0,0,0,1,0,0,1,1,1,0,1,0,1,0,1}, + {1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,0,0,0},{1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,1,0}, + {1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,1,1},{1,0,0,1,0,0,1,0,1,1,0,0,0,0,1,0,1,1}, + {1,0,0,1,0,1,0,1,0,0,0,0,1,0,1,1,1,0},{1,0,0,1,1,0,1,0,1,0,0,1,1,0,0,1,0,0}, + {1,0,0,1,1,1,0,1,0,1,0,1,0,0,0,0,0,1},{1,0,1,0,0,0,1,1,0,0,0,1,1,0,1,0,0,1} + }; + double minDist = 19.; + int bestVersion = -1; + const double penaltyFactor = 0.8; + + for (int i = 0; i < (int)(sizeof(versionCodes)/sizeof(versionCodes[0])); i++) { + Mat currVers(Size(3, 6), CV_8UC1, versionCodes[i]); + // minimum hamming distance between version = 8 + double tmp = norm(currVers, version1, NORM_HAMMING) + penaltyFactor*abs(estimatedVersion-i-7); + if (tmp < minDist) { + bestVersion = i+7; + minDist = tmp; + } + tmp = norm(currVers, version2, NORM_HAMMING) + penaltyFactor*abs(estimatedVersion-i-7); + if (tmp < minDist) { + bestVersion = i+7; + minDist = tmp; + } + } + return std::make_pair(minDist, bestVersion); +} + bool QRDecode::versionDefinition() { CV_TRACE_FUNCTION(); + CV_LOG_DEBUG(NULL, "QR corners: " << original_points[0] << " " << original_points[1] << " " << original_points[2] << + " " << original_points[3]); LineIterator line_iter(intermediate, Point2f(0, 0), Point2f(test_perspective_size, test_perspective_size)); Point black_point = Point(0, 0); for(int j = 0; j < line_iter.count; j++, ++line_iter) @@ -2359,12 +2548,120 @@ bool QRDecode::versionDefinition() transition_y++; } } - version = saturate_cast((std::min(transition_x, transition_y) - 1) * 0.25 - 1); - if ( !( 0 < version && version <= 40 ) ) { return false; } + + const int versionByTransition = saturate_cast((std::min(transition_x, transition_y) - 1) * 0.25 - 1); + const int numModulesByTransition = 21 + (versionByTransition - 1) * 4; + + const double numModulesByFinderPattern = getNumModules(); + const double versionByFinderPattern = (numModulesByFinderPattern - 21.) * .25 + 1.; + bool useFinderPattern = false; + const double thresholdFinderPattern = 0.2; + const double roundingError = abs(numModulesByFinderPattern - cvRound(numModulesByFinderPattern)); + + if (cvRound(versionByFinderPattern) >= 1 && versionByFinderPattern <= 6. && + transition_x != transition_y && roundingError < thresholdFinderPattern) { + useFinderPattern = true; + } + + bool useCode = false; + int versionByCode = 7; + if (cvRound(versionByFinderPattern) >= 7 || versionByTransition >= 7) { + vector> versionAndDistances; + if (cvRound(versionByFinderPattern) >= 7) { + versionAndDistances.push_back(getVersionByCode(numModulesByFinderPattern, no_border_intermediate, + cvRound(versionByFinderPattern))); + } + if (versionByTransition >= 7) { + versionAndDistances.push_back(getVersionByCode(numModulesByTransition, no_border_intermediate, + versionByTransition)); + } + const auto& bestVersion = min(versionAndDistances.front(), versionAndDistances.back()); + double distanceByCode = bestVersion.first; + versionByCode = bestVersion.second; + if (distanceByCode < 5.) { + useCode = true; + } + } + + if (useCode) { + CV_LOG_DEBUG(NULL, "Version type: useCode"); + version = (uint8_t)versionByCode; + } + else if (useFinderPattern ) { + CV_LOG_DEBUG(NULL, "Version type: useFinderPattern"); + version = (uint8_t)cvRound(versionByFinderPattern); + } + else { + CV_LOG_DEBUG(NULL, "Version type: useTransition"); + version = (uint8_t)versionByTransition; + } version_size = 21 + (version - 1) * 4; + if ( !(0 < version && version <= 40) ) { return false; } + CV_LOG_DEBUG(NULL, "QR version: " << (int)version); return true; } +void QRDecode::detectAlignment() { + vector> alignmentPositions = getAlignmentCoordinates(version); + if (alignmentPositions.size() > 0) { + vector perspective_points = {{0.f, 0.f}, {test_perspective_size, 0.f}, {0.f, test_perspective_size}}; + vector object_points = {original_points[0], original_points[1], original_points[3]}; + + // create alignment image + static uint8_t alignmentMarker[25] = { + 0, 0, 0, 0, 0, + 0, 255, 255, 255, 0, + 0, 255, 0, 255, 0, + 0, 255, 255, 255, 0, + 0, 0, 0, 0, 0 + }; + Mat alignmentMarkerMat(5, 5, CV_8UC1, alignmentMarker); + const float module_size = test_perspective_size / version_size; + Mat resizedAlignmentMarker; + resize(alignmentMarkerMat, resizedAlignmentMarker, + Size(cvRound(module_size * 5.f), cvRound(module_size * 5.f)), 0, 0, INTER_AREA); + const float module_offset = 1.9f; + const float offset = (module_size * (5 + module_offset * 2)); // 5 modules in alignment marker, 2 x module_offset modules in offset + for (const pair& alignmentPos : alignmentPositions) { + const float left_top_x = (module_size * (alignmentPos.first - 2.f - module_offset)); // add offset + const float left_top_y = (module_size * (alignmentPos.second - 2.f - module_offset)); // add offset + Mat subImage(no_border_intermediate, Rect(cvRound(left_top_x), cvRound(left_top_y), cvRound(offset), cvRound(offset))); + Mat resTemplate; + matchTemplate(subImage, resizedAlignmentMarker, resTemplate, TM_CCOEFF_NORMED); + double minVal = 0., maxVal = 0.; + Point minLoc, maxLoc, matchLoc; + minMaxLoc(resTemplate, &minVal, &maxVal, &minLoc, &maxLoc); + CV_LOG_DEBUG(NULL, "Alignment maxVal: " << maxVal); + if (maxVal > 0.65) { + const float templateOffset = static_cast(resizedAlignmentMarker.size().width) / 2.f; + Point2f alignmentCoord(Point2f(maxLoc.x + left_top_x + templateOffset, maxLoc.y + left_top_y + templateOffset)); + alignment_coords.push_back(alignmentCoord); + perspectiveTransform(alignment_coords, alignment_coords, homography.inv()); + CV_LOG_DEBUG(NULL, "Alignment coords: " << alignment_coords); + const float relativePosX = (alignmentPos.first + 0.5f) / version_size; + const float relativePosY = (alignmentPos.second + 0.5f) / version_size; + perspective_points.push_back({relativePosX * test_perspective_size, relativePosY * test_perspective_size}); + object_points.push_back(alignment_coords.back()); + } + } + if (object_points.size() > 3ull) { + double ransacReprojThreshold = 10.; + if (version == 2) { // in low version original_points[2] may be calculated more accurately using intersections method + object_points.push_back(original_points[2]); + ransacReprojThreshold = 5.; // set more strict ransacReprojThreshold + perspective_points.push_back({test_perspective_size, test_perspective_size}); + } + Mat H = findHomography(object_points, perspective_points, RANSAC, ransacReprojThreshold); + if (H.empty()) + return; + updatePerspective(H); + vector newCorner2 = {{test_perspective_size, test_perspective_size}}; + perspectiveTransform(newCorner2, newCorner2, H.inv()); + original_points[2] = newCorner2.front(); + } + } +} + bool QRDecode::samplingForVersion() { CV_TRACE_FUNCTION(); @@ -2451,8 +2748,10 @@ bool QRDecode::decodingProcess() bool QRDecode::straightDecodingProcess() { #ifdef HAVE_QUIRC - if (!updatePerspective()) { return false; } + if (!updatePerspective(getHomography())) { return false; } if (!versionDefinition()) { return false; } + if (useAlignmentMarkers) + detectAlignment(); if (!samplingForVersion()) { return false; } if (!decodingProcess()) { return false; } return true; @@ -2476,6 +2775,13 @@ bool QRDecode::curvedDecodingProcess() #endif } +QRDecode::QRDecode(bool _useAlignmentMarkers): + useAlignmentMarkers(_useAlignmentMarkers), + version(0), + version_size(0), + test_perspective_size(0.f) + {} + std::string QRCodeDetector::decode(InputArray in, InputArray points, OutputArray straight_qrcode) { @@ -2488,7 +2794,7 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points, CV_Assert(src_points.size() == 4); CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points"); - QRDecode qrdec; + QRDecode qrdec(p->useAlignmentMarkers); qrdec.init(inarr, src_points); bool ok = qrdec.straightDecodingProcess(); @@ -2501,7 +2807,10 @@ std::string QRCodeDetector::decode(InputArray in, InputArray points, { qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1); } - + if (ok && !decoded_info.empty()) { + p->alignmentMarkers = {qrdec.alignment_coords}; + p->updateQrCorners = qrdec.getOriginalPoints(); + } return ok ? decoded_info : std::string(); } @@ -2517,7 +2826,7 @@ cv::String QRCodeDetector::decodeCurved(InputArray in, InputArray points, CV_Assert(src_points.size() == 4); CV_CheckGT(contourArea(src_points), 0.0, "Invalid QR code source points"); - QRDecode qrdec; + QRDecode qrdec(p->useAlignmentMarkers); qrdec.init(inarr, src_points); bool ok = qrdec.curvedDecodingProcess(); @@ -2742,7 +3051,7 @@ void QRDetectMulti::init(const Mat& src, double eps_vertical_, double eps_horizo const int width = cvRound(src.size().width * coeff_expansion); const int height = cvRound(src.size().height * coeff_expansion); Size new_size(width, height); - resize(src, barcode, new_size, 0, 0, INTER_LINEAR); + resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); } else if (min_side > 512.0) { @@ -3099,7 +3408,7 @@ int QRDetectMulti::findNumberLocalizationPoints(vector& tmp_localizatio const int height = cvRound(bin_barcode.size().height * coeff_expansion); Size new_size(width, height); Mat intermediate; - resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR); + resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT); bin_barcode = intermediate.clone(); } else if (purpose == ZOOMING) @@ -3108,7 +3417,7 @@ int QRDetectMulti::findNumberLocalizationPoints(vector& tmp_localizatio const int height = cvRound(bin_barcode.size().height / coeff_expansion); Size new_size(width, height); Mat intermediate; - resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR); + resize(bin_barcode, intermediate, new_size, 0, 0, INTER_LINEAR_EXACT); bin_barcode = intermediate.clone(); } else @@ -3126,7 +3435,7 @@ void QRDetectMulti::findQRCodeContours(vector& tmp_localization_points, const int width = cvRound(bin_barcode.size().width); const int height = cvRound(bin_barcode.size().height); Size new_size(width, height); - resize(bar, bar, new_size, 0, 0, INTER_LINEAR); + resize(bar, bar, new_size, 0, 0, INTER_LINEAR_EXACT); blur(bar, blur_image, Size(3, 3)); threshold(blur_image, threshold_output, 50, 255, THRESH_BINARY); @@ -3552,15 +3861,15 @@ public: else if (std::min(inarr.size().width, inarr.size().height) > 512) { const int min_side = std::min(inarr.size().width, inarr.size().height); - double coeff_expansion = min_side / 512; - const int width = cvRound(inarr.size().width / coeff_expansion); - const int height = cvRound(inarr.size().height / coeff_expansion); + qrdec[i].coeff_expansion = min_side / 512.f; + const int width = cvRound(inarr.size().width / qrdec[i].coeff_expansion); + const int height = cvRound(inarr.size().height / qrdec[i].coeff_expansion); Size new_size(width, height); Mat inarr2; resize(inarr, inarr2, new_size, 0, 0, INTER_AREA); - for (size_t j = 0; j < 4; j++) + for (size_t j = 0ull; j < 4ull; j++) { - src_points[i][j] /= static_cast(coeff_expansion); + src_points[i][j] /= qrdec[i].coeff_expansion; } qrdec[i].init(inarr2, src_points[i]); ok = qrdec[i].straightDecodingProcess(); @@ -3568,6 +3877,8 @@ public: { decoded_info[i] = qrdec[i].getDecodeInformation(); straight_barcode[i] = qrdec[i].getStraightBarcode(); + for (size_t j = 0ull; j < qrdec[i].alignment_coords.size(); j++) + qrdec[i].alignment_coords[j] *= qrdec[i].coeff_expansion; } } if (decoded_info[i].empty()) @@ -3608,7 +3919,7 @@ bool QRCodeDetector::decodeMulti( } } CV_Assert(src_points.size() > 0); - vector qrdec(src_points.size()); + vector qrdec(src_points.size(), p->useAlignmentMarkers); vector straight_barcode(src_points.size()); vector info(src_points.size()); ParallelDecodeProcess parallelDecodeProcess(inarr, qrdec, info, straight_barcode, src_points); @@ -3639,6 +3950,13 @@ bool QRCodeDetector::decodeMulti( { decoded_info.push_back(info[i]); } + p->alignmentMarkers.resize(src_points.size()); + p->updateQrCorners.resize(src_points.size()*4ull); + for (size_t i = 0ull; i < src_points.size(); i++) { + p->alignmentMarkers[i] = qrdec[i].alignment_coords; + for (size_t j = 0ull; j < 4ull; j++) + p->updateQrCorners[i*4ull+j] = qrdec[i].getOriginalPoints()[j] * qrdec[i].coeff_expansion; + } if (!decoded_info.empty()) return true; else @@ -3669,7 +3987,13 @@ bool QRCodeDetector::detectAndDecodeMulti( updatePointsResult(points_, points); decoded_info.clear(); ok = decodeMulti(inarr, points, decoded_info, straight_qrcode); + updatePointsResult(points_, p->updateQrCorners); return ok; } +void QRCodeDetector::setUseAlignmentMarkers(bool useAlignmentMarkers) { + p->useAlignmentMarkers = useAlignmentMarkers; +} + + } // namespace diff --git a/modules/objdetect/test/test_aruco_utils.cpp b/modules/objdetect/test/test_aruco_utils.cpp new file mode 100644 index 0000000000..b301622b60 --- /dev/null +++ b/modules/objdetect/test/test_aruco_utils.cpp @@ -0,0 +1,205 @@ +// 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" + +#include "test_aruco_utils.hpp" + +namespace opencv_test { + +vector getAxis(InputArray _cameraMatrix, InputArray _distCoeffs, InputArray _rvec, + InputArray _tvec, float length, const float offset) { + vector axis; + axis.push_back(Point3f(offset, offset, 0.f)); + axis.push_back(Point3f(length+offset, offset, 0.f)); + axis.push_back(Point3f(offset, length+offset, 0.f)); + axis.push_back(Point3f(offset, offset, length)); + vector axis_to_img; + projectPoints(axis, _rvec, _tvec, _cameraMatrix, _distCoeffs, axis_to_img); + return axis_to_img; +} + +vector getMarkerById(int id, const vector >& corners, const vector& ids) { + for (size_t i = 0ull; i < ids.size(); i++) + if (ids[i] == id) + return corners[i]; + return vector(); +} + +void getSyntheticRT(double yaw, double pitch, double distance, Mat& rvec, Mat& tvec) { + rvec = Mat::zeros(3, 1, CV_64FC1); + tvec = Mat::zeros(3, 1, CV_64FC1); + + // rotate "scene" in pitch axis (x-axis) + Mat rotPitch(3, 1, CV_64FC1); + rotPitch.at(0) = -pitch; + rotPitch.at(1) = 0; + rotPitch.at(2) = 0; + + // rotate "scene" in yaw (y-axis) + Mat rotYaw(3, 1, CV_64FC1); + rotYaw.at(0) = 0; + rotYaw.at(1) = yaw; + rotYaw.at(2) = 0; + + // compose both rotations + composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw, + Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec); + + // Tvec, just move in z (camera) direction the specific distance + tvec.at(0) = 0.; + tvec.at(1) = 0.; + tvec.at(2) = distance; +} + +void projectMarker(Mat& img, const aruco::Board& board, int markerIndex, Mat cameraMatrix, Mat rvec, Mat tvec, + int markerBorder) { + // canonical image + Mat markerImg; + const int markerSizePixels = 100; + aruco::generateImageMarker(board.getDictionary(), board.getIds()[markerIndex], markerSizePixels, markerImg, markerBorder); + + // projected corners + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + vector corners; + + // get max coordinate of board + Point3f maxCoord = board.getRightBottomCorner(); + // copy objPoints + vector objPoints = board.getObjPoints()[markerIndex]; + // move the marker to the origin + for (size_t i = 0; i < objPoints.size(); i++) + objPoints[i] -= (maxCoord / 2.f); + + projectPoints(objPoints, rvec, tvec, cameraMatrix, distCoeffs, corners); + + // get perspective transform + vector originalCorners; + originalCorners.push_back(Point2f(0, 0)); + originalCorners.push_back(Point2f((float)markerSizePixels, 0)); + originalCorners.push_back(Point2f((float)markerSizePixels, (float)markerSizePixels)); + originalCorners.push_back(Point2f(0, (float)markerSizePixels)); + Mat transformation = getPerspectiveTransform(originalCorners, corners); + + // apply transformation + Mat aux; + const char borderValue = 127; + warpPerspective(markerImg, aux, transformation, img.size(), INTER_NEAREST, BORDER_CONSTANT, + Scalar::all(borderValue)); + + // copy only not-border pixels + for (int y = 0; y < aux.rows; y++) { + for (int x = 0; x < aux.cols; x++) { + if (aux.at< unsigned char >(y, x) == borderValue) continue; + img.at< unsigned char >(y, x) = aux.at< unsigned char >(y, x); + } + } +} + +Mat projectBoard(const aruco::GridBoard& board, Mat cameraMatrix, double yaw, double pitch, double distance, + Size imageSize, int markerBorder) { + Mat rvec, tvec; + getSyntheticRT(yaw, pitch, distance, rvec, tvec); + + Mat img = Mat(imageSize, CV_8UC1, Scalar::all(255)); + for (unsigned int index = 0; index < board.getIds().size(); index++) + projectMarker(img, board, index, cameraMatrix, rvec, tvec, markerBorder); + return img; +} + +/** Check if a set of 3d points are enough for calibration. Z coordinate is ignored. + * Only axis parallel lines are considered */ +static bool _arePointsEnoughForPoseEstimation(const std::vector &points) { + if(points.size() < 4) return false; + + std::vector sameXValue; // different x values in points + std::vector sameXCounter; // number of points with the x value in sameXValue + for(unsigned int i = 0; i < points.size(); i++) { + bool found = false; + for(unsigned int j = 0; j < sameXValue.size(); j++) { + if(sameXValue[j] == points[i].x) { + found = true; + sameXCounter[j]++; + } + } + if(!found) { + sameXValue.push_back(points[i].x); + sameXCounter.push_back(1); + } + } + + // count how many x values has more than 2 points + int moreThan2 = 0; + for(unsigned int i = 0; i < sameXCounter.size(); i++) { + if(sameXCounter[i] >= 2) moreThan2++; + } + + // if we have more than 1 two xvalues with more than 2 points, calibration is ok + if(moreThan2 > 1) + return true; + return false; +} + +bool getCharucoBoardPose(InputArray charucoCorners, InputArray charucoIds, const aruco::CharucoBoard &board, + InputArray cameraMatrix, InputArray distCoeffs, InputOutputArray rvec, InputOutputArray tvec, + bool useExtrinsicGuess) { + CV_Assert((charucoCorners.getMat().total() == charucoIds.getMat().total())); + if(charucoIds.getMat().total() < 4) return false; // need, at least, 4 corners + + std::vector objPoints; + objPoints.reserve(charucoIds.getMat().total()); + for(unsigned int i = 0; i < charucoIds.getMat().total(); i++) { + int currId = charucoIds.getMat().at< int >(i); + CV_Assert(currId >= 0 && currId < (int)board.getChessboardCorners().size()); + objPoints.push_back(board.getChessboardCorners()[currId]); + } + + // points need to be in different lines, check if detected points are enough + if(!_arePointsEnoughForPoseEstimation(objPoints)) return false; + + solvePnP(objPoints, charucoCorners, cameraMatrix, distCoeffs, rvec, tvec, useExtrinsicGuess); + return true; +} + +/** + * @brief Return object points for the system centered in a middle (by default) or in a top left corner of single + * marker, given the marker length + */ +static Mat _getSingleMarkerObjectPoints(float markerLength, bool use_aruco_ccw_center) { + CV_Assert(markerLength > 0); + Mat objPoints(4, 1, CV_32FC3); + // set coordinate system in the top-left corner of the marker, with Z pointing out + if (use_aruco_ccw_center) { + objPoints.ptr(0)[0] = Vec3f(-markerLength/2.f, markerLength/2.f, 0); + objPoints.ptr(0)[1] = Vec3f(markerLength/2.f, markerLength/2.f, 0); + objPoints.ptr(0)[2] = Vec3f(markerLength/2.f, -markerLength/2.f, 0); + objPoints.ptr(0)[3] = Vec3f(-markerLength/2.f, -markerLength/2.f, 0); + } + else { + objPoints.ptr(0)[0] = Vec3f(0.f, 0.f, 0); + objPoints.ptr(0)[1] = Vec3f(markerLength, 0.f, 0); + objPoints.ptr(0)[2] = Vec3f(markerLength, markerLength, 0); + objPoints.ptr(0)[3] = Vec3f(0.f, markerLength, 0); + } + return objPoints; +} + +void getMarkersPoses(InputArrayOfArrays corners, float markerLength, InputArray cameraMatrix, InputArray distCoeffs, + OutputArray _rvecs, OutputArray _tvecs, OutputArray objPoints, + bool use_aruco_ccw_center, SolvePnPMethod solvePnPMethod) { + CV_Assert(markerLength > 0); + Mat markerObjPoints = _getSingleMarkerObjectPoints(markerLength, use_aruco_ccw_center); + int nMarkers = (int)corners.total(); + _rvecs.create(nMarkers, 1, CV_64FC3); + _tvecs.create(nMarkers, 1, CV_64FC3); + + Mat rvecs = _rvecs.getMat(), tvecs = _tvecs.getMat(); + for (int i = 0; i < nMarkers; i++) + solvePnP(markerObjPoints, corners.getMat(i), cameraMatrix, distCoeffs, rvecs.at(i), tvecs.at(i), + false, solvePnPMethod); + + if(objPoints.needed()) + markerObjPoints.convertTo(objPoints, -1); +} + +} diff --git a/modules/objdetect/test/test_aruco_utils.hpp b/modules/objdetect/test/test_aruco_utils.hpp new file mode 100644 index 0000000000..3a1a8f3020 --- /dev/null +++ b/modules/objdetect/test/test_aruco_utils.hpp @@ -0,0 +1,42 @@ +// 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" +#include "opencv2/3d.hpp" + +namespace opencv_test { + +static inline double deg2rad(double deg) { return deg * CV_PI / 180.; } + +vector getAxis(InputArray _cameraMatrix, InputArray _distCoeffs, InputArray _rvec, InputArray _tvec, + float length, const float offset = 0.f); + +vector getMarkerById(int id, const vector >& corners, const vector& ids); + +/** + * @brief Get rvec and tvec from yaw, pitch and distance + */ +void getSyntheticRT(double yaw, double pitch, double distance, Mat& rvec, Mat& tvec); + +/** + * @brief Project a synthetic marker + */ +void projectMarker(Mat& img, const aruco::Board& board, int markerIndex, Mat cameraMatrix, Mat rvec, Mat tvec, + int markerBorder); + +/** + * @brief Get a synthetic image of GridBoard in perspective + */ +Mat projectBoard(const aruco::GridBoard& board, Mat cameraMatrix, double yaw, double pitch, double distance, + Size imageSize, int markerBorder); + +bool getCharucoBoardPose(InputArray charucoCorners, InputArray charucoIds, const aruco::CharucoBoard &board, + InputArray cameraMatrix, InputArray distCoeffs, InputOutputArray rvec, + InputOutputArray tvec, bool useExtrinsicGuess = false); + +void getMarkersPoses(InputArrayOfArrays corners, float markerLength, InputArray cameraMatrix, InputArray distCoeffs, + OutputArray _rvecs, OutputArray _tvecs, OutputArray objPoints = noArray(), + bool use_aruco_ccw_center = true, SolvePnPMethod solvePnPMethod = SolvePnPMethod::SOLVEPNP_ITERATIVE); + +} diff --git a/modules/objdetect/test/test_arucodetection.cpp b/modules/objdetect/test/test_arucodetection.cpp new file mode 100644 index 0000000000..e74990dd22 --- /dev/null +++ b/modules/objdetect/test/test_arucodetection.cpp @@ -0,0 +1,704 @@ +// 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" +#include "opencv2/objdetect/aruco_detector.hpp" +#include "opencv2/3d.hpp" + +namespace opencv_test { namespace { + +/** + * @brief Draw 2D synthetic markers and detect them + */ +class CV_ArucoDetectionSimple : public cvtest::BaseTest { + public: + CV_ArucoDetectionSimple(); + + protected: + void run(int); +}; + + +CV_ArucoDetectionSimple::CV_ArucoDetectionSimple() {} + + +void CV_ArucoDetectionSimple::run(int) { + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250)); + + // 20 images + for(int i = 0; i < 20; i++) { + + const int markerSidePixels = 100; + int imageSize = markerSidePixels * 2 + 3 * (markerSidePixels / 2); + + // draw synthetic image and store marker corners and ids + vector > groundTruthCorners; + vector groundTruthIds; + Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255)); + for(int y = 0; y < 2; y++) { + for(int x = 0; x < 2; x++) { + Mat marker; + int id = i * 4 + y * 2 + x; + aruco::generateImageMarker(detector.getDictionary(), id, markerSidePixels, marker); + Point2f firstCorner = + Point2f(markerSidePixels / 2.f + x * (1.5f * markerSidePixels), + markerSidePixels / 2.f + y * (1.5f * markerSidePixels)); + Mat aux = img.colRange((int)firstCorner.x, (int)firstCorner.x + markerSidePixels) + .rowRange((int)firstCorner.y, (int)firstCorner.y + markerSidePixels); + marker.copyTo(aux); + groundTruthIds.push_back(id); + groundTruthCorners.push_back(vector()); + groundTruthCorners.back().push_back(firstCorner); + groundTruthCorners.back().push_back(firstCorner + Point2f(markerSidePixels - 1, 0)); + groundTruthCorners.back().push_back( + firstCorner + Point2f(markerSidePixels - 1, markerSidePixels - 1)); + groundTruthCorners.back().push_back(firstCorner + Point2f(0, markerSidePixels - 1)); + } + } + if(i % 2 == 1) img.convertTo(img, CV_8UC3); + + // detect markers + vector > corners; + vector ids; + + detector.detectMarkers(img, corners, ids); + + // check detection results + for(unsigned int m = 0; m < groundTruthIds.size(); m++) { + int idx = -1; + for(unsigned int k = 0; k < ids.size(); k++) { + if(groundTruthIds[m] == ids[k]) { + idx = (int)k; + break; + } + } + if(idx == -1) { + ts->printf(cvtest::TS::LOG, "Marker not detected"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + + for(int c = 0; c < 4; c++) { + double dist = cv::norm(groundTruthCorners[m][c] - corners[idx][c]); // TODO cvtest + if(dist > 0.001) { + ts->printf(cvtest::TS::LOG, "Incorrect marker corners position"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + } + } + } +} + + +static double deg2rad(double deg) { return deg * CV_PI / 180.; } + +/** + * @brief Get rvec and tvec from yaw, pitch and distance + */ +static void getSyntheticRT(double yaw, double pitch, double distance, Mat &rvec, Mat &tvec) { + + rvec = Mat(3, 1, CV_64FC1); + tvec = Mat(3, 1, CV_64FC1); + + // Rvec + // first put the Z axis aiming to -X (like the camera axis system) + Mat rotZ(3, 1, CV_64FC1); + rotZ.ptr(0)[0] = 0; + rotZ.ptr(0)[1] = 0; + rotZ.ptr(0)[2] = -0.5 * CV_PI; + + Mat rotX(3, 1, CV_64FC1); + rotX.ptr(0)[0] = 0.5 * CV_PI; + rotX.ptr(0)[1] = 0; + rotX.ptr(0)[2] = 0; + + Mat camRvec, camTvec; + composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)), + camRvec, camTvec); + + // now pitch and yaw angles + Mat rotPitch(3, 1, CV_64FC1); + rotPitch.ptr(0)[0] = 0; + rotPitch.ptr(0)[1] = pitch; + rotPitch.ptr(0)[2] = 0; + + Mat rotYaw(3, 1, CV_64FC1); + rotYaw.ptr(0)[0] = yaw; + rotYaw.ptr(0)[1] = 0; + rotYaw.ptr(0)[2] = 0; + + composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw, + Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec); + + // compose both rotations + composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, + Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec); + + // Tvec, just move in z (camera) direction the specific distance + tvec.ptr(0)[0] = 0.; + tvec.ptr(0)[1] = 0.; + tvec.ptr(0)[2] = distance; +} + +/** + * @brief Create a synthetic image of a marker with perspective + */ +static Mat projectMarker(const aruco::Dictionary &dictionary, int id, Mat cameraMatrix, double yaw, + double pitch, double distance, Size imageSize, int markerBorder, + vector &corners, int encloseMarker=0) { + + // canonical image + Mat marker, markerImg; + const int markerSizePixels = 100; + + aruco::generateImageMarker(dictionary, id, markerSizePixels, marker, markerBorder); + marker.copyTo(markerImg); + + if(encloseMarker){ //to enclose the marker + int enclose = int(marker.rows/4); + markerImg = Mat::zeros(marker.rows+(2*enclose), marker.cols+(enclose*2), CV_8UC1); + + Mat field= markerImg.rowRange(int(enclose), int(markerImg.rows-enclose)) + .colRange(int(0), int(markerImg.cols)); + field.setTo(255); + field= markerImg.rowRange(int(0), int(markerImg.rows)) + .colRange(int(enclose), int(markerImg.cols-enclose)); + field.setTo(255); + + field = markerImg(Rect(enclose,enclose,marker.rows,marker.cols)); + marker.copyTo(field); + } + + // get rvec and tvec for the perspective + Mat rvec, tvec; + getSyntheticRT(yaw, pitch, distance, rvec, tvec); + + const float markerLength = 0.05f; + vector markerObjPoints; + markerObjPoints.push_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0)); + markerObjPoints.push_back(markerObjPoints[0] + Point3f(markerLength, 0, 0)); + markerObjPoints.push_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0)); + markerObjPoints.push_back(markerObjPoints[0] + Point3f(0, -markerLength, 0)); + + // project markers and draw them + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners); + + vector originalCorners; + originalCorners.push_back(Point2f(0+float(encloseMarker*markerSizePixels/4), 0+float(encloseMarker*markerSizePixels/4))); + originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, 0)); + originalCorners.push_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels)); + originalCorners.push_back(originalCorners[0]+Point2f(0, (float)markerSizePixels)); + + Mat transformation = getPerspectiveTransform(originalCorners, corners); + + Mat img(imageSize, CV_8UC1, Scalar::all(255)); + Mat aux; + const char borderValue = 127; + warpPerspective(markerImg, aux, transformation, imageSize, INTER_NEAREST, BORDER_CONSTANT, + Scalar::all(borderValue)); + + // copy only not-border pixels + for(int y = 0; y < aux.rows; y++) { + for(int x = 0; x < aux.cols; x++) { + if(aux.at(y, x) == borderValue) continue; + img.at(y, x) = aux.at(y, x); + } + } + + return img; +} + +enum class ArucoAlgParams +{ + USE_DEFAULT = 0, + USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag + DETECT_INVERTED_MARKER, /// Check if there is a white marker + USE_ARUCO3 /// Check if aruco3 should be used +}; + + +/** + * @brief Draws markers in perspective and detect them + */ +class CV_ArucoDetectionPerspective : public cvtest::BaseTest { + public: + CV_ArucoDetectionPerspective(ArucoAlgParams arucoAlgParam) : arucoAlgParams(arucoAlgParam) {} + + protected: + void run(int); + ArucoAlgParams arucoAlgParams; +}; + + +void CV_ArucoDetectionPerspective::run(int) { + + int iter = 0; + int szEnclosed = 0; + Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1); + Size imgSize(500, 500); + cameraMatrix.at(0, 0) = cameraMatrix.at(1, 1) = 650; + cameraMatrix.at(0, 2) = imgSize.width / 2; + cameraMatrix.at(1, 2) = imgSize.height / 2; + aruco::DetectorParameters params; + params.minDistanceToBorder = 1; + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params); + + // detect from different positions + for(double distance = 0.1; distance < 0.7; distance += 0.2) { + for(int pitch = 0; pitch < 360; pitch += (distance == 0.1? 60:180)) { + for(int yaw = 70; yaw <= 120; yaw += 40){ + int currentId = iter % 250; + int markerBorder = iter % 2 + 1; + iter++; + vector groundTruthCorners; + aruco::DetectorParameters detectorParameters = params; + detectorParameters.markerBorderBits = markerBorder; + + /// create synthetic image + Mat img= + projectMarker(detector.getDictionary(), currentId, cameraMatrix, deg2rad(yaw), deg2rad(pitch), + distance, imgSize, markerBorder, groundTruthCorners, szEnclosed); + // marker :: Inverted + if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams){ + img = ~img; + detectorParameters.detectInvertedMarker = true; + } + + if(ArucoAlgParams::USE_APRILTAG == arucoAlgParams){ + detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_APRILTAG; + } + + if (ArucoAlgParams::USE_ARUCO3 == arucoAlgParams) { + detectorParameters.useAruco3Detection = true; + detectorParameters.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + } + detector.setDetectorParameters(detectorParameters); + + // detect markers + vector > corners; + vector ids; + detector.detectMarkers(img, corners, ids); + + // check results + if(ids.size() != 1 || (ids.size() == 1 && ids[0] != currentId)) { + if(ids.size() != 1) + ts->printf(cvtest::TS::LOG, "Incorrect number of detected markers"); + else + ts->printf(cvtest::TS::LOG, "Incorrect marker id"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + for(int c = 0; c < 4; c++) { + double dist = cv::norm(groundTruthCorners[c] - corners[0][c]); // TODO cvtest + if(dist > 5) { + ts->printf(cvtest::TS::LOG, "Incorrect marker corners position"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + } + } + } + // change the state :: to detect an enclosed inverted marker + if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams && distance == 0.1){ + distance -= 0.1; + szEnclosed++; + } + } +} + + +/** + * @brief Check max and min size in marker detection parameters + */ +class CV_ArucoDetectionMarkerSize : public cvtest::BaseTest { + public: + CV_ArucoDetectionMarkerSize(); + + protected: + void run(int); +}; + + +CV_ArucoDetectionMarkerSize::CV_ArucoDetectionMarkerSize() {} + + +void CV_ArucoDetectionMarkerSize::run(int) { + aruco::DetectorParameters params; + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_6X6_250), params); + int markerSide = 20; + int imageSize = 200; + + // 10 cases + for(int i = 0; i < 10; i++) { + Mat marker; + int id = 10 + i * 20; + + // create synthetic image + Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255)); + aruco::generateImageMarker(detector.getDictionary(), id, markerSide, marker); + Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide); + marker.copyTo(aux); + + vector > corners; + vector ids; + + // set a invalid minMarkerPerimeterRate + aruco::DetectorParameters detectorParameters = params; + detectorParameters.minMarkerPerimeterRate = min(4., (4. * markerSide) / float(imageSize) + 0.1); + detector.setDetectorParameters(detectorParameters); + detector.detectMarkers(img, corners, ids); + if(corners.size() != 0) { + ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::minMarkerPerimeterRate"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + + // set an valid minMarkerPerimeterRate + detectorParameters = params; + detectorParameters.minMarkerPerimeterRate = max(0., (4. * markerSide) / float(imageSize) - 0.1); + detector.setDetectorParameters(detectorParameters); + detector.detectMarkers(img, corners, ids); + if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) { + ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::minMarkerPerimeterRate"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + + // set a invalid maxMarkerPerimeterRate + detectorParameters = params; + detectorParameters.maxMarkerPerimeterRate = min(4., (4. * markerSide) / float(imageSize) - 0.1); + detector.setDetectorParameters(detectorParameters); + detector.detectMarkers(img, corners, ids); + if(corners.size() != 0) { + ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::maxMarkerPerimeterRate"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + + // set an valid maxMarkerPerimeterRate + detectorParameters = params; + detectorParameters.maxMarkerPerimeterRate = max(0., (4. * markerSide) / float(imageSize) + 0.1); + detector.setDetectorParameters(detectorParameters); + detector.detectMarkers(img, corners, ids); + if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) { + ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::maxMarkerPerimeterRate"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + } +} + + +/** + * @brief Check error correction in marker bits + */ +class CV_ArucoBitCorrection : public cvtest::BaseTest { + public: + CV_ArucoBitCorrection(); + + protected: + void run(int); +}; + + +CV_ArucoBitCorrection::CV_ArucoBitCorrection() {} + + +void CV_ArucoBitCorrection::run(int) { + + aruco::Dictionary dictionary1 = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + aruco::Dictionary dictionary2 = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + aruco::DetectorParameters params; + aruco::ArucoDetector detector1(dictionary1, params); + int markerSide = 50; + int imageSize = 150; + + // 10 markers + for(int l = 0; l < 10; l++) { + Mat marker; + int id = 10 + l * 20; + + Mat currentCodeBytes = dictionary1.bytesList.rowRange(id, id + 1); + aruco::DetectorParameters detectorParameters = detector1.getDetectorParameters(); + // 5 valid cases + for(int i = 0; i < 5; i++) { + // how many bit errors (the error is low enough so it can be corrected) + detectorParameters.errorCorrectionRate = 0.2 + i * 0.1; + detector1.setDetectorParameters(detectorParameters); + int errors = + (int)std::floor(dictionary1.maxCorrectionBits * detector1.getDetectorParameters().errorCorrectionRate - 1.); + + // create erroneous marker in currentCodeBits + Mat currentCodeBits = + aruco::Dictionary::getBitsFromByteList(currentCodeBytes, dictionary1.markerSize); + for(int e = 0; e < errors; e++) { + currentCodeBits.ptr()[2 * e] = + !currentCodeBits.ptr()[2 * e]; + } + + // add erroneous marker to dictionary2 in order to create the erroneous marker image + Mat currentCodeBytesError = aruco::Dictionary::getByteListFromBits(currentCodeBits); + currentCodeBytesError.copyTo(dictionary2.bytesList.rowRange(id, id + 1)); + Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255)); + dictionary2.generateImageMarker(id, markerSide, marker); + Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide); + marker.copyTo(aux); + + // try to detect using original dictionary + vector > corners; + vector ids; + detector1.detectMarkers(img, corners, ids); + if(corners.size() != 1 || (corners.size() == 1 && ids[0] != id)) { + ts->printf(cvtest::TS::LOG, "Error in bit correction"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + } + + // 5 invalid cases + for(int i = 0; i < 5; i++) { + // how many bit errors (the error is too high to be corrected) + detectorParameters.errorCorrectionRate = 0.2 + i * 0.1; + detector1.setDetectorParameters(detectorParameters); + int errors = + (int)std::floor(dictionary1.maxCorrectionBits * detector1.getDetectorParameters().errorCorrectionRate + 1.); + + // create erroneous marker in currentCodeBits + Mat currentCodeBits = + aruco::Dictionary::getBitsFromByteList(currentCodeBytes, dictionary1.markerSize); + for(int e = 0; e < errors; e++) { + currentCodeBits.ptr()[2 * e] = + !currentCodeBits.ptr()[2 * e]; + } + + // dictionary3 is only composed by the modified marker (in its original form) + aruco::Dictionary _dictionary3 = aruco::Dictionary( + dictionary2.bytesList.rowRange(id, id + 1).clone(), + dictionary1.markerSize, + dictionary1.maxCorrectionBits); + aruco::ArucoDetector detector3(_dictionary3, detector1.getDetectorParameters()); + // add erroneous marker to dictionary2 in order to create the erroneous marker image + Mat currentCodeBytesError = aruco::Dictionary::getByteListFromBits(currentCodeBits); + currentCodeBytesError.copyTo(dictionary2.bytesList.rowRange(id, id + 1)); + Mat img = Mat(imageSize, imageSize, CV_8UC1, Scalar::all(255)); + dictionary2.generateImageMarker(id, markerSide, marker); + Mat aux = img.colRange(30, 30 + markerSide).rowRange(50, 50 + markerSide); + marker.copyTo(aux); + + // try to detect using dictionary3, it should fail + vector > corners; + vector ids; + detector3.detectMarkers(img, corners, ids); + if(corners.size() != 0) { + ts->printf(cvtest::TS::LOG, "Error in DetectorParameters::errorCorrectionRate"); + ts->set_failed_test_info(cvtest::TS::FAIL_BAD_ACCURACY); + return; + } + } + } +} + +typedef CV_ArucoDetectionPerspective CV_AprilTagDetectionPerspective; +typedef CV_ArucoDetectionPerspective CV_InvertedArucoDetectionPerspective; +typedef CV_ArucoDetectionPerspective CV_Aruco3DetectionPerspective; + +TEST(CV_InvertedArucoDetectionPerspective, algorithmic) { + CV_InvertedArucoDetectionPerspective test(ArucoAlgParams::DETECT_INVERTED_MARKER); + test.safe_run(); +} + +TEST(CV_AprilTagDetectionPerspective, algorithmic) { + CV_AprilTagDetectionPerspective test(ArucoAlgParams::USE_APRILTAG); + test.safe_run(); +} + +TEST(CV_Aruco3DetectionPerspective, algorithmic) { + CV_Aruco3DetectionPerspective test(ArucoAlgParams::USE_ARUCO3); + test.safe_run(); +} + +TEST(CV_ArucoDetectionSimple, algorithmic) { + CV_ArucoDetectionSimple test; + test.safe_run(); +} + +TEST(CV_ArucoDetectionPerspective, algorithmic) { + CV_ArucoDetectionPerspective test(ArucoAlgParams::USE_DEFAULT); + test.safe_run(); +} + +TEST(CV_ArucoDetectionMarkerSize, algorithmic) { + CV_ArucoDetectionMarkerSize test; + test.safe_run(); +} + +TEST(CV_ArucoBitCorrection, algorithmic) { + CV_ArucoBitCorrection test; + test.safe_run(); +} + +TEST(CV_ArucoDetectMarkers, regression_3192) +{ + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50)); + vector markerIds; + vector > markerCorners; + string imgPath = cvtest::findDataFile("aruco/regression_3192.png"); + Mat image = imread(imgPath); + const size_t N = 2ull; + const int goldCorners[N][8] = { {345,120, 520,120, 520,295, 345,295}, {101,114, 270,112, 276,287, 101,287} }; + const int goldCornersIds[N] = { 6, 4 }; + map mapGoldCorners; + for (size_t i = 0; i < N; i++) + mapGoldCorners[goldCornersIds[i]] = goldCorners[i]; + + detector.detectMarkers(image, markerCorners, markerIds); + + ASSERT_EQ(N, markerIds.size()); + for (size_t i = 0; i < N; i++) + { + int arucoId = markerIds[i]; + ASSERT_EQ(4ull, markerCorners[i].size()); + ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end()); + for (int j = 0; j < 4; j++) + { + EXPECT_NEAR(static_cast(mapGoldCorners[arucoId][j * 2]), markerCorners[i][j].x, 1.f); + EXPECT_NEAR(static_cast(mapGoldCorners[arucoId][j * 2 + 1]), markerCorners[i][j].y, 1.f); + } + } +} + +TEST(CV_ArucoDetectMarkers, regression_2492) +{ + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_5X5_50)); + aruco::DetectorParameters detectorParameters = detector.getDetectorParameters(); + detectorParameters.minMarkerDistanceRate = 0.026; + detector.setDetectorParameters(detectorParameters); + vector markerIds; + vector > markerCorners; + string imgPath = cvtest::findDataFile("aruco/regression_2492.png"); + Mat image = imread(imgPath); + const size_t N = 8ull; + const int goldCorners[N][8] = { {179,139, 179,95, 223,95, 223,139}, {99,139, 99,95, 143,95, 143,139}, + {19,139, 19,95, 63,95, 63,139}, {256,140, 256,93, 303,93, 303,140}, + {256,62, 259,21, 300,23, 297,64}, {99,21, 143,17, 147,60, 103,64}, + {69,61, 28,61, 14,21, 58,17}, {174,62, 182,13, 230,19, 223,68} }; + const int goldCornersIds[N] = {13, 13, 13, 13, 1, 15, 14, 4}; + map > mapGoldCorners; + for (size_t i = 0; i < N; i++) + mapGoldCorners[goldCornersIds[i]].push_back(goldCorners[i]); + + detector.detectMarkers(image, markerCorners, markerIds); + + ASSERT_EQ(N, markerIds.size()); + for (size_t i = 0; i < N; i++) + { + int arucoId = markerIds[i]; + ASSERT_EQ(4ull, markerCorners[i].size()); + ASSERT_TRUE(mapGoldCorners.find(arucoId) != mapGoldCorners.end()); + float totalDist = 8.f; + for (size_t k = 0ull; k < mapGoldCorners[arucoId].size(); k++) + { + float dist = 0.f; + for (int j = 0; j < 4; j++) // total distance up to 4 points + { + dist += abs(mapGoldCorners[arucoId][k][j * 2] - markerCorners[i][j].x); + dist += abs(mapGoldCorners[arucoId][k][j * 2 + 1] - markerCorners[i][j].y); + } + totalDist = min(totalDist, dist); + } + EXPECT_LT(totalDist, 8.f); + } +} + +struct ArucoThreading: public testing::TestWithParam +{ + struct NumThreadsSetter { + NumThreadsSetter(const int num_threads) + : original_num_threads_(getNumThreads()) { + setNumThreads(num_threads); + } + + ~NumThreadsSetter() { + setNumThreads(original_num_threads_); + } + private: + int original_num_threads_; + }; +}; + +TEST_P(ArucoThreading, number_of_threads_does_not_change_results) +{ + // We are not testing against different dictionaries + // As we are interested mostly in small images, smaller + // markers is better -> 4x4 + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_4X4_50)); + + // Height of the test image can be chosen quite freely + // We aim to test against small images as in those the + // number of threads has most effect + const int height_img = 20; + // Just to get nice white boarder + const int shift = height_img > 10 ? 5 : 1; + const int height_marker = height_img-2*shift; + + // Create a test image + Mat img_marker; + aruco::generateImageMarker(detector.getDictionary(), 23, height_marker, img_marker, 1); + + // Copy to bigger image to get a white border + Mat img(height_img, height_img, CV_8UC1, Scalar(255)); + img_marker.copyTo(img(Rect(shift, shift, height_marker, height_marker))); + + aruco::DetectorParameters detectorParameters = detector.getDetectorParameters(); + detectorParameters.cornerRefinementMethod = GetParam(); + detector.setDetectorParameters(detectorParameters); + + vector > original_corners; + vector original_ids; + { + NumThreadsSetter thread_num_setter(1); + detector.detectMarkers(img, original_corners, original_ids); + } + + ASSERT_EQ(original_ids.size(), 1ull); + ASSERT_EQ(original_corners.size(), 1ull); + + int num_threads_to_test[] = { 2, 8, 16, 32, height_img-1, height_img, height_img+1}; + + for (size_t i_num_threads = 0; i_num_threads < sizeof(num_threads_to_test)/sizeof(int); ++i_num_threads) { + NumThreadsSetter thread_num_setter(num_threads_to_test[i_num_threads]); + + vector > corners; + vector ids; + detector.detectMarkers(img, corners, ids); + + // If we don't find any markers, the test is broken + ASSERT_EQ(ids.size(), 1ull); + + // Make sure we got the same result as the first time + ASSERT_EQ(corners.size(), original_corners.size()); + ASSERT_EQ(ids.size(), original_ids.size()); + ASSERT_EQ(ids.size(), corners.size()); + for (size_t i = 0; i < corners.size(); ++i) { + EXPECT_EQ(ids[i], original_ids[i]); + for (size_t j = 0; j < corners[i].size(); ++j) { + EXPECT_NEAR(corners[i][j].x, original_corners[i][j].x, 0.1f); + EXPECT_NEAR(corners[i][j].y, original_corners[i][j].y, 0.1f); + } + } + } +} + +INSTANTIATE_TEST_CASE_P( + CV_ArucoDetectMarkers, ArucoThreading, + ::testing::Values( + aruco::CORNER_REFINE_NONE, + aruco::CORNER_REFINE_SUBPIX, + aruco::CORNER_REFINE_CONTOUR, + aruco::CORNER_REFINE_APRILTAG + )); + +}} // namespace diff --git a/modules/objdetect/test/test_boarddetection.cpp b/modules/objdetect/test/test_boarddetection.cpp new file mode 100644 index 0000000000..d3859920fc --- /dev/null +++ b/modules/objdetect/test/test_boarddetection.cpp @@ -0,0 +1,321 @@ +// 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" +#include "test_aruco_utils.hpp" + +namespace opencv_test { namespace { + +enum class ArucoAlgParams +{ + USE_DEFAULT = 0, + USE_ARUCO3 = 1 +}; + +/** + * @brief Check pose estimation of aruco board + */ +class CV_ArucoBoardPose : public cvtest::BaseTest { + public: + CV_ArucoBoardPose(ArucoAlgParams arucoAlgParams) + { + aruco::DetectorParameters params; + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + params.minDistanceToBorder = 3; + if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) { + params.useAruco3Detection = true; + params.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + params.minSideLengthCanonicalImg = 16; + params.errorCorrectionRate = 0.8; + } + detector = aruco::ArucoDetector(dictionary, params); + } + + protected: + aruco::ArucoDetector detector; + void run(int); +}; + + +void CV_ArucoBoardPose::run(int) { + int iter = 0; + Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1); + Size imgSize(500, 500); + cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650; + cameraMatrix.at< double >(0, 2) = imgSize.width / 2; + cameraMatrix.at< double >(1, 2) = imgSize.height / 2; + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + const int sizeX = 3, sizeY = 3; + aruco::DetectorParameters detectorParameters = detector.getDetectorParameters(); + + // for different perspectives + for(double distance = 0.2; distance <= 0.4; distance += 0.15) { + for(int yaw = -55; yaw <= 50; yaw += 25) { + for(int pitch = -55; pitch <= 50; pitch += 25) { + vector tmpIds; + for(int i = 0; i < sizeX*sizeY; i++) + tmpIds.push_back((iter + int(i)) % 250); + aruco::GridBoard gridboard(Size(sizeX, sizeY), 0.02f, 0.005f, detector.getDictionary(), tmpIds); + int markerBorder = iter % 2 + 1; + iter++; + // create synthetic image + Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance, + imgSize, markerBorder); + vector > corners; + vector ids; + detectorParameters.markerBorderBits = markerBorder; + detector.setDetectorParameters(detectorParameters); + detector.detectMarkers(img, corners, ids); + + ASSERT_EQ(ids.size(), gridboard.getIds().size()); + + // estimate pose + Mat rvec, tvec; + { + Mat objPoints, imgPoints; // get object and image points for the solvePnP function + gridboard.matchImagePoints(corners, ids, objPoints, imgPoints); + solvePnP(objPoints, imgPoints, cameraMatrix, distCoeffs, rvec, tvec); + } + + // check axes + vector axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, gridboard.getRightBottomCorner().x); + vector topLeft = getMarkerById(gridboard.getIds()[0], corners, ids); + ASSERT_NEAR(topLeft[0].x, axes[0].x, 2.f); + ASSERT_NEAR(topLeft[0].y, axes[0].y, 2.f); + vector topRight = getMarkerById(gridboard.getIds()[2], corners, ids); + ASSERT_NEAR(topRight[1].x, axes[1].x, 2.f); + ASSERT_NEAR(topRight[1].y, axes[1].y, 2.f); + vector bottomLeft = getMarkerById(gridboard.getIds()[6], corners, ids); + ASSERT_NEAR(bottomLeft[3].x, axes[2].x, 2.f); + ASSERT_NEAR(bottomLeft[3].y, axes[2].y, 2.f); + + // check estimate result + for(unsigned int i = 0; i < ids.size(); i++) { + int foundIdx = -1; + for(unsigned int j = 0; j < gridboard.getIds().size(); j++) { + if(gridboard.getIds()[j] == ids[i]) { + foundIdx = int(j); + break; + } + } + + if(foundIdx == -1) { + ts->printf(cvtest::TS::LOG, "Marker detected with wrong ID in Board test"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + + vector< Point2f > projectedCorners; + projectPoints(gridboard.getObjPoints()[foundIdx], rvec, tvec, cameraMatrix, distCoeffs, + projectedCorners); + + for(int c = 0; c < 4; c++) { + double repError = cv::norm(projectedCorners[c] - corners[i][c]); // TODO cvtest + if(repError > 5.) { + ts->printf(cvtest::TS::LOG, "Corner reprojection error too high"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } + } + } + } + } +} + + + +/** + * @brief Check refine strategy + */ +class CV_ArucoRefine : public cvtest::BaseTest { + public: + CV_ArucoRefine(ArucoAlgParams arucoAlgParams) + { + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + aruco::DetectorParameters params; + params.minDistanceToBorder = 3; + params.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) + params.useAruco3Detection = true; + aruco::RefineParameters refineParams(10.f, 3.f, true); + detector = aruco::ArucoDetector(dictionary, params, refineParams); + } + + protected: + aruco::ArucoDetector detector; + void run(int); +}; + + +void CV_ArucoRefine::run(int) { + + int iter = 0; + Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1); + Size imgSize(500, 500); + cameraMatrix.at< double >(0, 0) = cameraMatrix.at< double >(1, 1) = 650; + cameraMatrix.at< double >(0, 2) = imgSize.width / 2; + cameraMatrix.at< double >(1, 2) = imgSize.height / 2; + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + aruco::DetectorParameters detectorParameters = detector.getDetectorParameters(); + + // for different perspectives + for(double distance = 0.2; distance <= 0.4; distance += 0.2) { + for(int yaw = -60; yaw < 60; yaw += 30) { + for(int pitch = -60; pitch <= 60; pitch += 30) { + aruco::GridBoard gridboard(Size(3, 3), 0.02f, 0.005f, detector.getDictionary()); + int markerBorder = iter % 2 + 1; + iter++; + + // create synthetic image + Mat img = projectBoard(gridboard, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance, + imgSize, markerBorder); + // detect markers + vector > corners, rejected; + vector ids; + detectorParameters.markerBorderBits = markerBorder; + detector.setDetectorParameters(detectorParameters); + detector.detectMarkers(img, corners, ids, rejected); + + // remove a marker from detection + int markersBeforeDelete = (int)ids.size(); + if(markersBeforeDelete < 2) continue; + + rejected.push_back(corners[0]); + corners.erase(corners.begin(), corners.begin() + 1); + ids.erase(ids.begin(), ids.begin() + 1); + + // try to refind the erased marker + detector.refineDetectedMarkers(img, gridboard, corners, ids, rejected, cameraMatrix, + distCoeffs, noArray()); + + // check result + if((int)ids.size() < markersBeforeDelete) { + ts->printf(cvtest::TS::LOG, "Error in refine detected markers"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } + } + } +} + +TEST(CV_ArucoBoardPose, accuracy) { + CV_ArucoBoardPose test(ArucoAlgParams::USE_DEFAULT); + test.safe_run(); +} + +typedef CV_ArucoBoardPose CV_Aruco3BoardPose; +TEST(CV_Aruco3BoardPose, accuracy) { + CV_Aruco3BoardPose test(ArucoAlgParams::USE_ARUCO3); + test.safe_run(); +} + +typedef CV_ArucoRefine CV_Aruco3Refine; + +TEST(CV_ArucoRefine, accuracy) { + CV_ArucoRefine test(ArucoAlgParams::USE_DEFAULT); + test.safe_run(); +} + +TEST(CV_Aruco3Refine, accuracy) { + CV_Aruco3Refine test(ArucoAlgParams::USE_ARUCO3); + test.safe_run(); +} + +TEST(CV_ArucoBoardPose, CheckNegativeZ) +{ + double matrixData[9] = { -3.9062571886921410e+02, 0., 4.2350000000000000e+02, + 0., 3.9062571886921410e+02, 2.3950000000000000e+02, + 0., 0., 1 }; + cv::Mat cameraMatrix = cv::Mat(3, 3, CV_64F, matrixData); + + vector pts3d1, pts3d2; + pts3d1.push_back(cv::Point3f(0.326198f, -0.030621f, 0.303620f)); + pts3d1.push_back(cv::Point3f(0.325340f, -0.100594f, 0.301862f)); + pts3d1.push_back(cv::Point3f(0.255859f, -0.099530f, 0.293416f)); + pts3d1.push_back(cv::Point3f(0.256717f, -0.029557f, 0.295174f)); + + pts3d2.push_back(cv::Point3f(-0.033144f, -0.034819f, 0.245216f)); + pts3d2.push_back(cv::Point3f(-0.035507f, -0.104705f, 0.241987f)); + pts3d2.push_back(cv::Point3f(-0.105289f, -0.102120f, 0.237120f)); + pts3d2.push_back(cv::Point3f(-0.102926f, -0.032235f, 0.240349f)); + + vector tmpIds = {0, 1}; + vector > tmpObjectPoints = {pts3d1, pts3d2}; + aruco::Board board(tmpObjectPoints, aruco::getPredefinedDictionary(0), tmpIds); + + vector > corners; + vector pts2d; + pts2d.push_back(cv::Point2f(37.7f, 203.3f)); + pts2d.push_back(cv::Point2f(38.5f, 120.5f)); + pts2d.push_back(cv::Point2f(105.5f, 115.8f)); + pts2d.push_back(cv::Point2f(104.2f, 202.7f)); + corners.push_back(pts2d); + pts2d.clear(); + pts2d.push_back(cv::Point2f(476.0f, 184.2f)); + pts2d.push_back(cv::Point2f(479.6f, 73.8f)); + pts2d.push_back(cv::Point2f(590.9f, 77.0f)); + pts2d.push_back(cv::Point2f(587.5f, 188.1f)); + corners.push_back(pts2d); + + Vec3d rvec, tvec; + int nUsed = 0; + { + Mat objPoints, imgPoints; // get object and image points for the solvePnP function + board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints); + nUsed = (int)objPoints.total()/4; + solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec); + } + ASSERT_EQ(nUsed, 2); + + cv::Matx33d rotm; cv::Point3d out; + cv::Rodrigues(rvec, rotm); + out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]); + ASSERT_GT(out.z, 0); + + corners.clear(); pts2d.clear(); + pts2d.push_back(cv::Point2f(38.4f, 204.5f)); + pts2d.push_back(cv::Point2f(40.0f, 124.7f)); + pts2d.push_back(cv::Point2f(102.0f, 119.1f)); + pts2d.push_back(cv::Point2f(99.9f, 203.6f)); + corners.push_back(pts2d); + pts2d.clear(); + pts2d.push_back(cv::Point2f(476.0f, 184.3f)); + pts2d.push_back(cv::Point2f(479.2f, 75.1f)); + pts2d.push_back(cv::Point2f(588.7f, 79.2f)); + pts2d.push_back(cv::Point2f(586.3f, 188.5f)); + corners.push_back(pts2d); + + nUsed = 0; + { + Mat objPoints, imgPoints; // get object and image points for the solvePnP function + board.matchImagePoints(corners, board.getIds(), objPoints, imgPoints); + nUsed = (int)objPoints.total()/4; + solvePnP(objPoints, imgPoints, cameraMatrix, Mat(), rvec, tvec, true); + } + ASSERT_EQ(nUsed, 2); + + cv::Rodrigues(rvec, rotm); + out = cv::Point3d(tvec) + rotm*Point3d(board.getObjPoints()[0][0]); + ASSERT_GT(out.z, 0); +} + +TEST(CV_ArucoGenerateBoard, regression_1226) { + int bwidth = 1600; + int bheight = 1200; + + cv::aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_50); + cv::aruco::CharucoBoard board(Size(7, 5), 1.0, 0.75, dict); + cv::Size sz(bwidth, bheight); + cv::Mat mat; + + ASSERT_NO_THROW( + { + board.generateImage(sz, mat, 0, 1); + }); +} + +}} // namespace diff --git a/modules/objdetect/test/test_charucodetection.cpp b/modules/objdetect/test/test_charucodetection.cpp new file mode 100644 index 0000000000..ef044c893b --- /dev/null +++ b/modules/objdetect/test/test_charucodetection.cpp @@ -0,0 +1,659 @@ +// 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" +#include "test_aruco_utils.hpp" + +namespace opencv_test { namespace { + +/** + * @brief Get a synthetic image of Chessboard in perspective + */ +static Mat projectChessboard(int squaresX, int squaresY, float squareSize, Size imageSize, + Mat cameraMatrix, Mat rvec, Mat tvec) { + + Mat img(imageSize, CV_8UC1, Scalar::all(255)); + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + + for(int y = 0; y < squaresY; y++) { + float startY = float(y) * squareSize; + for(int x = 0; x < squaresX; x++) { + if(y % 2 != x % 2) continue; + float startX = float(x) * squareSize; + + vector< Point3f > squareCorners; + squareCorners.push_back(Point3f(startX, startY, 0) - Point3f(squaresX*squareSize/2.f, squaresY*squareSize/2.f, 0.f)); + squareCorners.push_back(squareCorners[0] + Point3f(squareSize, 0, 0)); + squareCorners.push_back(squareCorners[0] + Point3f(squareSize, squareSize, 0)); + squareCorners.push_back(squareCorners[0] + Point3f(0, squareSize, 0)); + + vector< vector< Point2f > > projectedCorners; + projectedCorners.push_back(vector< Point2f >()); + projectPoints(squareCorners, rvec, tvec, cameraMatrix, distCoeffs, projectedCorners[0]); + + vector< vector< Point > > projectedCornersInt; + projectedCornersInt.push_back(vector< Point >()); + + for(int k = 0; k < 4; k++) + projectedCornersInt[0] + .push_back(Point((int)projectedCorners[0][k].x, (int)projectedCorners[0][k].y)); + + fillPoly(img, projectedCornersInt, Scalar::all(0)); + } + } + + return img; +} + + +/** + * @brief Check pose estimation of charuco board + */ +static Mat projectCharucoBoard(aruco::CharucoBoard& board, Mat cameraMatrix, double yaw, + double pitch, double distance, Size imageSize, int markerBorder, + Mat &rvec, Mat &tvec) { + + getSyntheticRT(yaw, pitch, distance, rvec, tvec); + + // project markers + Mat img = Mat(imageSize, CV_8UC1, Scalar::all(255)); + for(unsigned int indexMarker = 0; indexMarker < board.getIds().size(); indexMarker++) { + projectMarker(img, board, indexMarker, cameraMatrix, rvec, tvec, markerBorder); + } + + // project chessboard + Mat chessboard = + projectChessboard(board.getChessboardSize().width, board.getChessboardSize().height, + board.getSquareLength(), imageSize, cameraMatrix, rvec, tvec); + + for(unsigned int i = 0; i < chessboard.total(); i++) { + if(chessboard.ptr< unsigned char >()[i] == 0) { + img.ptr< unsigned char >()[i] = 0; + } + } + + return img; +} + +/** + * @brief Check Charuco detection + */ +class CV_CharucoDetection : public cvtest::BaseTest { + public: + CV_CharucoDetection(); + + protected: + void run(int); +}; + + +CV_CharucoDetection::CV_CharucoDetection() {} + + +void CV_CharucoDetection::run(int) { + + int iter = 0; + Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1); + Size imgSize(500, 500); + aruco::DetectorParameters params; + params.minDistanceToBorder = 3; + aruco::CharucoBoard board(Size(4, 4), 0.03f, 0.015f, aruco::getPredefinedDictionary(aruco::DICT_6X6_250)); + aruco::CharucoDetector detector(board, aruco::CharucoParameters(), params); + + cameraMatrix.at(0, 0) = cameraMatrix.at(1, 1) = 600; + cameraMatrix.at(0, 2) = imgSize.width / 2; + cameraMatrix.at(1, 2) = imgSize.height / 2; + + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + + // for different perspectives + for(double distance = 0.2; distance <= 0.4; distance += 0.2) { + for(int yaw = -55; yaw <= 50; yaw += 25) { + for(int pitch = -55; pitch <= 50; pitch += 25) { + + int markerBorder = iter % 2 + 1; + iter++; + + // create synthetic image + Mat rvec, tvec; + Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch), + distance, imgSize, markerBorder, rvec, tvec); + + // detect markers and interpolate charuco corners + vector > corners; + vector charucoCorners; + vector ids, charucoIds; + + params.markerBorderBits = markerBorder; + detector.setDetectorParameters(params); + + //detector.detectMarkers(img, corners, ids); + if(iter % 2 == 0) { + detector.detectBoard(img, charucoCorners, charucoIds, corners, ids); + } else { + aruco::CharucoParameters charucoParameters; + charucoParameters.cameraMatrix = cameraMatrix; + charucoParameters.distCoeffs = distCoeffs; + detector.setCharucoParameters(charucoParameters); + detector.detectBoard(img, charucoCorners, charucoIds, corners, ids); + } + + if(ids.size() == 0) { + ts->printf(cvtest::TS::LOG, "Marker detection failed"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + + // check results + vector< Point2f > projectedCharucoCorners; + + // copy chessboardCorners + vector copyChessboardCorners = board.getChessboardCorners(); + // move copyChessboardCorners points + for (size_t i = 0; i < copyChessboardCorners.size(); i++) + copyChessboardCorners[i] -= board.getRightBottomCorner() / 2.f; + projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs, + projectedCharucoCorners); + + for(unsigned int i = 0; i < charucoIds.size(); i++) { + + int currentId = charucoIds[i]; + + if(currentId >= (int)board.getChessboardCorners().size()) { + ts->printf(cvtest::TS::LOG, "Invalid Charuco corner id"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + + double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest + + + if(repError > 5.) { + ts->printf(cvtest::TS::LOG, "Charuco corner reprojection error too high"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } + } + } + } +} + + + +/** + * @brief Check charuco pose estimation + */ +class CV_CharucoPoseEstimation : public cvtest::BaseTest { + public: + CV_CharucoPoseEstimation(); + + protected: + void run(int); +}; + + +CV_CharucoPoseEstimation::CV_CharucoPoseEstimation() {} + + +void CV_CharucoPoseEstimation::run(int) { + int iter = 0; + Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1); + Size imgSize(500, 500); + aruco::DetectorParameters params; + params.minDistanceToBorder = 3; + aruco::CharucoBoard board(Size(4, 4), 0.03f, 0.015f, aruco::getPredefinedDictionary(aruco::DICT_6X6_250)); + aruco::CharucoDetector detector(board, aruco::CharucoParameters(), params); + + cameraMatrix.at(0, 0) = cameraMatrix.at< double >(1, 1) = 650; + cameraMatrix.at(0, 2) = imgSize.width / 2; + cameraMatrix.at(1, 2) = imgSize.height / 2; + + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + // for different perspectives + for(double distance = 0.2; distance <= 0.3; distance += 0.1) { + for(int yaw = -55; yaw <= 50; yaw += 25) { + for(int pitch = -55; pitch <= 50; pitch += 25) { + + int markerBorder = iter % 2 + 1; + iter++; + + // get synthetic image + Mat rvec, tvec; + Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch), + distance, imgSize, markerBorder, rvec, tvec); + + // detect markers + vector > corners; + vector ids; + params.markerBorderBits = markerBorder; + detector.setDetectorParameters(params); + + // detect markers and interpolate charuco corners + vector charucoCorners; + vector charucoIds; + + if(iter % 2 == 0) { + detector.detectBoard(img, charucoCorners, charucoIds, corners, ids); + } else { + aruco::CharucoParameters charucoParameters; + charucoParameters.cameraMatrix = cameraMatrix; + charucoParameters.distCoeffs = distCoeffs; + detector.setCharucoParameters(charucoParameters); + detector.detectBoard(img, charucoCorners, charucoIds, corners, ids); + } + ASSERT_EQ(ids.size(), board.getIds().size()); + if(charucoIds.size() == 0) continue; + + // estimate charuco pose + getCharucoBoardPose(charucoCorners, charucoIds, board, cameraMatrix, distCoeffs, rvec, tvec); + + + // check axes + const float offset = (board.getSquareLength() - board.getMarkerLength()) / 2.f; + vector axes = getAxis(cameraMatrix, distCoeffs, rvec, tvec, board.getSquareLength(), offset); + vector topLeft = getMarkerById(board.getIds()[0], corners, ids); + ASSERT_NEAR(topLeft[0].x, axes[1].x, 3.f); + ASSERT_NEAR(topLeft[0].y, axes[1].y, 3.f); + vector bottomLeft = getMarkerById(board.getIds()[2], corners, ids); + ASSERT_NEAR(bottomLeft[0].x, axes[2].x, 3.f); + ASSERT_NEAR(bottomLeft[0].y, axes[2].y, 3.f); + + // check estimate result + vector< Point2f > projectedCharucoCorners; + + projectPoints(board.getChessboardCorners(), rvec, tvec, cameraMatrix, distCoeffs, + projectedCharucoCorners); + + for(unsigned int i = 0; i < charucoIds.size(); i++) { + + int currentId = charucoIds[i]; + + if(currentId >= (int)board.getChessboardCorners().size()) { + ts->printf(cvtest::TS::LOG, "Invalid Charuco corner id"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + + double repError = cv::norm(charucoCorners[i] - projectedCharucoCorners[currentId]); // TODO cvtest + + + if(repError > 5.) { + ts->printf(cvtest::TS::LOG, "Charuco corner reprojection error too high"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } + } + } + } +} + + +/** + * @brief Check diamond detection + */ +class CV_CharucoDiamondDetection : public cvtest::BaseTest { + public: + CV_CharucoDiamondDetection(); + + protected: + void run(int); +}; + + +CV_CharucoDiamondDetection::CV_CharucoDiamondDetection() {} + + +void CV_CharucoDiamondDetection::run(int) { + + int iter = 0; + Mat cameraMatrix = Mat::eye(3, 3, CV_64FC1); + Size imgSize(500, 500); + aruco::DetectorParameters params; + params.minDistanceToBorder = 0; + float squareLength = 0.03f; + float markerLength = 0.015f; + aruco::CharucoBoard board(Size(3, 3), squareLength, markerLength, + aruco::getPredefinedDictionary(aruco::DICT_6X6_250)); + aruco::CharucoDetector detector(board); + + + cameraMatrix.at(0, 0) = cameraMatrix.at< double >(1, 1) = 650; + cameraMatrix.at(0, 2) = imgSize.width / 2; + cameraMatrix.at(1, 2) = imgSize.height / 2; + + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + aruco::CharucoParameters charucoParameters; + charucoParameters.cameraMatrix = cameraMatrix; + charucoParameters.distCoeffs = distCoeffs; + detector.setCharucoParameters(charucoParameters); + + // for different perspectives + for(double distance = 0.2; distance <= 0.3; distance += 0.1) { + for(int yaw = -50; yaw <= 50; yaw += 25) { + for(int pitch = -50; pitch <= 50; pitch += 25) { + + int markerBorder = iter % 2 + 1; + vector idsTmp; + for(int i = 0; i < 4; i++) + idsTmp.push_back(4 * iter + i); + board = aruco::CharucoBoard(Size(3, 3), squareLength, markerLength, + aruco::getPredefinedDictionary(aruco::DICT_6X6_250), idsTmp); + detector.setBoard(board); + iter++; + + // get synthetic image + Mat rvec, tvec; + Mat img = projectCharucoBoard(board, cameraMatrix, deg2rad(yaw), deg2rad(pitch), + distance, imgSize, markerBorder, rvec, tvec); + + // detect markers + vector> corners; + vector ids; + params.markerBorderBits = markerBorder; + detector.setDetectorParameters(params); + //detector.detectMarkers(img, corners, ids); + + + // detect diamonds + vector> diamondCorners; + vector diamondIds; + + detector.detectDiamonds(img, diamondCorners, diamondIds, corners, ids); + + // check detect + if(ids.size() != 4) { + ts->printf(cvtest::TS::LOG, "Not enough markers for diamond detection"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + + // check results + if(diamondIds.size() != 1) { + ts->printf(cvtest::TS::LOG, "Diamond not detected correctly"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + + for(int i = 0; i < 4; i++) { + if(diamondIds[0][i] != board.getIds()[i]) { + ts->printf(cvtest::TS::LOG, "Incorrect diamond ids"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } + + + vector< Point2f > projectedDiamondCorners; + + // copy chessboardCorners + vector copyChessboardCorners = board.getChessboardCorners(); + // move copyChessboardCorners points + for (size_t i = 0; i < copyChessboardCorners.size(); i++) + copyChessboardCorners[i] -= board.getRightBottomCorner() / 2.f; + + projectPoints(copyChessboardCorners, rvec, tvec, cameraMatrix, distCoeffs, + projectedDiamondCorners); + + vector< Point2f > projectedDiamondCornersReorder(4); + projectedDiamondCornersReorder[0] = projectedDiamondCorners[0]; + projectedDiamondCornersReorder[1] = projectedDiamondCorners[1]; + projectedDiamondCornersReorder[2] = projectedDiamondCorners[3]; + projectedDiamondCornersReorder[3] = projectedDiamondCorners[2]; + + + for(unsigned int i = 0; i < 4; i++) { + + double repError = cv::norm(diamondCorners[0][i] - projectedDiamondCornersReorder[i]); // TODO cvtest + + if(repError > 5.) { + ts->printf(cvtest::TS::LOG, "Diamond corner reprojection error too high"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } + + // estimate diamond pose + vector< Vec3d > estimatedRvec, estimatedTvec; + getMarkersPoses(diamondCorners, squareLength, cameraMatrix, distCoeffs, estimatedRvec, + estimatedTvec, noArray(), false); + + // check result + vector< Point2f > projectedDiamondCornersPose; + vector< Vec3f > diamondObjPoints(4); + diamondObjPoints[0] = Vec3f(0.f, 0.f, 0); + diamondObjPoints[1] = Vec3f(squareLength, 0.f, 0); + diamondObjPoints[2] = Vec3f(squareLength, squareLength, 0); + diamondObjPoints[3] = Vec3f(0.f, squareLength, 0); + projectPoints(diamondObjPoints, estimatedRvec[0], estimatedTvec[0], cameraMatrix, + distCoeffs, projectedDiamondCornersPose); + + for(unsigned int i = 0; i < 4; i++) { + double repError = cv::norm(projectedDiamondCornersReorder[i] - projectedDiamondCornersPose[i]); // TODO cvtest + + if(repError > 5.) { + ts->printf(cvtest::TS::LOG, "Charuco pose error too high"); + ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); + return; + } + } + } + } + } +} + +/** +* @brief Check charuco board creation +*/ +class CV_CharucoBoardCreation : public cvtest::BaseTest { +public: + CV_CharucoBoardCreation(); + +protected: + void run(int); +}; + +CV_CharucoBoardCreation::CV_CharucoBoardCreation() {} + +void CV_CharucoBoardCreation::run(int) +{ + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::DICT_5X5_250); + int n = 6; + + float markerSizeFactor = 0.5f; + + for (float squareSize_mm = 5.0f; squareSize_mm < 35.0f; squareSize_mm += 0.1f) + { + aruco::CharucoBoard board_meters(Size(n, n), squareSize_mm*1e-3f, + squareSize_mm * markerSizeFactor * 1e-3f, dictionary); + + aruco::CharucoBoard board_millimeters(Size(n, n), squareSize_mm, + squareSize_mm * markerSizeFactor, dictionary); + + for (size_t i = 0; i < board_meters.getNearestMarkerIdx().size(); i++) + { + if (board_meters.getNearestMarkerIdx()[i].size() != board_millimeters.getNearestMarkerIdx()[i].size() || + board_meters.getNearestMarkerIdx()[i][0] != board_millimeters.getNearestMarkerIdx()[i][0]) + { + ts->printf(cvtest::TS::LOG, + cv::format("Charuco board topology is sensitive to scale with squareSize=%.1f\n", + squareSize_mm).c_str()); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + break; + } + } + } +} + + +TEST(CV_CharucoDetection, accuracy) { + CV_CharucoDetection test; + test.safe_run(); +} + +TEST(CV_CharucoPoseEstimation, accuracy) { + CV_CharucoPoseEstimation test; + test.safe_run(); +} + +TEST(CV_CharucoDiamondDetection, accuracy) { + CV_CharucoDiamondDetection test; + test.safe_run(); +} + +TEST(CV_CharucoBoardCreation, accuracy) { + CV_CharucoBoardCreation test; + test.safe_run(); +} + +TEST(Charuco, testCharucoCornersCollinear_true) +{ + int squaresX = 13; + int squaresY = 28; + float squareLength = 300; + float markerLength = 150; + int dictionaryId = 11; + + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::PredefinedDictionaryType(dictionaryId)); + + aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary); + + // consistency with C++98 + const int arrLine[9] = {192, 204, 216, 228, 240, 252, 264, 276, 288}; + vector charucoIdsAxisLine(9, 0); + + for (int i = 0; i < 9; i++){ + charucoIdsAxisLine[i] = arrLine[i]; + } + + const int arrDiag[7] = {198, 209, 220, 231, 242, 253, 264}; + + vector charucoIdsDiagonalLine(7, 0); + + for (int i = 0; i < 7; i++){ + charucoIdsDiagonalLine[i] = arrDiag[i]; + } + + bool resultAxisLine = charucoBoard.checkCharucoCornersCollinear(charucoIdsAxisLine); + EXPECT_TRUE(resultAxisLine); + + bool resultDiagonalLine = charucoBoard.checkCharucoCornersCollinear(charucoIdsDiagonalLine); + EXPECT_TRUE(resultDiagonalLine); +} + +TEST(Charuco, testCharucoCornersCollinear_false) +{ + int squaresX = 13; + int squaresY = 28; + float squareLength = 300; + float markerLength = 150; + int dictionaryId = 11; + + aruco::Dictionary dictionary = aruco::getPredefinedDictionary(aruco::PredefinedDictionaryType(dictionaryId)); + + aruco::CharucoBoard charucoBoard(Size(squaresX, squaresY), squareLength, markerLength, dictionary); + + // consistency with C++98 + const int arr[63] = {192, 193, 194, 195, 196, 197, 198, 204, 205, 206, 207, 208, + 209, 210, 216, 217, 218, 219, 220, 221, 222, 228, 229, 230, + 231, 232, 233, 234, 240, 241, 242, 243, 244, 245, 246, 252, + 253, 254, 255, 256, 257, 258, 264, 265, 266, 267, 268, 269, + 270, 276, 277, 278, 279, 280, 281, 282, 288, 289, 290, 291, + 292, 293, 294}; + + vector charucoIds(63, 0); + for (int i = 0; i < 63; i++){ + charucoIds[i] = arr[i]; + } + + bool result = charucoBoard.checkCharucoCornersCollinear(charucoIds); + + EXPECT_FALSE(result); +} + +// test that ChArUco board detection is subpixel accurate +TEST(Charuco, testBoardSubpixelCoords) +{ + cv::Size res{500, 500}; + cv::Mat K = (cv::Mat_(3,3) << + 0.5*res.width, 0, 0.5*res.width, + 0, 0.5*res.height, 0.5*res.height, + 0, 0, 1); + + // set expected_corners values + cv::Mat expected_corners = (cv::Mat_(9,2) << + 200, 200, + 250, 200, + 300, 200, + 200, 250, + 250, 250, + 300, 250, + 200, 300, + 250, 300, + 300, 300 + ); + + cv::Mat gray; + + aruco::Dictionary dict = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_APRILTAG_36h11); + aruco::CharucoBoard board(Size(4, 4), 1.f, .8f, dict); + + // generate ChArUco board + board.generateImage(Size(res.width, res.height), gray, 150); + cv::GaussianBlur(gray, gray, Size(5, 5), 1.0); + + aruco::DetectorParameters params; + params.cornerRefinementMethod = cv::aruco::CORNER_REFINE_APRILTAG; + + aruco::CharucoParameters charucoParameters; + charucoParameters.cameraMatrix = K; + aruco::CharucoDetector detector(board, charucoParameters); + detector.setDetectorParameters(params); + + std::vector ids; + std::vector> corners; + cv::Mat c_ids, c_corners; + + detector.detectBoard(gray, c_corners, c_ids, corners, ids); + + ASSERT_EQ(ids.size(), size_t(8)); + ASSERT_EQ(c_corners.rows, expected_corners.rows); + EXPECT_NEAR(0, cvtest::norm(expected_corners, c_corners.reshape(1), NORM_INF), 1e-1); +} + +TEST(Charuco, issue_14014) +{ + string imgPath = cvtest::findDataFile("aruco/recover.png"); + Mat img = imread(imgPath); + + aruco::DetectorParameters detectorParams; + detectorParams.cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + detectorParams.cornerRefinementMinAccuracy = 0.01; + aruco::ArucoDetector detector(aruco::getPredefinedDictionary(aruco::DICT_7X7_250), detectorParams); + aruco::CharucoBoard board(Size(8, 5), 0.03455f, 0.02164f, detector.getDictionary()); + + vector corners, rejectedPoints; + vector ids; + + detector.detectMarkers(img, corners, ids, rejectedPoints); + + ASSERT_EQ(corners.size(), 19ull); + EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of detected corners + + size_t numRejPoints = rejectedPoints.size(); + ASSERT_EQ(rejectedPoints.size(), 26ull); // optional check to track regressions + EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of detected corners + + detector.refineDetectedMarkers(img, board, corners, ids, rejectedPoints); + + ASSERT_EQ(corners.size(), 20ull); + EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of rejected corners after successfully refine + + ASSERT_EQ(rejectedPoints.size() + 1, numRejPoints); + EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of rejected corners after successfully refine +} + +}} // namespace diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index b5680387cb..568324b7ca 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -3,6 +3,7 @@ // of this distribution and at http://opencv.org/license.html. #include "test_precomp.hpp" +#include "opencv2/imgproc.hpp" namespace opencv_test { namespace { @@ -11,13 +12,14 @@ std::string qrcode_images_name[] = { "version_2_down.jpg", "version_2_left.jpg", "version_2_right.jpg", "version_2_up.jpg", "version_2_top.jpg", "version_3_down.jpg", "version_3_left.jpg", "version_3_right.jpg", "version_3_up.jpg", "version_3_top.jpg", "version_4_down.jpg", "version_4_left.jpg", "version_4_right.jpg", "version_4_up.jpg", "version_4_top.jpg", - "version_5_down.jpg", "version_5_left.jpg"/*"version_5_right.jpg"*/, + "version_5_down.jpg", "version_5_left.jpg", /*"version_5_right.jpg",*/ "version_5_up.jpg", "version_5_top.jpg", "russian.jpg", "kanji.jpg", "link_github_ocv.jpg", "link_ocv.jpg", "link_wiki_cv.jpg" // version_5_right.jpg DISABLED after tile fix, PR #22025 }; +// Todo: fix corner align in big QRs to enable close_5.png std::string qrcode_images_close[] = { - "close_1.png", "close_2.png", "close_3.png", "close_4.png", "close_5.png" + "close_1.png", "close_2.png", "close_3.png", "close_4.png"//, "close_5.png" }; std::string qrcode_images_monitor[] = { "monitor_1.png", "monitor_2.png", "monitor_3.png", "monitor_4.png", "monitor_5.png" @@ -30,6 +32,7 @@ std::string qrcode_images_multiple[] = { "2_qrcodes.png", "3_close_qrcodes.png", "3_qrcodes.png", "4_qrcodes.png", "5_qrcodes.png", "6_qrcodes.png", "7_qrcodes.png", "8_close_qrcodes.png" }; + //#define UPDATE_QRCODE_TEST_DATA #ifdef UPDATE_QRCODE_TEST_DATA @@ -87,7 +90,7 @@ TEST(Objdetect_QRCode_Close, generate_test_data) const int width = cvRound(src.size().width * coeff_expansion); const int height = cvRound(src.size().height * coeff_expansion); Size new_size(width, height); - resize(src, barcode, new_size, 0, 0, INTER_LINEAR); + resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); EXPECT_TRUE(detectQRCode(barcode, corners)); #ifdef HAVE_QUIRC EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode)); @@ -125,7 +128,7 @@ TEST(Objdetect_QRCode_Monitor, generate_test_data) const int width = cvRound(src.size().width * coeff_expansion); const int height = cvRound(src.size().height * coeff_expansion); Size new_size(width, height); - resize(src, barcode, new_size, 0, 0, INTER_LINEAR); + resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); EXPECT_TRUE(detectQRCode(barcode, corners)); #ifdef HAVE_QUIRC EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode)); @@ -313,7 +316,7 @@ TEST_P(Objdetect_QRCode_Close, regression) const int width = cvRound(src.size().width * coeff_expansion); const int height = cvRound(src.size().height * coeff_expansion); Size new_size(width, height); - resize(src, barcode, new_size, 0, 0, INTER_LINEAR); + resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); std::vector corners; std::string decoded_info; QRCodeDetector qrcode; @@ -380,7 +383,7 @@ TEST_P(Objdetect_QRCode_Monitor, regression) const int width = cvRound(src.size().width * coeff_expansion); const int height = cvRound(src.size().height * coeff_expansion); Size new_size(width, height); - resize(src, barcode, new_size, 0, 0, INTER_LINEAR); + resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); std::vector corners; std::string decoded_info; QRCodeDetector qrcode; @@ -499,7 +502,7 @@ TEST_P(Objdetect_QRCode_Multi, regression) { const std::string name_current_image = GetParam(); const std::string root = "qrcode/multiple/"; - const int pixels_error = 3; + const int pixels_error = 4; std::string image_path = findDataFile(root + name_current_image); Mat src = imread(image_path); @@ -758,6 +761,26 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25) #endif } +TEST(Objdetect_QRCode_decodeMulti, decode_9_qrcodes_version7) +{ + const std::string name_current_image = "9_qrcodes_version7.jpg"; + const std::string root = "qrcode/multiple/"; + + std::string image_path = findDataFile(root + name_current_image); + Mat src = imread(image_path); + QRCodeDetector qrcode; + std::vector corners; + std::vector decoded_info; + + std::vector straight_barcode; + qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode); + EXPECT_EQ(9ull, decoded_info.size()); + const string gold_info = "I love OpenCV, QR Code version = 7, error correction = level Quartile"; + for (const auto& info : decoded_info) { + EXPECT_EQ(info, gold_info); + } +} + #endif // UPDATE_QRCODE_TEST_DATA }} // namespace diff --git a/modules/objdetect/tutorials/images/singlemarkersaxes.jpg b/modules/objdetect/tutorials/images/singlemarkersaxes.jpg new file mode 100644 index 0000000000..c7a1db44d2 Binary files /dev/null and b/modules/objdetect/tutorials/images/singlemarkersaxes.jpg differ diff --git a/modules/objdetect/tutorials/images/singlemarkersaxes2.jpg b/modules/objdetect/tutorials/images/singlemarkersaxes2.jpg new file mode 100644 index 0000000000..dc8edee15d Binary files /dev/null and b/modules/objdetect/tutorials/images/singlemarkersaxes2.jpg differ diff --git a/modules/photo/include/opencv2/photo/cuda.hpp b/modules/photo/include/opencv2/photo/cuda.hpp index a2f38167e6..b6ab40a764 100644 --- a/modules/photo/include/opencv2/photo/cuda.hpp +++ b/modules/photo/include/opencv2/photo/cuda.hpp @@ -65,11 +65,20 @@ BORDER_REPLICATE , BORDER_CONSTANT , BORDER_REFLECT and BORDER_WRAP are supporte fastNlMeansDenoising */ CV_EXPORTS void nonLocalMeans(InputArray src, OutputArray dst, - float h, - int search_window = 21, - int block_size = 7, - int borderMode = BORDER_DEFAULT, - Stream& stream = Stream::Null()); + float h, + int search_window = 21, + int block_size = 7, + int borderMode = BORDER_DEFAULT, + Stream& stream = Stream::Null()); +CV_WRAP inline void nonLocalMeans(const GpuMat& src, CV_OUT GpuMat& dst, + float h, + int search_window = 21, + int block_size = 7, + int borderMode = BORDER_DEFAULT, + Stream& stream = Stream::Null()) +{ + nonLocalMeans(InputArray(src), OutputArray(dst), h, search_window, block_size, borderMode, stream); +}; /** @brief Perform image denoising using Non-local Means Denoising algorithm with several computational @@ -93,10 +102,18 @@ FastNonLocalMeansDenoising::labMethod. fastNlMeansDenoising */ CV_EXPORTS void fastNlMeansDenoising(InputArray src, OutputArray dst, - float h, - int search_window = 21, - int block_size = 7, - Stream& stream = Stream::Null()); + float h, + int search_window = 21, + int block_size = 7, + Stream& stream = Stream::Null()); +CV_WRAP inline void fastNlMeansDenoising(const GpuMat& src, CV_OUT GpuMat& dst, + float h, + int search_window = 21, + int block_size = 7, + Stream& stream = Stream::Null()) +{ + fastNlMeansDenoising(InputArray(src), OutputArray(dst), h, search_window, block_size, stream); +} /** @brief Modification of fastNlMeansDenoising function for colored images @@ -124,6 +141,14 @@ CV_EXPORTS void fastNlMeansDenoisingColored(InputArray src, OutputArray dst, int search_window = 21, int block_size = 7, Stream& stream = Stream::Null()); +CV_WRAP inline void fastNlMeansDenoisingColored(const GpuMat& src, CV_OUT GpuMat& dst, + float h_luminance, float photo_render, + int search_window = 21, + int block_size = 7, + Stream& stream = Stream::Null()) +{ + fastNlMeansDenoisingColored(InputArray(src), OutputArray(dst), h_luminance, photo_render, search_window, block_size, stream); +} //! @} photo diff --git a/modules/photo/perf/perf_hdr.cpp b/modules/photo/perf/perf_hdr.cpp new file mode 100644 index 0000000000..4001a7913b --- /dev/null +++ b/modules/photo/perf/perf_hdr.cpp @@ -0,0 +1,64 @@ +// 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 "perf_precomp.hpp" + +namespace opencv_test +{ +namespace +{ +struct ExposureSeq +{ + std::vector images; + std::vector times; +}; + +ExposureSeq loadExposureSeq(const std::string& list_filename) +{ + std::ifstream list_file(list_filename); + EXPECT_TRUE(list_file.is_open()); + string name; + float val; + const String path(list_filename.substr(0, list_filename.find_last_of("\\/") + 1)); + ExposureSeq seq; + while (list_file >> name >> val) + { + Mat img = imread(path + name); + EXPECT_FALSE(img.empty()) << "Could not load input image " << path + name; + seq.images.push_back(img); + seq.times.push_back(1 / val); + } + list_file.close(); + return seq; +} + +PERF_TEST(HDR, Mertens) +{ + const ExposureSeq seq = loadExposureSeq(getDataPath("cv/hdr/exposures/list.txt")); + Ptr merge = createMergeMertens(); + Mat result(seq.images.front().size(), seq.images.front().type()); + TEST_CYCLE() merge->process(seq.images, result); + SANITY_CHECK_NOTHING(); +} + +PERF_TEST(HDR, Debevec) +{ + const ExposureSeq seq = loadExposureSeq(getDataPath("cv/hdr/exposures/list.txt")); + Ptr merge = createMergeDebevec(); + Mat result(seq.images.front().size(), seq.images.front().type()); + TEST_CYCLE() merge->process(seq.images, result, seq.times); + SANITY_CHECK_NOTHING(); +} + +PERF_TEST(HDR, Robertson) +{ + const ExposureSeq seq = loadExposureSeq(getDataPath("cv/hdr/exposures/list.txt")); + Ptr merge = createMergeRobertson(); + Mat result(seq.images.front().size(), seq.images.front().type()); + TEST_CYCLE() merge->process(seq.images, result, seq.times); + SANITY_CHECK_NOTHING(); +} + +} // namespace +} // namespace opencv_test diff --git a/modules/photo/src/merge.cpp b/modules/photo/src/merge.cpp index e6a00fedb8..18050574ab 100644 --- a/modules/photo/src/merge.cpp +++ b/modules/photo/src/merge.cpp @@ -172,87 +172,97 @@ public: std::vector weights(images.size()); Mat weight_sum = Mat::zeros(size, CV_32F); + Mutex weight_sum_mutex; - for(size_t i = 0; i < images.size(); i++) { - Mat img, gray, contrast, saturation, wellexp; - std::vector splitted(channels); + parallel_for_(Range(0, static_cast(images.size())), [&](const Range& range) { + for(int i = range.start; i < range.end; i++) { + Mat img, gray, contrast, saturation, wellexp; + std::vector splitted(channels); - images[i].convertTo(img, CV_32F, 1.0f/255.0f); - if(channels == 3) { - cvtColor(img, gray, COLOR_RGB2GRAY); - } else { - img.copyTo(gray); + images[i].convertTo(img, CV_32F, 1.0f/255.0f); + if(channels == 3) { + cvtColor(img, gray, COLOR_RGB2GRAY); + } else { + img.copyTo(gray); + } + images[i] = img; + split(img, splitted); + + Laplacian(gray, contrast, CV_32F); + contrast = abs(contrast); + + Mat mean = Mat::zeros(size, CV_32F); + for(int c = 0; c < channels; c++) { + mean += splitted[c]; + } + mean /= channels; + + saturation = Mat::zeros(size, CV_32F); + for(int c = 0; c < channels; c++) { + Mat deviation = splitted[c] - mean; + pow(deviation, 2.0f, deviation); + saturation += deviation; + } + sqrt(saturation, saturation); + + wellexp = Mat::ones(size, CV_32F); + for(int c = 0; c < channels; c++) { + Mat expo = splitted[c] - 0.5f; + pow(expo, 2.0f, expo); + expo = -expo / 0.08f; + exp(expo, expo); + wellexp = wellexp.mul(expo); + } + + pow(contrast, wcon, contrast); + pow(saturation, wsat, saturation); + pow(wellexp, wexp, wellexp); + + weights[i] = contrast; + if(channels == 3) { + weights[i] = weights[i].mul(saturation); + } + weights[i] = weights[i].mul(wellexp) + 1e-12f; + + AutoLock lock(weight_sum_mutex); + weight_sum += weights[i]; } - split(img, splitted); + }); - Laplacian(gray, contrast, CV_32F); - contrast = abs(contrast); - - Mat mean = Mat::zeros(size, CV_32F); - for(int c = 0; c < channels; c++) { - mean += splitted[c]; - } - mean /= channels; - - saturation = Mat::zeros(size, CV_32F); - for(int c = 0; c < channels; c++) { - Mat deviation = splitted[c] - mean; - pow(deviation, 2.0f, deviation); - saturation += deviation; - } - sqrt(saturation, saturation); - - wellexp = Mat::ones(size, CV_32F); - for(int c = 0; c < channels; c++) { - Mat expo = splitted[c] - 0.5f; - pow(expo, 2.0f, expo); - expo = -expo / 0.08f; - exp(expo, expo); - wellexp = wellexp.mul(expo); - } - - pow(contrast, wcon, contrast); - pow(saturation, wsat, saturation); - pow(wellexp, wexp, wellexp); - - weights[i] = contrast; - if(channels == 3) { - weights[i] = weights[i].mul(saturation); - } - weights[i] = weights[i].mul(wellexp) + 1e-12f; - weight_sum += weights[i]; - } int maxlevel = static_cast(logf(static_cast(min(size.width, size.height))) / logf(2.0f)); std::vector res_pyr(maxlevel + 1); + std::vector res_pyr_mutexes(maxlevel + 1); - for(size_t i = 0; i < images.size(); i++) { - weights[i] /= weight_sum; - Mat img; - images[i].convertTo(img, CV_32F, 1.0f/255.0f); + parallel_for_(Range(0, static_cast(images.size())), [&](const Range& range) { + for(int i = range.start; i < range.end; i++) { + weights[i] /= weight_sum; - std::vector img_pyr, weight_pyr; - buildPyramid(img, img_pyr, maxlevel); - buildPyramid(weights[i], weight_pyr, maxlevel); + std::vector img_pyr, weight_pyr; + buildPyramid(images[i], img_pyr, maxlevel); + buildPyramid(weights[i], weight_pyr, maxlevel); - for(int lvl = 0; lvl < maxlevel; lvl++) { - Mat up; - pyrUp(img_pyr[lvl + 1], up, img_pyr[lvl].size()); - img_pyr[lvl] -= up; - } - for(int lvl = 0; lvl <= maxlevel; lvl++) { - std::vector splitted(channels); - split(img_pyr[lvl], splitted); - for(int c = 0; c < channels; c++) { - splitted[c] = splitted[c].mul(weight_pyr[lvl]); + for(int lvl = 0; lvl < maxlevel; lvl++) { + Mat up; + pyrUp(img_pyr[lvl + 1], up, img_pyr[lvl].size()); + img_pyr[lvl] -= up; } - merge(splitted, img_pyr[lvl]); - if(res_pyr[lvl].empty()) { - res_pyr[lvl] = img_pyr[lvl]; - } else { - res_pyr[lvl] += img_pyr[lvl]; + for(int lvl = 0; lvl <= maxlevel; lvl++) { + std::vector splitted(channels); + split(img_pyr[lvl], splitted); + for(int c = 0; c < channels; c++) { + splitted[c] = splitted[c].mul(weight_pyr[lvl]); + } + merge(splitted, img_pyr[lvl]); + + AutoLock lock(res_pyr_mutexes[lvl]); + if(res_pyr[lvl].empty()) { + res_pyr[lvl] = img_pyr[lvl]; + } else { + res_pyr[lvl] += img_pyr[lvl]; + } } } - } + }); for(int lvl = maxlevel; lvl > 0; lvl--) { Mat up; pyrUp(res_pyr[lvl], up, res_pyr[lvl - 1].size()); diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index eb800b6ad5..71e1cc05ee 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -444,6 +444,30 @@ PyObject* pyopencv_from(const int& value) // --- int64 +template<> +bool pyopencv_to(PyObject* obj, int64& value, const ArgInfo& info) +{ + if (!obj || obj == Py_None) + { + return true; + } + if (isBool(obj)) + { + failmsg("Argument '%s' must be integer, not bool", info.name); + return false; + } + if (PyArray_IsIntegerScalar(obj)) + { + value = PyLong_AsLongLong(obj); + } + else + { + failmsg("Argument '%s' is required to be an integer", info.name); + return false; + } + return !CV_HAS_CONVERSION_ERROR(value); +} + template<> PyObject* pyopencv_from(const int64& value) { diff --git a/modules/python/src2/cv2_convert.hpp b/modules/python/src2/cv2_convert.hpp index 563f5386b7..eae20b2c98 100644 --- a/modules/python/src2/cv2_convert.hpp +++ b/modules/python/src2/cv2_convert.hpp @@ -62,6 +62,10 @@ PyObject* pyopencv_from(const T& src) { return PyOpenCV_Converter::from(src); template bool pyopencv_to(PyObject* o, cv::Matx<_Tp, m, n>& mx, const ArgInfo& info) { + if (!o || o == Py_None) { + return true; + } + cv::Mat tmp; if (!pyopencv_to(o, tmp, info)) { return false; @@ -121,6 +125,7 @@ template<> bool pyopencv_to(PyObject* obj, int& value, const ArgInfo& info); template<> PyObject* pyopencv_from(const int& value); // --- int64 +template<> bool pyopencv_to(PyObject* obj, int64& value, const ArgInfo& info); template<> PyObject* pyopencv_from(const int64& value); // There is conflict between "size_t" and "unsigned int". diff --git a/modules/python/src2/cv2_numpy.hpp b/modules/python/src2/cv2_numpy.hpp index 7c386c7dbc..934333921d 100644 --- a/modules/python/src2/cv2_numpy.hpp +++ b/modules/python/src2/cv2_numpy.hpp @@ -51,13 +51,13 @@ NPY_TYPES asNumpyType() return NPY_U##dst; \ } -CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int8_t, INT8); +CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int8_t, INT8) -CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int16_t, INT16); +CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int16_t, INT16) -CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int32_t, INT32); +CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int32_t, INT32) -CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int64_t, INT64); +CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION(int64_t, INT64) #undef CV_GENERATE_INTEGRAL_TYPE_NPY_CONVERSION diff --git a/modules/python/src2/cv2_util.hpp b/modules/python/src2/cv2_util.hpp index 3662ffcd4d..0d27e98825 100644 --- a/modules/python/src2/cv2_util.hpp +++ b/modules/python/src2/cv2_util.hpp @@ -12,7 +12,7 @@ bool isPythonBindingsDebugEnabled(); void emit_failmsg(PyObject * exc, const char *msg); int failmsg(const char *fmt, ...); -PyObject* failmsgp(const char *fmt, ...);; +PyObject* failmsgp(const char *fmt, ...); //====================================================================================================================== diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index 1e3ba1150d..6a3b2c424f 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -169,10 +169,10 @@ static int pyopencv_${name}_set_${member}(pyopencv_${name}_t* p, PyObject *value gen_template_prop_init = Template(""" - {(char*)"${member}", (getter)pyopencv_${name}_get_${member}, NULL, (char*)"${member}", NULL},""") + {(char*)"${export_member_name}", (getter)pyopencv_${name}_get_${member}, NULL, (char*)"${export_member_name}", NULL},""") gen_template_rw_prop_init = Template(""" - {(char*)"${member}", (getter)pyopencv_${name}_get_${member}, (setter)pyopencv_${name}_set_${member}, (char*)"${member}", NULL},""") + {(char*)"${export_member_name}", (getter)pyopencv_${name}_get_${member}, (setter)pyopencv_${name}_set_${member}, (char*)"${export_member_name}", NULL},""") gen_template_overloaded_function_call = Template(""" { @@ -212,6 +212,7 @@ simple_argtype_mapping = { "c_string": ArgTypeInfo("char*", FormatStrings.string, '(char*)""'), "string": ArgTypeInfo("std::string", FormatStrings.object, None, True), "Stream": ArgTypeInfo("Stream", FormatStrings.object, 'Stream::Null()', True), + "UMat": ArgTypeInfo("UMat", FormatStrings.object, 'UMat()', True), # FIXIT: switch to CV_EXPORTS_W_SIMPLE as UMat is already a some kind of smart pointer } # Set of reserved keywords for Python. Can be acquired via the following call @@ -245,6 +246,13 @@ class ClassProp(object): if "/RW" in decl[3]: self.readonly = False + @property + def export_name(self): + if self.name in python_reserved_keywords: + return self.name + "_" + return self.name + + class ClassInfo(object): def __init__(self, name, decl=None, codegen=None): # Scope name can be a module or other class e.g. cv::SimpleBlobDetector::Params @@ -296,6 +304,7 @@ class ClassInfo(object): self.issimple = True elif m == "/Params": self.isparams = True + self.issimple = True # TODO: rework 'params' generation/handling (see #19156 and #22934) self.props = [ClassProp(p) for p in decl[3]] if not self.has_export_alias and self.original_name.startswith("Cv"): @@ -359,13 +368,13 @@ class ClassInfo(object): else: getset_code.write(gen_template_get_prop.substitute(name=self.name, member=pname, membertype=p.tp, access=access_op)) if p.readonly: - getset_inits.write(gen_template_prop_init.substitute(name=self.name, member=pname)) + getset_inits.write(gen_template_prop_init.substitute(name=self.name, member=pname, export_member_name=p.export_name)) else: if self.isalgorithm: getset_code.write(gen_template_set_prop_algo.substitute(name=self.name, cname=self.cname, member=pname, membertype=p.tp, access=access_op)) else: getset_code.write(gen_template_set_prop.substitute(name=self.name, member=pname, membertype=p.tp, access=access_op)) - getset_inits.write(gen_template_rw_prop_init.substitute(name=self.name, member=pname)) + getset_inits.write(gen_template_rw_prop_init.substitute(name=self.name, member=pname, export_member_name=p.export_name)) methods_code = StringIO() methods_inits = StringIO() @@ -436,6 +445,7 @@ class ArgInfo(object): self.name += "_" self.defval = arg_tuple[2] self.isarray = False + self.is_smart_ptr = self.tp.startswith('Ptr<') # FIXIT: handle through modifiers - need to modify parser self.arraylen = 0 self.arraycvt = None self.inputarg = True @@ -746,7 +756,21 @@ class FuncInfo(object): if any(tp in codegen.enums.keys() for tp in tp_candidates): defval0 = "static_cast<%s>(%d)" % (a.tp, 0) - arg_type_info = simple_argtype_mapping.get(tp, ArgTypeInfo(tp, FormatStrings.object, defval0, True)) + if tp in simple_argtype_mapping: + arg_type_info = simple_argtype_mapping[tp] + else: + if tp in all_classes: + tp_classinfo = all_classes[tp] + cname_of_value = tp_classinfo.cname if tp_classinfo.issimple else "Ptr<{}>".format(tp_classinfo.cname) + arg_type_info = ArgTypeInfo(cname_of_value, FormatStrings.object, defval0, True) + assert not (a.is_smart_ptr and tp_classinfo.issimple), "Can't pass 'simple' type as Ptr<>" + if not a.is_smart_ptr and not tp_classinfo.issimple: + assert amp == '' + amp = '*' + else: + # FIXIT: Ptr_ / vector_ / enums / nested types + arg_type_info = ArgTypeInfo(tp, FormatStrings.object, defval0, True) + parse_name = a.name if a.py_inputarg: if arg_type_info.strict_conversion: diff --git a/modules/python/test/test_cuda.py b/modules/python/test/test_cuda.py index a5f3fae847..586c0ecf81 100644 --- a/modules/python/test/test_cuda.py +++ b/modules/python/test/test_cuda.py @@ -64,5 +64,10 @@ class cuda_test(NewOpenCVTests): self.assertTrue(cuMat.step == 0) self.assertTrue(cuMat.size() == (0, 0)) + def test_cuda_denoising(self): + self.assertEqual(True, hasattr(cv.cuda, 'fastNlMeansDenoising')) + self.assertEqual(True, hasattr(cv.cuda, 'fastNlMeansDenoisingColored')) + self.assertEqual(True, hasattr(cv.cuda, 'nonLocalMeans')) + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index fd21656d83..765201e973 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -126,6 +126,23 @@ class Bindings(NewOpenCVTests): test_overload_resolution('rect with float coordinates', (4.5, 4, 2, 1)) test_overload_resolution('rect with wrong number of coordinates', (4, 4, 1)) + def test_properties_with_reserved_keywords_names_are_transformed(self): + obj = cv.utils.ClassWithKeywordProperties(except_arg=23) + self.assertTrue(hasattr(obj, "lambda_"), + msg="Class doesn't have RW property with converted name") + try: + obj.lambda_ = 32 + except Exception as e: + self.fail("Failed to set value to RW property. Error: {}".format(e)) + + self.assertTrue(hasattr(obj, "except_"), + msg="Class doesn't have readonly property with converted name") + self.assertEqual(obj.except_, 23, + msg="Can't access readonly property value") + with self.assertRaises(AttributeError): + obj.except_ = 32 + + class Arguments(NewOpenCVTests): @@ -194,8 +211,8 @@ class Arguments(NewOpenCVTests): def test_parse_to_bool_convertible(self): try_to_convert = partial(self._try_to_convert, cv.utils.dumpBool) - for convertible_true in (True, 1, 64, np.bool(1), np.int8(123), np.int16(11), np.int32(2), - np.int64(1), np.bool_(3), np.bool8(12)): + for convertible_true in (True, 1, 64, np.int8(123), np.int16(11), np.int32(2), + np.int64(1), np.bool_(12)): actual = try_to_convert(convertible_true) self.assertEqual('bool: true', actual, msg=get_conversion_error_msg(convertible_true, 'bool: true', actual)) @@ -206,8 +223,8 @@ class Arguments(NewOpenCVTests): msg=get_conversion_error_msg(convertible_false, 'bool: false', actual)) def test_parse_to_bool_not_convertible(self): - for not_convertible in (1.2, np.float(2.3), 's', 'str', (1, 2), [1, 2], complex(1, 1), - complex(imag=2), complex(1.1), np.array([1, 0], dtype=np.bool)): + for not_convertible in (1.2, np.float32(2.3), 's', 'str', (1, 2), [1, 2], complex(1, 1), + complex(imag=2), complex(1.1), np.array([1, 0], dtype=bool)): with self.assertRaises((TypeError, OverflowError), msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpBool(not_convertible) @@ -221,7 +238,7 @@ class Arguments(NewOpenCVTests): msg=get_conversion_error_msg(convertible_true, 'bool: true', actual)) def test_parse_to_bool_not_convertible_extra(self): - for not_convertible in (np.array([False]), np.array([True], dtype=np.bool)): + for not_convertible in (np.array([False]), np.array([True])): with self.assertRaises((TypeError, OverflowError), msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpBool(not_convertible) @@ -238,7 +255,7 @@ class Arguments(NewOpenCVTests): def test_parse_to_int_not_convertible(self): min_int, max_int = get_limits(ctypes.c_int) - for not_convertible in (1.2, np.float(4), float(3), np.double(45), 's', 'str', + for not_convertible in (1.2, float(3), np.float32(4), np.double(45), 's', 'str', np.array([1, 2]), (1,), [1, 2], min_int - 1, max_int + 1, complex(1, 1), complex(imag=2), complex(1.1)): with self.assertRaises((TypeError, OverflowError, ValueError), @@ -248,11 +265,32 @@ class Arguments(NewOpenCVTests): def test_parse_to_int_not_convertible_extra(self): for not_convertible in (np.bool_(True), True, False, np.float32(2.3), np.array([3, ], dtype=int), np.array([-2, ], dtype=np.int32), - np.array([1, ], dtype=np.int), np.array([11, ], dtype=np.uint8)): + np.array([11, ], dtype=np.uint8)): with self.assertRaises((TypeError, OverflowError), msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpInt(not_convertible) + def test_parse_to_int64_convertible(self): + try_to_convert = partial(self._try_to_convert, cv.utils.dumpInt64) + min_int64, max_int64 = get_limits(ctypes.c_longlong) + for convertible in (-10, -1, 2, int(43.2), np.uint8(15), np.int8(33), np.int16(-13), + np.int32(4), np.int64(345), (23), min_int64, max_int64, np.int_(33)): + expected = 'int64: {0:d}'.format(convertible) + actual = try_to_convert(convertible) + self.assertEqual(expected, actual, + msg=get_conversion_error_msg(convertible, expected, actual)) + + def test_parse_to_int64_not_convertible(self): + min_int64, max_int64 = get_limits(ctypes.c_longlong) + for not_convertible in (1.2, np.float32(4), float(3), np.double(45), 's', 'str', + np.array([1, 2]), (1,), [1, 2], min_int64 - 1, max_int64 + 1, + complex(1, 1), complex(imag=2), complex(1.1), np.bool_(True), + True, False, np.float32(2.3), np.array([3, ], dtype=int), + np.array([-2, ], dtype=np.int32), np.array([11, ], dtype=np.uint8)): + with self.assertRaises((TypeError, OverflowError, ValueError), + msg=get_no_exception_msg(not_convertible)): + _ = cv.utils.dumpInt64(not_convertible) + def test_parse_to_size_t_convertible(self): try_to_convert = partial(self._try_to_convert, cv.utils.dumpSizeT) _, max_uint = get_limits(ctypes.c_uint) @@ -266,7 +304,7 @@ class Arguments(NewOpenCVTests): def test_parse_to_size_t_not_convertible(self): min_long, _ = get_limits(ctypes.c_long) - for not_convertible in (1.2, True, False, np.bool_(True), np.float(4), float(3), + for not_convertible in (1.2, True, False, np.bool_(True), np.float32(4), float(3), np.double(45), 's', 'str', np.array([1, 2]), (1,), [1, 2], np.float64(6), complex(1, 1), complex(imag=2), complex(1.1), -1, min_long, np.int8(-35)): @@ -292,7 +330,7 @@ class Arguments(NewOpenCVTests): def test_parse_to_float_convertible(self): try_to_convert = partial(self._try_to_convert, cv.utils.dumpFloat) min_float, max_float = get_limits(ctypes.c_float) - for convertible in (2, -13, 1.24, float(32), np.float(32.45), np.double(12.23), + for convertible in (2, -13, 1.24, np.float32(32.45), float(32), np.double(12.23), np.float32(-12.3), np.float64(3.22), np.float_(-1.5), min_float, max_float, np.inf, -np.inf, float('Inf'), -float('Inf'), np.double(np.inf), np.double(-np.inf), np.double(float('Inf')), @@ -318,7 +356,7 @@ class Arguments(NewOpenCVTests): msg=get_conversion_error_msg(inf, expected, actual)) def test_parse_to_float_not_convertible(self): - for not_convertible in ('s', 'str', (12,), [1, 2], np.array([1, 2], dtype=np.float), + for not_convertible in ('s', 'str', (12,), [1, 2], np.array([1, 2], dtype=float), np.array([1, 2], dtype=np.double), complex(1, 1), complex(imag=2), complex(1.1)): with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)): @@ -327,7 +365,7 @@ class Arguments(NewOpenCVTests): def test_parse_to_float_not_convertible_extra(self): for not_convertible in (np.bool_(False), True, False, np.array([123, ], dtype=int), np.array([1., ]), np.array([False]), - np.array([True], dtype=np.bool)): + np.array([True])): with self.assertRaises((TypeError, OverflowError), msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpFloat(not_convertible) @@ -336,7 +374,7 @@ class Arguments(NewOpenCVTests): try_to_convert = partial(self._try_to_convert, cv.utils.dumpDouble) min_float, max_float = get_limits(ctypes.c_float) min_double, max_double = get_limits(ctypes.c_double) - for convertible in (2, -13, 1.24, np.float(32.45), float(2), np.double(12.23), + for convertible in (2, -13, 1.24, np.float32(32.45), float(2), np.double(12.23), np.float32(-12.3), np.float64(3.22), np.float_(-1.5), min_float, max_float, min_double, max_double, np.inf, -np.inf, float('Inf'), -float('Inf'), np.double(np.inf), np.double(-np.inf), @@ -355,7 +393,7 @@ class Arguments(NewOpenCVTests): "Actual: {}".format(type(nan).__name__, actual)) def test_parse_to_double_not_convertible(self): - for not_convertible in ('s', 'str', (12,), [1, 2], np.array([1, 2], dtype=np.float), + for not_convertible in ('s', 'str', (12,), [1, 2], np.array([1, 2], dtype=np.float32), np.array([1, 2], dtype=np.double), complex(1, 1), complex(imag=2), complex(1.1)): with self.assertRaises((TypeError), msg=get_no_exception_msg(not_convertible)): @@ -364,14 +402,14 @@ class Arguments(NewOpenCVTests): def test_parse_to_double_not_convertible_extra(self): for not_convertible in (np.bool_(False), True, False, np.array([123, ], dtype=int), np.array([1., ]), np.array([False]), - np.array([12.4], dtype=np.double), np.array([True], dtype=np.bool)): + np.array([12.4], dtype=np.double), np.array([True])): with self.assertRaises((TypeError, OverflowError), msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpDouble(not_convertible) def test_parse_to_cstring_convertible(self): try_to_convert = partial(self._try_to_convert, cv.utils.dumpCString) - for convertible in ('', 's', 'str', str(123), ('char'), np.str('test1'), np.str_('test2')): + for convertible in ('', 's', 'str', str(123), ('char'), np.str_('test2')): expected = 'string: ' + convertible actual = try_to_convert(convertible) self.assertEqual(expected, actual, @@ -385,7 +423,7 @@ class Arguments(NewOpenCVTests): def test_parse_to_string_convertible(self): try_to_convert = partial(self._try_to_convert, cv.utils.dumpString) - for convertible in (None, '', 's', 'str', str(123), np.str('test1'), np.str_('test2')): + for convertible in (None, '', 's', 'str', str(123), np.str_('test2')): expected = 'string: ' + (convertible if convertible else '') actual = try_to_convert(convertible) self.assertEqual(expected, actual, @@ -507,12 +545,12 @@ class Arguments(NewOpenCVTests): def test_parse_vector_int_not_convertible(self): np.random.seed(123098765) - arr = np.random.randint(-20, 20, 40).astype(np.float).reshape(10, 2, 2) + arr = np.random.randint(-20, 20, 40).astype(np.float32).reshape(10, 2, 2) int_min, int_max = get_limits(ctypes.c_int) test_dict = {1: 2, 3: 10, 10: 20} for not_convertible in ((int_min, 1, 2.5, 3, int_max), [True, 50], 'test', test_dict, reversed([1, 2, 3]), - np.array([int_min, -10, 24, [1, 2]], dtype=np.object), + np.array([int_min, -10, 24, [1, 2]], dtype=object), np.array([[1, 2], [3, 4]]), arr[:, 0, 1],): with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpVectorOfInt(not_convertible) @@ -524,7 +562,7 @@ class Arguments(NewOpenCVTests): for convertible in ((1, 2.12, 3.5), [40, 50], tuple(), np.array([-10, 24], dtype=np.int32), np.array([-12.5, 1.4], dtype=np.double), - np.array([10, 230, 12], dtype=np.float), arr[:, 0, 1], ): + np.array([10, 230, 12], dtype=np.float32), arr[:, 0, 1], ): expected = "[" + ", ".join(map(lambda v: "{:.2f}".format(v), convertible)) + "]" actual = try_to_convert(convertible) self.assertEqual(expected, actual, @@ -533,7 +571,7 @@ class Arguments(NewOpenCVTests): def test_parse_vector_double_not_convertible(self): test_dict = {1: 2, 3: 10, 10: 20} for not_convertible in (('t', 'e', 's', 't'), [True, 50.55], 'test', test_dict, - np.array([-10.1, 24.5, [1, 2]], dtype=np.object), + np.array([-10.1, 24.5, [1, 2]], dtype=object), np.array([[1, 2], [3, 4]]),): with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpVectorOfDouble(not_convertible) @@ -545,7 +583,7 @@ class Arguments(NewOpenCVTests): arr_of_rect_cast = np.random.randint(10, 40, 4 * 5).astype(np.uint8).reshape(5, 4) for convertible in (((1, 2, 3, 4), (10, -20, 30, 10)), arr_of_rect_int32, arr_of_rect_cast, arr_of_rect_int32.astype(np.int8), [[5, 3, 1, 4]], - ((np.int8(4), np.uint8(10), np.int(32), np.int16(55)),)): + ((np.int8(4), np.uint8(10), int(32), np.int16(55)),)): expected = "[" + ", ".join(map(lambda v: "[x={}, y={}, w={}, h={}]".format(*v), convertible)) + "]" actual = try_to_convert(convertible) self.assertEqual(expected, actual, @@ -553,10 +591,10 @@ class Arguments(NewOpenCVTests): def test_parse_vector_rect_not_convertible(self): np.random.seed(1238765) - arr = np.random.randint(5, 20, 4 * 3).astype(np.float).reshape(3, 4) + arr = np.random.randint(5, 20, 4 * 3).astype(np.float32).reshape(3, 4) for not_convertible in (((1, 2, 3, 4), (10.5, -20, 30.1, 10)), arr, [[5, 3, 1, 4], []], - ((np.float(4), np.uint8(10), np.int(32), np.int16(55)),)): + ((float(4), np.uint8(10), int(32), np.int16(55)),)): with self.assertRaises(TypeError, msg=get_no_exception_msg(not_convertible)): _ = cv.utils.dumpVectorOfRect(not_convertible) @@ -643,26 +681,14 @@ class Arguments(NewOpenCVTests): msg="Classes from submodules and global module don't refer " "to the same type") - def test_class_from_submodule_has_global_alias(self): - self.assertTrue(hasattr(cv.ml, "Boost"), - msg="Class is not registered in the submodule") - self.assertTrue(hasattr(cv, "ml_Boost"), - msg="Class from submodule doesn't have alias in the " - "global module") - self.assertEqual(cv.ml.Boost, cv.ml_Boost, - msg="Classes from submodules and global module don't refer " - "to the same type") - def test_inner_class_has_global_alias(self): self.assertTrue(hasattr(cv.SimpleBlobDetector, "Params"), msg="Class is not registered as inner class") self.assertTrue(hasattr(cv, "SimpleBlobDetector_Params"), msg="Inner class doesn't have alias in the global module") self.assertEqual(cv.SimpleBlobDetector.Params, cv.SimpleBlobDetector_Params, - msg="Inner class and class in global module don't refer " - "to the same type") - self.assertTrue(hasattr(cv, "SimpleBlobDetector_Params"), - msg="Inner class doesn't have alias in the global module") + msg="Inner class and class in global module don't refer " + "to the same type") def test_export_class_with_different_name(self): self.assertTrue(hasattr(cv.utils.nested, "ExportClassName"), @@ -683,7 +709,8 @@ class Arguments(NewOpenCVTests): def test_export_inner_class_of_class_exported_with_different_name(self): if not hasattr(cv.utils.nested, "ExportClassName"): - raise unittest.SkipTest("Outer class with export alias is not registered in the submodule") + raise unittest.SkipTest( + "Outer class with export alias is not registered in the submodule") self.assertTrue(hasattr(cv.utils.nested.ExportClassName, "Params"), msg="Inner class with export alias is not registered in " @@ -701,14 +728,15 @@ class Arguments(NewOpenCVTests): self.assertEqual( params.int_value, instance.getIntParam(), msg="Class initialized with wrong integer parameter. Expected: {}. Actual: {}".format( - params.int_value, instance.getIntParam() - )) + params.int_value, instance.getIntParam() + ) + ) self.assertEqual( params.float_value, instance.getFloatParam(), msg="Class initialized with wrong integer parameter. Expected: {}. Actual: {}".format( - params.float_value, instance.getFloatParam() - )) - + params.float_value, instance.getFloatParam() + ) + ) @@ -736,6 +764,13 @@ class CanUsePurePythonModuleFunction(NewOpenCVTests): res = cv.utils._native.testOverwriteNativeMethod(123) self.assertEqual(res, 123, msg="Failed to call native method implementation") + def test_default_matx_argument(self): + res = cv.utils.dumpVec2i() + self.assertEqual(res, "Vec2i(42, 24)", + msg="Default argument is not properly handled") + res = cv.utils.dumpVec2i((12, 21)) + self.assertEqual(res, "Vec2i(12, 21)") + class SamplesFindFile(NewOpenCVTests): diff --git a/modules/python/test/test_umat.py b/modules/python/test/test_umat.py index 0081bec546..d8a9a10283 100644 --- a/modules/python/test/test_umat.py +++ b/modules/python/test/test_umat.py @@ -107,12 +107,19 @@ class UMat(NewOpenCVTests): images, _ = load_exposure_seq(os.path.join(test_data_path, 'exposures')) + # As we want to test mat vs. umat here, we temporarily set only one worker-thread to achieve + # deterministic summations inside mertens' parallelized process. + num_threads = cv.getNumThreads() + cv.setNumThreads(1) + merge = cv.createMergeMertens() mat_result = merge.process(images) umat_images = [cv.UMat(img) for img in images] umat_result = merge.process(umat_images) + cv.setNumThreads(num_threads) + self.assertTrue(np.allclose(umat_result.get(), mat_result)) diff --git a/modules/python/test/tst_scene_render.py b/modules/python/test/tst_scene_render.py index 2dd6309ce1..33fef512e9 100644 --- a/modules/python/test/tst_scene_render.py +++ b/modules/python/test/tst_scene_render.py @@ -85,7 +85,7 @@ class TestSceneRender(): img[self.currentCenter[0]:self.currentCenter[0]+self.foreground.shape[0], self.currentCenter[1]:self.currentCenter[1]+self.foreground.shape[1]] = self.foreground else: - self.currentRect = self.initialRect + np.int( 30*cos(self.time) + 50*sin(self.time/3)) + self.currentRect = self.initialRect + int( 30*cos(self.time) + 50*sin(self.time/3)) if self.deformation: self.currentRect[1:3] += int(self.h/20*cos(self.time)) cv.fillConvexPoly(img, self.currentRect, (0, 0, 255)) diff --git a/modules/stereo/include/opencv2/stereo.hpp b/modules/stereo/include/opencv2/stereo.hpp index f3a0de2d32..d4cda975f1 100644 --- a/modules/stereo/include/opencv2/stereo.hpp +++ b/modules/stereo/include/opencv2/stereo.hpp @@ -89,11 +89,18 @@ coordinates. The function distinguishes the following two cases: \end{bmatrix}\f] \f[\texttt{P2} = \begin{bmatrix} - f & 0 & cx_2 & T_x*f \\ + f & 0 & cx_2 & T_x \cdot f \\ 0 & f & cy & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} ,\f] + \f[\texttt{Q} = \begin{bmatrix} + 1 & 0 & 0 & -cx_1 \\ + 0 & 1 & 0 & -cy \\ + 0 & 0 & 0 & f \\ + 0 & 0 & -\frac{1}{T_x} & \frac{cx_1 - cx_2}{T_x} + \end{bmatrix} \f] + where \f$T_x\f$ is a horizontal shift between the cameras and \f$cx_1=cx_2\f$ if @ref STEREO_ZERO_DISPARITY is set. @@ -109,10 +116,17 @@ coordinates. The function distinguishes the following two cases: \f[\texttt{P2} = \begin{bmatrix} f & 0 & cx & 0 \\ - 0 & f & cy_2 & T_y*f \\ + 0 & f & cy_2 & T_y \cdot f \\ 0 & 0 & 1 & 0 \end{bmatrix},\f] + \f[\texttt{Q} = \begin{bmatrix} + 1 & 0 & 0 & -cx \\ + 0 & 1 & 0 & -cy_1 \\ + 0 & 0 & 0 & f \\ + 0 & 0 & -\frac{1}{T_y} & \frac{cy_1 - cy_2}{T_y} + \end{bmatrix} \f] + where \f$T_y\f$ is a vertical shift between the cameras and \f$cy_1=cy_2\f$ if @ref STEREO_ZERO_DISPARITY is set. @@ -148,8 +162,8 @@ CV_EXPORTS_W void stereoRectify( InputArray cameraMatrix1, InputArray distCoeffs @param H2 Output rectification homography matrix for the second image. @param threshold Optional threshold used to filter out the outliers. If the parameter is greater than zero, all the point pairs that do not comply with the epipolar geometry (that is, the points -for which \f$|\texttt{points2[i]}^T*\texttt{F}*\texttt{points1[i]}|>\texttt{threshold}\f$ ) are -rejected prior to computing the homographies. Otherwise, all the points are considered inliers. +for which \f$|\texttt{points2[i]}^T \cdot \texttt{F} \cdot \texttt{points1[i]}|>\texttt{threshold}\f$ ) +are rejected prior to computing the homographies. Otherwise, all the points are considered inliers. The function computes the rectification transformations without knowing intrinsic parameters of the cameras and their relative position in the space, which explains the suffix "uncalibrated". Another diff --git a/modules/stitching/include/opencv2/stitching/detail/matchers.hpp b/modules/stitching/include/opencv2/stitching/detail/matchers.hpp index 1b7d7d6897..b0ad1847cf 100644 --- a/modules/stitching/include/opencv2/stitching/detail/matchers.hpp +++ b/modules/stitching/include/opencv2/stitching/detail/matchers.hpp @@ -138,7 +138,7 @@ public: @sa detail::MatchesInfo */ CV_WRAP_AS(apply2) void operator ()(const std::vector &features, CV_OUT std::vector &pairwise_matches, - const cv::UMat &mask = cv::UMat()); + const cv::UMat &mask = cv::UMat()) { match(features, pairwise_matches, mask); }; /** @return True, if it's possible to use the same matcher instance in parallel, false otherwise */ @@ -161,6 +161,16 @@ protected: virtual void match(const ImageFeatures &features1, const ImageFeatures &features2, MatchesInfo& matches_info) = 0; + /** @brief This method implements logic to match features between arbitrary number of features. + By default this checks every pair of inputs in the input, but the behaviour can be changed by subclasses. + + @param features vector of image features + @param pairwise_matches found matches + @param mask (optional) mask indicating which image pairs should be matched + */ + virtual void match(const std::vector &features, std::vector &pairwise_matches, + const cv::UMat &mask = cv::UMat()); + bool is_thread_safe_; }; @@ -180,19 +190,22 @@ public: estimation used in the inliers classification step @param num_matches_thresh2 Minimum number of matches required for the 2D projective transform re-estimation on inliers + @param matches_confindece_thresh Matching confidence threshold to take the match into account. + The threshold was determined experimentally and set to 3 by default. */ CV_WRAP BestOf2NearestMatcher(bool try_use_gpu = false, float match_conf = 0.3f, int num_matches_thresh1 = 6, - int num_matches_thresh2 = 6); + int num_matches_thresh2 = 6, double matches_confindece_thresh = 3.); CV_WRAP void collectGarbage() CV_OVERRIDE; CV_WRAP static Ptr create(bool try_use_gpu = false, float match_conf = 0.3f, int num_matches_thresh1 = 6, - int num_matches_thresh2 = 6); + int num_matches_thresh2 = 6, double matches_confindece_thresh = 3.); protected: void match(const ImageFeatures &features1, const ImageFeatures &features2, MatchesInfo &matches_info) CV_OVERRIDE; int num_matches_thresh1_; int num_matches_thresh2_; + double matches_confindece_thresh_; Ptr impl_; }; @@ -202,11 +215,12 @@ public: CV_WRAP BestOf2NearestRangeMatcher(int range_width = 5, bool try_use_gpu = false, float match_conf = 0.3f, int num_matches_thresh1 = 6, int num_matches_thresh2 = 6); - void operator ()(const std::vector &features, std::vector &pairwise_matches, - const cv::UMat &mask = cv::UMat()); - - protected: + // indicate that we do not want to hide the base class match method with a different signature + using BestOf2NearestMatcher::match; + void match(const std::vector &features, std::vector &pairwise_matches, + const cv::UMat &mask = cv::UMat()) CV_OVERRIDE; + int range_width_; }; diff --git a/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp b/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp index 71dae7fdff..9ccfd14424 100644 --- a/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp +++ b/modules/stitching/include/opencv2/stitching/detail/seam_finders.hpp @@ -248,7 +248,7 @@ public: ~GraphCutSeamFinder(); CV_WRAP void find(const std::vector &src, const std::vector &corners, - std::vector &masks) CV_OVERRIDE; + CV_IN_OUT std::vector &masks) CV_OVERRIDE; private: // To avoid GCGraph dependency diff --git a/modules/stitching/misc/python/test/test_stitching.py b/modules/stitching/misc/python/test/test_stitching.py index 2e7b2b5818..5f143d0013 100644 --- a/modules/stitching/misc/python/test/test_stitching.py +++ b/modules/stitching/misc/python/test/test_stitching.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import cv2 as cv +import numpy as np from tests_common import NewOpenCVTests @@ -134,6 +135,47 @@ class stitching_matches_info_test(NewOpenCVTests): self.assertIsNotNone(matches_info.matches) self.assertIsNotNone(matches_info.inliers_mask) +class stitching_range_matcher_test(NewOpenCVTests): + + def test_simple(self): + images = [ + self.get_sample('stitching/a1.png'), + self.get_sample('stitching/a2.png'), + self.get_sample('stitching/a3.png') + ] + + orb = cv.ORB_create() + + features = [cv.detail.computeImageFeatures2(orb, img) for img in images] + + matcher = cv.detail_BestOf2NearestRangeMatcher(range_width=1) + matches = matcher.apply2(features) + + # matches[1] is image 0 and image 1, should have non-zero confidence + self.assertNotEqual(matches[1].confidence, 0) + + # matches[2] is image 0 and image 2, should have zero confidence due to range_width=1 + self.assertEqual(matches[2].confidence, 0) + + +class stitching_seam_finder_graph_cuts(NewOpenCVTests): + + def test_simple(self): + images = [ + self.get_sample('stitching/a1.png'), + self.get_sample('stitching/a2.png'), + self.get_sample('stitching/a3.png') + ] + + images = [cv.resize(img, [100, 100]) for img in images] + + finder = cv.detail_GraphCutSeamFinder('COST_COLOR_GRAD') + masks = [cv.UMat(255 * np.ones((img.shape[0], img.shape[1]), np.uint8)) for img in images] + images_f = [img.astype(np.float32) for img in images] + masks_warped = finder.find(images_f, [(0, 0), (75, 0), (150, 0)], masks) + + self.assertIsNotNone(masks_warped) + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/stitching/src/matchers.cpp b/modules/stitching/src/matchers.cpp index 4791584366..5d5707661f 100644 --- a/modules/stitching/src/matchers.cpp +++ b/modules/stitching/src/matchers.cpp @@ -335,8 +335,8 @@ MatchesInfo& MatchesInfo::operator =(const MatchesInfo &other) ////////////////////////////////////////////////////////////////////////////// -void FeaturesMatcher::operator ()(const std::vector &features, std::vector &pairwise_matches, - const UMat &mask) +void FeaturesMatcher::match(const std::vector &features, std::vector &pairwise_matches, + const UMat &mask) { const int num_images = static_cast(features.size()); @@ -365,7 +365,7 @@ void FeaturesMatcher::operator ()(const std::vector &features, st ////////////////////////////////////////////////////////////////////////////// -BestOf2NearestMatcher::BestOf2NearestMatcher(bool try_use_gpu, float match_conf, int num_matches_thresh1, int num_matches_thresh2) +BestOf2NearestMatcher::BestOf2NearestMatcher(bool try_use_gpu, float match_conf, int num_matches_thresh1, int num_matches_thresh2, double matches_confindece_thresh) { CV_UNUSED(try_use_gpu); @@ -383,11 +383,13 @@ BestOf2NearestMatcher::BestOf2NearestMatcher(bool try_use_gpu, float match_conf, is_thread_safe_ = impl_->isThreadSafe(); num_matches_thresh1_ = num_matches_thresh1; num_matches_thresh2_ = num_matches_thresh2; + matches_confindece_thresh_ = matches_confindece_thresh; } -Ptr BestOf2NearestMatcher::create(bool try_use_gpu, float match_conf, int num_matches_thresh1, int num_matches_thresh2) +Ptr BestOf2NearestMatcher::create(bool try_use_gpu, float match_conf, int num_matches_thresh1, int num_matches_thresh2, + double matches_confindece_thresh) { - return makePtr(try_use_gpu, match_conf, num_matches_thresh1, num_matches_thresh2); + return makePtr(try_use_gpu, match_conf, num_matches_thresh1, num_matches_thresh2, matches_confindece_thresh); } @@ -437,8 +439,8 @@ void BestOf2NearestMatcher::match(const ImageFeatures &features1, const ImageFea matches_info.confidence = matches_info.num_inliers / (8 + 0.3 * matches_info.matches.size()); // Set zero confidence to remove matches between too close images, as they don't provide - // additional information anyway. The threshold was set experimentally. - matches_info.confidence = matches_info.confidence > 3. ? 0. : matches_info.confidence; + // additional information anyway. + matches_info.confidence = matches_info.confidence > matches_confindece_thresh_ ? 0. : matches_info.confidence; // Check if we should try to refine motion if (matches_info.num_inliers < num_matches_thresh2_) @@ -484,8 +486,8 @@ BestOf2NearestRangeMatcher::BestOf2NearestRangeMatcher(int range_width, bool try } -void BestOf2NearestRangeMatcher::operator ()(const std::vector &features, std::vector &pairwise_matches, - const UMat &mask) +void BestOf2NearestRangeMatcher::match(const std::vector &features, std::vector &pairwise_matches, + const UMat &mask) { const int num_images = static_cast(features.size()); diff --git a/modules/stitching/test/test_matchers.cpp b/modules/stitching/test/test_matchers.cpp index 08c5aa56db..2deb676fb3 100644 --- a/modules/stitching/test/test_matchers.cpp +++ b/modules/stitching/test/test_matchers.cpp @@ -114,4 +114,30 @@ TEST(ParallelFeaturesFinder, IsSameWithSerial) } } +TEST(RangeMatcher, MatchesRangeOnly) +{ + Ptr finder = ORB::create(); + + Mat img0 = imread(string(cvtest::TS::ptr()->get_data_path()) + "stitching/a1.png", IMREAD_GRAYSCALE); + Mat img1 = imread(string(cvtest::TS::ptr()->get_data_path()) + "stitching/a2.png", IMREAD_GRAYSCALE); + Mat img2 = imread(string(cvtest::TS::ptr()->get_data_path()) + "stitching/a3.png", IMREAD_GRAYSCALE); + + vector features(3); + + computeImageFeatures(finder, img0, features[0]); + computeImageFeatures(finder, img1, features[1]); + computeImageFeatures(finder, img2, features[2]); + + vector pairwise_matches; + Ptr matcher = makePtr(1); + + (*matcher)(features, pairwise_matches); + + // matches[1] will be image 0 and image 1, should have non-zero confidence + EXPECT_NE(pairwise_matches[1].confidence, .0); + + // matches[2] will be image 0 and image 2, should have zero confidence due to range_width=1 + EXPECT_DOUBLE_EQ(pairwise_matches[2].confidence, .0); +} + }} // namespace diff --git a/modules/ts/misc/chart.py b/modules/ts/misc/chart.py index 6ae726abeb..e2b79ed78e 100755 --- a/modules/ts/misc/chart.py +++ b/modules/ts/misc/chart.py @@ -1,4 +1,46 @@ #!/usr/bin/env python +""" OpenCV performance test results charts generator. + +This script formats results of a performance test as a table or a series of tables according to test +parameters. + +### Description + +Performance data is stored in the GTest log file created by performance tests. Default name is +`test_details.xml`. It can be changed with the `--gtest_output=xml:/.xml` test +option. See https://github.com/opencv/opencv/wiki/HowToUsePerfTests for more details. + +Script accepts an XML with performance test results as an input. Only one test (aka testsuite) +containing multiple cases (aka testcase) with different parameters can be used. Test should have 2 +or more parameters, for example resolution (640x480), data type (8UC1), mode (NORM_TYPE), etc. +Parameters #2 and #1 will be used as table row and column by default, this mapping can be changed +with `-x` and `-y` options. Parameter combination besides the two selected for row and column will +be represented as a separate table. I.e. one table (RES x TYPE) for `NORM_L1`, another for +`NORM_L2`, etc. + +Test can be selected either by using `--gtest_filter` option when running the test, or by using the +`--filter` script option. + +### Options: + +-f REGEX, --filter=REGEX - regular expression used to select a test +-x ROW, -y COL - choose different parameters for rows and columns +-u UNITS, --units=UNITS - units for output values (s, ms (default), us, ns or ticks) +-m NAME, --metric=NAME - output metric (mean, median, stddev, etc.) +-o FMT, --output=FMT - output format ('txt', 'html' or 'auto') + +### Example: + +./chart.py -f sum opencv_perf_core.xml + +Geometric mean for +sum::Size_MatType::(Y, X) + + X\Y 127x61 640x480 1280x720 1920x1080 +8UC1 0.03 ms 1.21 ms 3.61 ms 8.11 ms +8UC4 0.10 ms 3.56 ms 10.67 ms 23.90 ms +32FC1 0.05 ms 1.77 ms 5.23 ms 11.72 ms +""" from __future__ import print_function import testlog_parser, sys, os, xml, re @@ -180,7 +222,7 @@ if __name__ == "__main__": exit(1) for i in range(argsnum): - arglists[i] = sorted([str(key) for key in arglists[i].iterkeys()], key=alphanum_keyselector) + arglists[i] = sorted([str(key) for key in arglists[i].keys()], key=alphanum_keyselector) if options.generateHtml and options.format != "moinwiki": htmlPrintHeader(sys.stdout, "Report %s for %s" % (args[0], sname)) diff --git a/modules/ts/misc/color.py b/modules/ts/misc/color.py old mode 100755 new mode 100644 index 4492ed4798..41e799892d --- a/modules/ts/misc/color.py +++ b/modules/ts/misc/color.py @@ -1,5 +1,6 @@ #!/usr/bin/env python - +""" Utility package used by other test result formatting scripts. +""" import math, os, sys webcolors = { diff --git a/modules/ts/misc/concatlogs.py b/modules/ts/misc/concatlogs.py index afcb9cc89f..ce717ef1eb 100755 --- a/modules/ts/misc/concatlogs.py +++ b/modules/ts/misc/concatlogs.py @@ -1,4 +1,9 @@ #!/usr/bin/env python +""" Combines multiple uniform HTML documents with tables into a single one. + +HTML header from the first document will be used in the output document. Largest +`...` part from each document will be joined together. +""" from optparse import OptionParser import glob, sys, os, re diff --git a/modules/ts/misc/perf_tests_timing.py b/modules/ts/misc/perf_tests_timing.py old mode 100644 new mode 100755 index e3e94f4ce5..af26572969 --- a/modules/ts/misc/perf_tests_timing.py +++ b/modules/ts/misc/perf_tests_timing.py @@ -1,4 +1,26 @@ #!/usr/bin/env python +""" Prints total execution time and number of total/failed tests. + +Performance data is stored in the GTest log file created by performance tests. Default name is +`test_details.xml`. It can be changed with the `--gtest_output=xml:/.xml` test +option. See https://github.com/opencv/opencv/wiki/HowToUsePerfTests for more details. + +This script uses XML test log to produce basic runtime statistics in a text or HTML table. + +### Example: + +./perf_tests_timing.py opencv_perf_core.xml + +Overall time: 222.71 min + +Module Testsuit Time (min) Num of tests Failed +opencv Gemm::OCL_GemmFixture 113.669 24 +opencv dft::Size_MatType_FlagsType_NzeroRows 21.127 180 +opencv Dft::OCL_DftFixture 11.153 144 +opencv convertTo::Size_DepthSrc_DepthDst_Channels_alpha 7.992 392 +opencv Normalize::OCL_NormalizeFixture 5.412 96 +... ... ... ... +""" from __future__ import print_function import testlog_parser, sys, os, xml, glob, re @@ -78,7 +100,7 @@ if __name__ == "__main__": suit_time = 0 suit_num = 0 fails_num = 0 - for name in sorted(test_cases.iterkeys(), key=alphanum_keyselector): + for name in sorted(test_cases.keys(), key=alphanum_keyselector): cases = test_cases[name] groupName = next(c for c in cases if c).shortName() diff --git a/modules/ts/misc/report.py b/modules/ts/misc/report.py index ec60e16a13..0300d4426d 100755 --- a/modules/ts/misc/report.py +++ b/modules/ts/misc/report.py @@ -1,4 +1,38 @@ #!/usr/bin/env python +""" Print performance test run statistics. + +Performance data is stored in the GTest log file created by performance tests. Default name is +`test_details.xml`. It can be changed with the `--gtest_output=xml:/.xml` test +option. See https://github.com/opencv/opencv/wiki/HowToUsePerfTests for more details. + +This script produces configurable performance report tables in text and HTML formats. It allows to +filter test cases by name and parameter string and select specific performance metrics columns. One +or multiple test results can be used for input. + +### Example + +./report.py -c min,mean,median -f '(LUT|Match).*640' opencv_perf_core.xml opencv_perf_features2d.xml + +opencv_perf_features2d.xml, opencv_perf_core.xml + + Name of Test Min Mean Median +KnnMatch::OCL_BruteForceMatcherFixture::(640x480, 32FC1) 1365.04 ms 1368.18 ms 1368.52 ms +LUT::OCL_LUTFixture::(640x480, 32FC1) 2.57 ms 2.62 ms 2.64 ms +LUT::OCL_LUTFixture::(640x480, 32FC4) 21.15 ms 21.25 ms 21.24 ms +LUT::OCL_LUTFixture::(640x480, 8UC1) 2.22 ms 2.28 ms 2.29 ms +LUT::OCL_LUTFixture::(640x480, 8UC4) 19.12 ms 19.24 ms 19.19 ms +LUT::SizePrm::640x480 2.22 ms 2.27 ms 2.29 ms +Match::OCL_BruteForceMatcherFixture::(640x480, 32FC1) 1364.15 ms 1367.73 ms 1365.45 ms +RadiusMatch::OCL_BruteForceMatcherFixture::(640x480, 32FC1) 1372.68 ms 1375.52 ms 1375.42 ms + +### Options + +-o FMT, --output=FMT - output results in text format (can be 'txt', 'html' or 'auto' - default) +-u UNITS, --units=UNITS - units for output values (s, ms (default), us, ns or ticks) +-c COLS, --columns=COLS - comma-separated list of columns to show +-f REGEX, --filter=REGEX - regex to filter tests +--show-all - also include empty and "notrun" lines +""" from __future__ import print_function import testlog_parser, sys, os, xml, re, glob diff --git a/modules/ts/misc/run.py b/modules/ts/misc/run.py index bb17aa884f..b03553feeb 100755 --- a/modules/ts/misc/run.py +++ b/modules/ts/misc/run.py @@ -1,4 +1,41 @@ #!/usr/bin/env python +""" Test runner and results collector for OpenCV + +This script abstracts execution procedure for OpenCV tests. Target scenario: running automated tests +in a continuous integration system. +See https://github.com/opencv/opencv/wiki/HowToUsePerfTests for more details. + +### Main features + +- Collect test executables, distinguish between accuracy and performance, main and contrib test sets +- Pass through common GTest and OpenCV test options and handle some of them internally +- Set up testing environment and handle some OpenCV-specific environment variables +- Test Java and Python bindings +- Test on remote android device +- Support valgrind, qemu wrapping and trace collection + +### Main options + +-t MODULES, --tests MODULES - Comma-separated list of modules to test (example: -t core,imgproc,java) +-b MODULES, --blacklist MODULES - Comma-separated list of modules to exclude from test (example: -b java) +-a, --accuracy - Look for accuracy tests instead of performance tests +--check - Shortcut for '--perf_min_samples=1 --perf_force_samples=1' +-w PATH, --cwd PATH - Working directory for tests (default is current) +-n, --dry_run - Do not run anything +-v, --verbose - Print more debug information + +### Example + +./run.py -a -t core --gtest_filter=*CopyTo* + +Run: /work/build-opencv/bin/opencv_test_core --gtest_filter=*CopyTo* --gtest_output=xml:core_20221017-195300.xml --gtest_color=yes +CTEST_FULL_OUTPUT +... +regular test output +... +[ PASSED ] 113 tests. +Collected: ['core_20221017-195300.xml'] +""" import os import argparse diff --git a/modules/ts/misc/run_android.py b/modules/ts/misc/run_android.py index 4aa2b0dd78..498819cb30 100644 --- a/modules/ts/misc/run_android.py +++ b/modules/ts/misc/run_android.py @@ -1,4 +1,7 @@ #!/usr/bin/env python +""" Utility package for run.py +""" + import os import re import getpass diff --git a/modules/ts/misc/run_long.py b/modules/ts/misc/run_long.py index e49b5df743..d7cfa96bf5 100755 --- a/modules/ts/misc/run_long.py +++ b/modules/ts/misc/run_long.py @@ -1,4 +1,7 @@ #!/usr/bin/env python +""" Utility package for run.py +""" + from __future__ import print_function import xml.etree.ElementTree as ET from glob import glob diff --git a/modules/ts/misc/run_suite.py b/modules/ts/misc/run_suite.py index 2f382238cd..f1812cdf2a 100644 --- a/modules/ts/misc/run_suite.py +++ b/modules/ts/misc/run_suite.py @@ -1,4 +1,7 @@ #!/usr/bin/env python +""" Utility package for run.py +""" + import os import re import sys diff --git a/modules/ts/misc/run_utils.py b/modules/ts/misc/run_utils.py index 8c942ba9d1..aadc2b4eae 100644 --- a/modules/ts/misc/run_utils.py +++ b/modules/ts/misc/run_utils.py @@ -1,4 +1,7 @@ #!/usr/bin/env python +""" Utility package for run.py +""" + import sys import os import platform diff --git a/modules/ts/misc/summary.py b/modules/ts/misc/summary.py index bfb98358d2..5874d37e72 100755 --- a/modules/ts/misc/summary.py +++ b/modules/ts/misc/summary.py @@ -1,4 +1,36 @@ #!/usr/bin/env python +""" Format performance test results and compare metrics between test runs + +Performance data is stored in the GTest log file created by performance tests. Default name is +`test_details.xml`. It can be changed with the `--gtest_output=xml:/.xml` test +option. See https://github.com/opencv/opencv/wiki/HowToUsePerfTests for more details. + +This script allows to compare performance data collected during separate test runs and present it in +a text, Markdown or HTML table. + +### Major options + +-o FMT, --output=FMT - output format ('txt', 'html', 'markdown', 'tabs' or 'auto') +-f REGEX, --filter=REGEX - regex to filter tests +-m NAME, --metric=NAME - output metric +-u UNITS, --units=UNITS - units for output values (s, ms (default), us, ns or ticks) + +### Example + +./summary.py -f LUT.*640 core1.xml core2.xml + +Geometric mean (ms) + + Name of Test core1 core2 core2 + vs + core1 + (x-factor) +LUT::OCL_LUTFixture::(640x480, 8UC1) 2.278 0.737 3.09 +LUT::OCL_LUTFixture::(640x480, 32FC1) 2.622 0.805 3.26 +LUT::OCL_LUTFixture::(640x480, 8UC4) 19.243 3.624 5.31 +LUT::OCL_LUTFixture::(640x480, 32FC4) 21.254 4.296 4.95 +LUT::SizePrm::640x480 2.268 0.687 3.30 +""" from __future__ import print_function import testlog_parser, sys, os, xml, glob, re diff --git a/modules/ts/misc/table_formatter.py b/modules/ts/misc/table_formatter.py old mode 100755 new mode 100644 index 96bafab72d..f88681b200 --- a/modules/ts/misc/table_formatter.py +++ b/modules/ts/misc/table_formatter.py @@ -1,4 +1,8 @@ #!/usr/bin/env python +""" Prints data in a table format. + +This module serves as utility for other scripts. +""" from __future__ import print_function import sys, re, os.path, stat, math diff --git a/modules/ts/misc/testlog_parser.py b/modules/ts/misc/testlog_parser.py old mode 100755 new mode 100644 index f52a051c8f..a148b31116 --- a/modules/ts/misc/testlog_parser.py +++ b/modules/ts/misc/testlog_parser.py @@ -1,5 +1,8 @@ #!/usr/bin/env python +""" Parse XML test log file. +This module serves as utility for other scripts. +""" from __future__ import print_function import collections import re diff --git a/modules/ts/misc/trace_profiler.py b/modules/ts/misc/trace_profiler.py old mode 100644 new mode 100755 index 189bed2743..1e4d52e409 --- a/modules/ts/misc/trace_profiler.py +++ b/modules/ts/misc/trace_profiler.py @@ -1,3 +1,31 @@ +#!/usr/bin/env python +""" Parse OpenCV trace logs and present summarized statistics in a table + +To collect trace logs use OpenCV built with tracing support (enabled by default), set +`OPENCV_TRACE=1` environment variable and run your application. `OpenCVTrace.txt` file will be +created in the current folder. +See https://github.com/opencv/opencv/wiki/Profiling-OpenCV-Applications for more details. + +### Options + +./trace_profiler.py + + - usually OpenCVTrace.txt + - number of functions to show (depth) + +### Example + +./trace_profiler.py OpenCVTrace.txt 2 + + ID name count thr min ... + t-min ... + 1 main#test_main.cpp:6 1 1 88.484 ... + 200.210 ... + + 2 UMatBasicTests_copyTo#test_umat.cpp:176|main 40 1 0.125 ... + 0.173 ... +""" + from __future__ import print_function import os diff --git a/modules/video/include/opencv2/video/tracking.hpp b/modules/video/include/opencv2/video/tracking.hpp index 7ec6bc55cd..eb5a6c7030 100644 --- a/modules/video/include/opencv2/video/tracking.hpp +++ b/modules/video/include/opencv2/video/tracking.hpp @@ -849,6 +849,43 @@ public: //bool update(InputArray image, CV_OUT Rect& boundingBox) CV_OVERRIDE; }; +/** @brief the Nano tracker is a super lightweight dnn-based general object tracking. + * + * Nano tracker is much faster and extremely lightweight due to special model structure, the whole model size is about 1.9 MB. + * Nano tracker needs two models: one for feature extraction (backbone) and the another for localization (neckhead). + * Model download link: https://github.com/HonglinChu/SiamTrackers/tree/master/NanoTrack/models/nanotrackv2 + * Original repo is here: https://github.com/HonglinChu/NanoTrack + * Author: HongLinChu, 1628464345@qq.com + */ +class CV_EXPORTS_W TrackerNano : public Tracker +{ +protected: + TrackerNano(); // use ::create() +public: + virtual ~TrackerNano() CV_OVERRIDE; + + struct CV_EXPORTS_W_SIMPLE Params + { + CV_WRAP Params(); + CV_PROP_RW std::string backbone; + CV_PROP_RW std::string neckhead; + CV_PROP_RW int backend; + CV_PROP_RW int target; + }; + + /** @brief Constructor + @param parameters NanoTrack parameters TrackerNano::Params + */ + static CV_WRAP + Ptr create(const TrackerNano::Params& parameters = TrackerNano::Params()); + + /** @brief Return tracking score + */ + CV_WRAP virtual float getTrackingScore() = 0; + + //void init(InputArray image, const Rect& boundingBox) CV_OVERRIDE; + //bool update(InputArray image, CV_OUT Rect& boundingBox) CV_OVERRIDE; +}; //! @} video_track diff --git a/modules/video/misc/python/pyopencv_video.hpp b/modules/video/misc/python/pyopencv_video.hpp index ea8977911f..02d890d859 100644 --- a/modules/video/misc/python/pyopencv_video.hpp +++ b/modules/video/misc/python/pyopencv_video.hpp @@ -2,4 +2,5 @@ typedef TrackerMIL::Params TrackerMIL_Params; typedef TrackerGOTURN::Params TrackerGOTURN_Params; typedef TrackerDaSiamRPN::Params TrackerDaSiamRPN_Params; +typedef TrackerNano::Params TrackerNano_Params; #endif diff --git a/modules/video/src/bgfg_gaussmix2.cpp b/modules/video/src/bgfg_gaussmix2.cpp index f7b26ef06b..00a65fcf8f 100644 --- a/modules/video/src/bgfg_gaussmix2.cpp +++ b/modules/video/src/bgfg_gaussmix2.cpp @@ -47,7 +47,7 @@ //International Conference Pattern Recognition, UK, August, 2004 //http://www.zoranz.net/Publications/zivkovic2004ICPR.pdf //The code is very fast and performs also shadow detection. -//Number of Gausssian components is adapted per pixel. +//Number of Gaussian components is adapted per pixel. // // and // @@ -97,7 +97,7 @@ namespace cv http://www.zoranz.net/Publications/zivkovic2004ICPR.pdf Advantages: - -fast - number of Gausssian components is constantly adapted per pixel. + -fast - number of Gaussian components is constantly adapted per pixel. -performs also shadow detection (see bgfg_segm_test.cpp example) */ diff --git a/modules/video/src/tracking/tracker_nano.cpp b/modules/video/src/tracking/tracker_nano.cpp new file mode 100644 index 0000000000..dadad1ac55 --- /dev/null +++ b/modules/video/src/tracking/tracker_nano.cpp @@ -0,0 +1,359 @@ +// 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. + +// This file is modified from the https://github.com/HonglinChu/NanoTrack/blob/master/ncnn_macos_nanotrack/nanotrack.cpp +// Author, HongLinChu, 1628464345@qq.com +// Adapt to OpenCV, ZihaoMu: zihaomu@outlook.com + +// Link to original inference code: https://github.com/HonglinChu/NanoTrack +// Link to original training repo: https://github.com/HonglinChu/SiamTrackers/tree/master/NanoTrack + +#include "../precomp.hpp" +#ifdef HAVE_OPENCV_DNN +#include "opencv2/dnn.hpp" +#endif + +namespace cv { + +TrackerNano::TrackerNano() +{ + // nothing +} + +TrackerNano::~TrackerNano() +{ + // nothing +} + +TrackerNano::Params::Params() +{ + backbone = "backbone.onnx"; + neckhead = "neckhead.onnx"; +#ifdef HAVE_OPENCV_DNN + backend = dnn::DNN_BACKEND_DEFAULT; + target = dnn::DNN_TARGET_CPU; +#else + backend = -1; // invalid value + target = -1; // invalid value +#endif +} + +#ifdef HAVE_OPENCV_DNN +static void softmax(const Mat& src, Mat& dst) +{ + Mat maxVal; + cv::max(src.row(1), src.row(0), maxVal); + + src.row(1) -= maxVal; + src.row(0) -= maxVal; + + exp(src, dst); + + Mat sumVal = dst.row(0) + dst.row(1); + dst.row(0) = dst.row(0) / sumVal; + dst.row(1) = dst.row(1) / sumVal; +} + +static float sizeCal(float w, float h) +{ + float pad = (w + h) * 0.5f; + float sz2 = (w + pad) * (h + pad); + return sqrt(sz2); +} + +static Mat sizeCal(const Mat& w, const Mat& h) +{ + Mat pad = (w + h) * 0.5; + Mat sz2 = (w + pad).mul((h + pad)); + + cv::sqrt(sz2, sz2); + return sz2; +} + +// Similar python code: r = np.maximum(r, 1. / r) # r is matrix +static void elementReciprocalMax(Mat& srcDst) +{ + size_t totalV = srcDst.total(); + float* ptr = srcDst.ptr(0); + for (size_t i = 0; i < totalV; i++) + { + float val = *(ptr + i); + *(ptr + i) = std::max(val, 1.0f/val); + } +} + +class TrackerNanoImpl : public TrackerNano +{ +public: + TrackerNanoImpl(const TrackerNano::Params& parameters) + : params(parameters) + { + backbone = dnn::readNet(params.backbone); + neckhead = dnn::readNet(params.neckhead); + + CV_Assert(!backbone.empty()); + CV_Assert(!neckhead.empty()); + + backbone.setPreferableBackend(params.backend); + backbone.setPreferableTarget(params.target); + neckhead.setPreferableBackend(params.backend); + neckhead.setPreferableTarget(params.target); + } + + void init(InputArray image, const Rect& boundingBox) CV_OVERRIDE; + bool update(InputArray image, Rect& boundingBox) CV_OVERRIDE; + float getTrackingScore() CV_OVERRIDE; + + // Save the target bounding box for each frame. + std::vector targetSz = {0, 0}; // H and W of bounding box + std::vector targetPos = {0, 0}; // center point of bounding box (x, y) + float tracking_score; + + TrackerNano::Params params; + + struct trackerConfig + { + float windowInfluence = 0.455f; + float lr = 0.37f; + float contextAmount = 0.5; + bool swapRB = true; + int totalStride = 16; + float penaltyK = 0.055f; + }; + +protected: + const int exemplarSize = 127; + const int instanceSize = 255; + + trackerConfig trackState; + int scoreSize; + Size imgSize = {0, 0}; + Mat hanningWindow; + Mat grid2searchX, grid2searchY; + + dnn::Net backbone, neckhead; + Mat image; + + void getSubwindow(Mat& dstCrop, Mat& srcImg, int originalSz, int resizeSz); + void generateGrids(); +}; + +void TrackerNanoImpl::generateGrids() +{ + int sz = scoreSize; + const int sz2 = sz / 2; + + std::vector x1Vec(sz, 0); + + for (int i = 0; i < sz; i++) + { + x1Vec[i] = (float)(i - sz2); + } + + Mat x1M(1, sz, CV_32FC1, x1Vec.data()); + + cv::repeat(x1M, sz, 1, grid2searchX); + cv::repeat(x1M.t(), 1, sz, grid2searchY); + + grid2searchX *= trackState.totalStride; + grid2searchY *= trackState.totalStride; + + grid2searchX += instanceSize/2; + grid2searchY += instanceSize/2; +} + +void TrackerNanoImpl::init(InputArray image_, const Rect &boundingBox_) +{ + scoreSize = (instanceSize - exemplarSize) / trackState.totalStride + 8; + trackState = trackerConfig(); + image = image_.getMat().clone(); + + // convert Rect2d from left-up to center. + targetPos[0] = float(boundingBox_.x) + float(boundingBox_.width) * 0.5f; + targetPos[1] = float(boundingBox_.y) + float(boundingBox_.height) * 0.5f; + + targetSz[0] = float(boundingBox_.width); + targetSz[1] = float(boundingBox_.height); + + imgSize = image.size(); + + // Extent the bounding box. + float sumSz = targetSz[0] + targetSz[1]; + float wExtent = targetSz[0] + trackState.contextAmount * (sumSz); + float hExtent = targetSz[1] + trackState.contextAmount * (sumSz); + int sz = int(cv::sqrt(wExtent * hExtent)); + + Mat crop; + getSubwindow(crop, image, sz, exemplarSize); + Mat blob = dnn::blobFromImage(crop, 1.0, Size(), Scalar(), trackState.swapRB); + + backbone.setInput(blob); + Mat out = backbone.forward(); // Feature extraction. + neckhead.setInput(out, "input1"); + + createHanningWindow(hanningWindow, Size(scoreSize, scoreSize), CV_32F); + generateGrids(); +} + +void TrackerNanoImpl::getSubwindow(Mat& dstCrop, Mat& srcImg, int originalSz, int resizeSz) +{ + Scalar avgChans = mean(srcImg); + Size imgSz = srcImg.size(); + int c = (originalSz + 1) / 2; + + int context_xmin = (int)(targetPos[0]) - c; + int context_xmax = context_xmin + originalSz - 1; + int context_ymin = (int)(targetPos[1]) - c; + int context_ymax = context_ymin + originalSz - 1; + + int left_pad = std::max(0, -context_xmin); + int top_pad = std::max(0, -context_ymin); + int right_pad = std::max(0, context_xmax - imgSz.width + 1); + int bottom_pad = std::max(0, context_ymax - imgSz.height + 1); + + context_xmin += left_pad; + context_xmax += left_pad; + context_ymin += top_pad; + context_ymax += top_pad; + + Mat cropImg; + if (left_pad == 0 && top_pad == 0 && right_pad == 0 && bottom_pad == 0) + { + // Crop image without padding. + cropImg = srcImg(cv::Rect(context_xmin, context_ymin, + context_xmax - context_xmin + 1, context_ymax - context_ymin + 1)); + } + else // Crop image with padding, and the padding value is avgChans + { + cv::Mat tmpMat; + cv::copyMakeBorder(srcImg, tmpMat, top_pad, bottom_pad, left_pad, right_pad, cv::BORDER_CONSTANT, avgChans); + cropImg = tmpMat(cv::Rect(context_xmin, context_ymin, context_xmax - context_xmin + 1, context_ymax - context_ymin + 1)); + } + resize(cropImg, dstCrop, Size(resizeSz, resizeSz)); +} + +bool TrackerNanoImpl::update(InputArray image_, Rect &boundingBoxRes) +{ + image = image_.getMat().clone(); + int targetSzSum = (int)(targetSz[0] + targetSz[1]); + + float wc = targetSz[0] + trackState.contextAmount * targetSzSum; + float hc = targetSz[1] + trackState.contextAmount * targetSzSum; + float sz = cv::sqrt(wc * hc); + float scale_z = exemplarSize / sz; + float sx = sz * (instanceSize / exemplarSize); + targetSz[0] *= scale_z; + targetSz[1] *= scale_z; + + Mat crop; + getSubwindow(crop, image, int(sx), instanceSize); + + Mat blob = dnn::blobFromImage(crop, 1.0, Size(), Scalar(), trackState.swapRB); + backbone.setInput(blob); + Mat xf = backbone.forward(); + neckhead.setInput(xf, "input2"); + std::vector outputName = {"output1", "output2"}; + std::vector outs; + neckhead.forward(outs, outputName); + + CV_Assert(outs.size() == 2); + + Mat clsScore = outs[0]; // 1x2x16x16 + Mat bboxPred = outs[1]; // 1x4x16x16 + + clsScore = clsScore.reshape(0, {2, scoreSize, scoreSize}); + bboxPred = bboxPred.reshape(0, {4, scoreSize, scoreSize}); + + Mat scoreSoftmax; // 2x16x16 + softmax(clsScore, scoreSoftmax); + + Mat score = scoreSoftmax.row(1); + score = score.reshape(0, {scoreSize, scoreSize}); + + Mat predX1 = grid2searchX - bboxPred.row(0).reshape(0, {scoreSize, scoreSize}); + Mat predY1 = grid2searchY - bboxPred.row(1).reshape(0, {scoreSize, scoreSize}); + Mat predX2 = grid2searchX + bboxPred.row(2).reshape(0, {scoreSize, scoreSize}); + Mat predY2 = grid2searchY + bboxPred.row(3).reshape(0, {scoreSize, scoreSize}); + + // size penalty + // scale penalty + Mat sc = sizeCal(predX2 - predX1, predY2 - predY1)/sizeCal(targetPos[0], targetPos[1]); + elementReciprocalMax(sc); + + // ratio penalty + float ratioVal = targetSz[0] / targetSz[1]; + + Mat ratioM(scoreSize, scoreSize, CV_32FC1, Scalar::all(ratioVal)); + Mat rc = ratioM / ((predX2 - predX1) / (predY2 - predY1)); + elementReciprocalMax(rc); + + Mat penalty; + exp(((rc.mul(sc) - 1) * trackState.penaltyK * (-1)), penalty); + Mat pscore = penalty.mul(score); + + // Window penalty + pscore = pscore * (1.0 - trackState.windowInfluence) + hanningWindow * trackState.windowInfluence; + + // get Max + int bestID[2] = { 0, 0 }; + minMaxIdx(pscore, 0, 0, 0, bestID); + + tracking_score = pscore.at(bestID); + + float x1Val = predX1.at(bestID); + float x2Val = predX2.at(bestID); + float y1Val = predY1.at(bestID); + float y2Val = predY2.at(bestID); + + float predXs = (x1Val + x2Val)/2; + float predYs = (y1Val + y2Val)/2; + float predW = (x2Val - x1Val)/scale_z; + float predH = (y2Val - y1Val)/scale_z; + + float diffXs = (predXs - instanceSize / 2) / scale_z; + float diffYs = (predYs - instanceSize / 2) / scale_z; + + targetSz[0] /= scale_z; + targetSz[1] /= scale_z; + + float lr = penalty.at(bestID) * score.at(bestID) * trackState.lr; + + float resX = targetPos[0] + diffXs; + float resY = targetPos[1] + diffYs; + float resW = predW * lr + (1 - lr) * targetSz[0]; + float resH = predH * lr + (1 - lr) * targetSz[1]; + + resX = std::max(0.f, std::min((float)imgSize.width, resX)); + resY = std::max(0.f, std::min((float)imgSize.height, resY)); + resW = std::max(10.f, std::min((float)imgSize.width, resW)); + resH = std::max(10.f, std::min((float)imgSize.height, resH)); + + targetPos[0] = resX; + targetPos[1] = resY; + targetSz[0] = resW; + targetSz[1] = resH; + + // convert center to Rect. + boundingBoxRes = { int(resX - resW/2), int(resY - resH/2), int(resW), int(resH)}; + return true; +} + +float TrackerNanoImpl::getTrackingScore() +{ + return tracking_score; +} + +Ptr TrackerNano::create(const TrackerNano::Params& parameters) +{ + return makePtr(parameters); +} + +#else // OPENCV_HAVE_DNN +Ptr TrackerNano::create(const TrackerNano::Params& parameters) +{ + CV_UNUSED(parameters); + CV_Error(cv::Error::StsNotImplemented, "to use NanoTrack, the tracking module needs to be built with opencv_dnn !"); +} +#endif // OPENCV_HAVE_DNN +} diff --git a/modules/video/test/test_trackers.cpp b/modules/video/test/test_trackers.cpp index 2d0e184408..6ede40896c 100644 --- a/modules/video/test/test_trackers.cpp +++ b/modules/video/test/test_trackers.cpp @@ -64,40 +64,67 @@ TEST_P(DistanceAndOverlap, GOTURN) INSTANTIATE_TEST_CASE_P(Tracking, DistanceAndOverlap, TESTSET_NAMES); -TEST(GOTURN, memory_usage) +static bool checkIOU(const Rect& r0, const Rect& r1, double threshold) { - cv::Rect roi(145, 70, 85, 85); + int interArea = (r0 & r1).area(); + double iouVal = (interArea * 1.0 )/ (r0.area() + r1.area() - interArea);; + if (iouVal > threshold) + return true; + else + { + std::cout <<"Unmatched IOU: expect IOU val ("< the IOU threadhold ("<& tracker, double iouThreshold = 0.7) +{ + // Template image + Mat img0 = imread(findDataFile("tracking/bag/00000001.jpg"), 1); + + // Tracking image sequence. + std::vector imgs; + imgs.push_back(imread(findDataFile("tracking/bag/00000002.jpg"), 1)); + imgs.push_back(imread(findDataFile("tracking/bag/00000003.jpg"), 1)); + imgs.push_back(imread(findDataFile("tracking/bag/00000004.jpg"), 1)); + imgs.push_back(imread(findDataFile("tracking/bag/00000005.jpg"), 1)); + imgs.push_back(imread(findDataFile("tracking/bag/00000006.jpg"), 1)); + + cv::Rect roi(325, 164, 100, 100); + std::vector targetRois; + targetRois.push_back(cv::Rect(278, 133, 99, 104)); + targetRois.push_back(cv::Rect(293, 88, 93, 110)); + targetRois.push_back(cv::Rect(287, 76, 89, 116)); + targetRois.push_back(cv::Rect(297, 74, 82, 122)); + targetRois.push_back(cv::Rect(311, 83, 78, 125)); + + tracker->init(img0, roi); + CV_Assert(targetRois.size() == imgs.size()); + + for (int i = 0; i < (int)imgs.size(); i++) + { + bool res = tracker->update(imgs[i], roi); + ASSERT_TRUE(res); + ASSERT_TRUE(checkIOU(roi, targetRois[i], iouThreshold)) << cv::format("Fail at img %d.",i); + } +} + +TEST(GOTURN, accuracy) +{ std::string model = cvtest::findDataFile("dnn/gsoc2016-goturn/goturn.prototxt"); std::string weights = cvtest::findDataFile("dnn/gsoc2016-goturn/goturn.caffemodel", false); cv::TrackerGOTURN::Params params; params.modelTxt = model; params.modelBin = weights; cv::Ptr tracker = TrackerGOTURN::create(params); - - string inputVideo = cvtest::findDataFile("tracking/david/data/david.webm"); - cv::VideoCapture video(inputVideo); - ASSERT_TRUE(video.isOpened()) << inputVideo; - - cv::Mat frame; - video >> frame; - ASSERT_FALSE(frame.empty()) << inputVideo; - tracker->init(frame, roi); - string ground_truth_bb; - for (int nframes = 0; nframes < 15; ++nframes) - { - std::cout << "Frame: " << nframes << std::endl; - video >> frame; - bool res = tracker->update(frame, roi); - ASSERT_TRUE(res); - std::cout << "Predicted ROI: " << roi << std::endl; - } + // TODO! GOTURN have low accuracy. Try to remove this api at 5.x. + checkTrackingAccuracy(tracker, 0.08); } -TEST(DaSiamRPN, memory_usage) +TEST(DaSiamRPN, accuracy) { - cv::Rect roi(145, 70, 85, 85); - std::string model = cvtest::findDataFile("dnn/onnx/models/dasiamrpn_model.onnx", false); std::string kernel_r1 = cvtest::findDataFile("dnn/onnx/models/dasiamrpn_kernel_r1.onnx", false); std::string kernel_cls1 = cvtest::findDataFile("dnn/onnx/models/dasiamrpn_kernel_cls1.onnx", false); @@ -106,24 +133,31 @@ TEST(DaSiamRPN, memory_usage) params.kernel_r1 = kernel_r1; params.kernel_cls1 = kernel_cls1; cv::Ptr tracker = TrackerDaSiamRPN::create(params); + checkTrackingAccuracy(tracker, 0.7); +} - string inputVideo = cvtest::findDataFile("tracking/david/data/david.webm"); - cv::VideoCapture video(inputVideo); - ASSERT_TRUE(video.isOpened()) << inputVideo; +TEST(NanoTrack, accuracy_NanoTrack_V1) +{ + std::string backbonePath = cvtest::findDataFile("dnn/onnx/models/nanotrack_backbone_sim.onnx", false); + std::string neckheadPath = cvtest::findDataFile("dnn/onnx/models/nanotrack_head_sim.onnx", false); - cv::Mat frame; - video >> frame; - ASSERT_FALSE(frame.empty()) << inputVideo; - tracker->init(frame, roi); - string ground_truth_bb; - for (int nframes = 0; nframes < 15; ++nframes) - { - std::cout << "Frame: " << nframes << std::endl; - video >> frame; - bool res = tracker->update(frame, roi); - ASSERT_TRUE(res); - std::cout << "Predicted ROI: " << roi << std::endl; - } + cv::TrackerNano::Params params; + params.backbone = backbonePath; + params.neckhead = neckheadPath; + cv::Ptr tracker = TrackerNano::create(params); + checkTrackingAccuracy(tracker); +} + +TEST(NanoTrack, accuracy_NanoTrack_V2) +{ + std::string backbonePath = cvtest::findDataFile("dnn/onnx/models/nanotrack_backbone_sim_v2.onnx", false); + std::string neckheadPath = cvtest::findDataFile("dnn/onnx/models/nanotrack_head_sim_v2.onnx", false); + + cv::TrackerNano::Params params; + params.backbone = backbonePath; + params.neckhead = neckheadPath; + cv::Ptr tracker = TrackerNano::create(params); + checkTrackingAccuracy(tracker, 0.69); } }} // namespace opencv_test:: diff --git a/modules/videoio/cmake/detect_ffmpeg.cmake b/modules/videoio/cmake/detect_ffmpeg.cmake index c33eaf221b..aa669f36b0 100644 --- a/modules/videoio/cmake/detect_ffmpeg.cmake +++ b/modules/videoio/cmake/detect_ffmpeg.cmake @@ -1,4 +1,7 @@ # --- FFMPEG --- +OCV_OPTION(OPENCV_FFMPEG_ENABLE_LIBAVDEVICE "Include FFMPEG/libavdevice library support." OFF + VISIBLE_IF WITH_FFMPEG) + if(NOT HAVE_FFMPEG AND OPENCV_FFMPEG_USE_FIND_PACKAGE) if(OPENCV_FFMPEG_USE_FIND_PACKAGE STREQUAL "1" OR OPENCV_FFMPEG_USE_FIND_PACKAGE STREQUAL "ON") set(OPENCV_FFMPEG_USE_FIND_PACKAGE "FFMPEG") @@ -29,6 +32,13 @@ if(NOT HAVE_FFMPEG AND PKG_CONFIG_FOUND) list(APPEND FFMPEG_LIBRARIES ${FFMPEG_libavresample_LIBRARIES}) list(APPEND _used_ffmpeg_libraries libavresample) endif() + if(OPENCV_FFMPEG_ENABLE_LIBAVDEVICE) + ocv_check_modules(FFMPEG_libavdevice libavdevice) # optional + if(FFMPEG_libavdevice_FOUND) + list(APPEND FFMPEG_LIBRARIES ${FFMPEG_libavdevice_LIBRARIES}) + list(APPEND _used_ffmpeg_libraries libavdevice) + endif() + endif() set(HAVE_FFMPEG TRUE) else() set(_missing_ffmpeg_libraries "") @@ -51,6 +61,7 @@ if(HAVE_FFMPEG AND NOT HAVE_FFMPEG_WRAPPER) set(_min_libavutil_version 52.3.0) set(_min_libswscale_version 2.1.1) set(_min_libavresample_version 1.0.1) + set(_min_libavdevice_version 53.2.0) foreach(ffmpeg_lib ${_used_ffmpeg_libraries}) if(FFMPEG_${ffmpeg_lib}_VERSION VERSION_LESS _min_${ffmpeg_lib}_version) message(STATUS "FFMPEG is disabled. Can't find suitable ${ffmpeg_lib} library" @@ -67,6 +78,7 @@ if(HAVE_FFMPEG AND NOT HAVE_FFMPEG_WRAPPER) unset(_min_libavutil_version) unset(_min_libswscale_version) unset(_min_libavresample_version) + unset(_min_libavdevice_version) endif() #================================== @@ -93,7 +105,12 @@ unset(_used_ffmpeg_libraries) if(HAVE_FFMPEG_WRAPPER) ocv_add_external_target(ffmpeg "" "" "HAVE_FFMPEG_WRAPPER") elseif(HAVE_FFMPEG) - ocv_add_external_target(ffmpeg "${FFMPEG_INCLUDE_DIRS}" "${FFMPEG_LIBRARIES}" "HAVE_FFMPEG") + if(OPENCV_FFMPEG_ENABLE_LIBAVDEVICE AND FFMPEG_libavdevice_FOUND) + set(HAVE_FFMPEG_LIBAVDEVICE TRUE) + ocv_add_external_target(ffmpeg "${FFMPEG_INCLUDE_DIRS}" "${FFMPEG_LIBRARIES}" "HAVE_FFMPEG;HAVE_FFMPEG_LIBAVDEVICE") + else() + ocv_add_external_target(ffmpeg "${FFMPEG_INCLUDE_DIRS}" "${FFMPEG_LIBRARIES}" "HAVE_FFMPEG") + endif() set(__builtin_defines "") set(__builtin_include_dirs "") set(__builtin_libs "") diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index bbe392266f..c22fb369ca 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -182,13 +182,13 @@ enum VideoCaptureProperties { CAP_PROP_WB_TEMPERATURE=45, //!< white-balance color temperature CAP_PROP_CODEC_PIXEL_FORMAT =46, //!< (read-only) codec's pixel format. 4-character code - see VideoWriter::fourcc . Subset of [AV_PIX_FMT_*](https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/raw.c) or -1 if unknown CAP_PROP_BITRATE =47, //!< (read-only) Video bitrate in kbits/s - CAP_PROP_ORIENTATION_META=48, //!< (read-only) Frame rotation defined by stream meta (applicable for FFmpeg back-end only) - CAP_PROP_ORIENTATION_AUTO=49, //!< if true - rotates output frames of CvCapture considering video file's metadata (applicable for FFmpeg back-end only) (https://github.com/opencv/opencv/issues/15499) + CAP_PROP_ORIENTATION_META=48, //!< (read-only) Frame rotation defined by stream meta (applicable for FFmpeg and AVFoundation back-ends only) + CAP_PROP_ORIENTATION_AUTO=49, //!< if true - rotates output frames of CvCapture considering video file's metadata (applicable for FFmpeg and AVFoundation back-ends only) (https://github.com/opencv/opencv/issues/15499) CAP_PROP_HW_ACCELERATION=50, //!< (**open-only**) Hardware acceleration type (see #VideoAccelerationType). Setting supported only via `params` parameter in cv::VideoCapture constructor / .open() method. Default value is backend-specific. CAP_PROP_HW_DEVICE =51, //!< (**open-only**) Hardware device index (select GPU if multiple available). Device enumeration is acceleration type specific. CAP_PROP_HW_ACCELERATION_USE_OPENCL=52, //!< (**open-only**) If non-zero, create new OpenCL context and bind it to current thread. The OpenCL context created with Video Acceleration context attached it (if not attached yet) for optimized GPU data copy between HW accelerated decoder and cv::UMat. - CAP_PROP_OPEN_TIMEOUT_MSEC=53, //!< (**open-only**) timeout in milliseconds for opening a video capture (applicable for FFmpeg back-end only) - CAP_PROP_READ_TIMEOUT_MSEC=54, //!< (**open-only**) timeout in milliseconds for reading from a video capture (applicable for FFmpeg back-end only) + CAP_PROP_OPEN_TIMEOUT_MSEC=53, //!< (**open-only**) timeout in milliseconds for opening a video capture (applicable for FFmpeg and GStreamer back-ends only) + CAP_PROP_READ_TIMEOUT_MSEC=54, //!< (**open-only**) timeout in milliseconds for reading from a video capture (applicable for FFmpeg and GStreamer back-ends only) CAP_PROP_STREAM_OPEN_TIME_USEC =55, //). E.g. When reading from a h264 encoded RTSP stream, the FFmpeg backend could return the SPS and/or PPS if available (if sent in reply to a DESCRIBE request), from calls to cap.retrieve(data, ). CAP_PROP_FRAME_TYPE = 69, //!< (read-only) FFmpeg back-end only - Frame type ascii code (73 = 'I', 80 = 'P', 66 = 'B' or 63 = '?' if unknown) of the most recently read frame. + CAP_PROP_N_THREADS = 70, //!< (**open-only**) Set the maximum number of threads to use. Use 0 to use as many threads as CPU cores (applicable for FFmpeg back-end only). #ifndef CV_DOXYGEN CV__CAP_PROP_LATEST #endif @@ -960,7 +961,7 @@ public: After this call use VideoCapture::retrieve() to decode and fetch frame data. */ - static /*CV_WRAP*/ + CV_WRAP static bool waitAny( const std::vector& streams, CV_OUT std::vector& readyIndex, @@ -1003,9 +1004,11 @@ public: /** @overload @param filename Name of the output video file. @param fourcc 4-character code of codec used to compress the frames. For example, - VideoWriter::fourcc('P','I','M','1') is a MPEG-1 codec, VideoWriter::fourcc('M','J','P','G') is a - motion-jpeg codec etc. List of codes can be obtained at [Video Codecs by - FOURCC](http://www.fourcc.org/codecs.php) page. FFMPEG backend with MP4 container natively uses + VideoWriter::fourcc('P','I','M','1') is a MPEG-1 codec, VideoWriter::fourcc('M','J','P','G') + is a motion-jpeg codec etc. List of codes can be obtained at + [MSDN](https://docs.microsoft.com/en-us/windows/win32/medfound/video-fourccs) page + or with this [archived page](https://web.archive.org/web/20220316062600/http://www.fourcc.org/codecs.php) + of the fourcc site for a more complete list). FFMPEG backend with MP4 container natively uses other values as fourcc code: see [ObjectType](http://mp4ra.org/#/codecs), so you may receive a warning message from OpenCV about fourcc code conversion. @param fps Framerate of the created video stream. diff --git a/modules/videoio/misc/plugin_ffmpeg/CMakeLists.txt b/modules/videoio/misc/plugin_ffmpeg/CMakeLists.txt index ebe388a886..204a425b17 100644 --- a/modules/videoio/misc/plugin_ffmpeg/CMakeLists.txt +++ b/modules/videoio/misc/plugin_ffmpeg/CMakeLists.txt @@ -16,3 +16,6 @@ message(STATUS "FFMPEG_libavformat_VERSION=${FFMPEG_libavformat_VERSION}") message(STATUS "FFMPEG_libavutil_VERSION=${FFMPEG_libavutil_VERSION}") message(STATUS "FFMPEG_libswscale_VERSION=${FFMPEG_libswscale_VERSION}") message(STATUS "FFMPEG_libavresample_VERSION=${FFMPEG_libavresample_VERSION}") +if(OPENCV_FFMPEG_ENABLE_LIBAVDEVICE) + message(STATUS "FFMPEG_libavdevice_VERSION=${FFMPEG_libavdevice_VERSION}") +endif() diff --git a/modules/videoio/misc/python/pyopencv_videoio.hpp b/modules/videoio/misc/python/pyopencv_videoio.hpp index 5fa2f9e221..e729c8631f 100644 --- a/modules/videoio/misc/python/pyopencv_videoio.hpp +++ b/modules/videoio/misc/python/pyopencv_videoio.hpp @@ -1,5 +1,6 @@ #ifdef HAVE_OPENCV_VIDEOIO typedef std::vector vector_VideoCaptureAPIs; +typedef std::vector vector_VideoCapture; template<> struct pyopencvVecConverter { @@ -20,4 +21,14 @@ bool pyopencv_to(PyObject *o, std::vector& apis, const Arg return pyopencvVecConverter::to(o, apis, info); } +template<> bool pyopencv_to(PyObject* obj, cv::VideoCapture& stream, const ArgInfo& info) +{ + Ptr * obj_getp = nullptr; + if (!pyopencv_VideoCapture_getp(obj, obj_getp)) + return (failmsgp("Incorrect type of self (must be 'VideoCapture' or its derivative)") != nullptr); + + stream = **obj_getp; + return true; +} + #endif // HAVE_OPENCV_VIDEOIO diff --git a/modules/videoio/src/cap_android_camera.cpp b/modules/videoio/src/cap_android_camera.cpp index 18fc604367..84034e6208 100644 --- a/modules/videoio/src/cap_android_camera.cpp +++ b/modules/videoio/src/cap_android_camera.cpp @@ -157,6 +157,7 @@ void OnCaptureFailed(void* context, ACameraCaptureFailure* failure); #define CAPTURE_TIMEOUT_SECONDS 2 +#define CAPTURE_POLL_INTERVAL_MS 5 /** * Range of Camera Exposure Time: @@ -167,6 +168,10 @@ void OnCaptureFailed(void* context, static const long kMinExposureTime = 1000000L; static const long kMaxExposureTime = 250000000L; +static double elapsedTimeFrom(std::chrono::time_point start) { + return std::chrono::duration(std::chrono::system_clock::now() - start).count(); +} + class AndroidCameraCapture : public IVideoCapture { int cachedIndex; @@ -181,6 +186,7 @@ class AndroidCameraCapture : public IVideoCapture std::shared_ptr captureSession; CaptureSessionState sessionState = CaptureSessionState::INITIALIZING; int32_t frameWidth = 0; + int32_t frameStride = 0; int32_t frameHeight = 0; int32_t colorFormat; std::vector buffer; @@ -266,12 +272,21 @@ public: LOGW("No Buffer Available error occured - waiting for callback"); waitingCapture = true; captureSuccess = false; + auto start = std::chrono::system_clock::now(); bool captured = condition.wait_for(lock, std::chrono::seconds(CAPTURE_TIMEOUT_SECONDS), [this]{ return captureSuccess; }); waitingCapture = false; if (captured) { mStatus = AImageReader_acquireLatestImage(imageReader.get(), &img); + // even though an image has been captured we may not be able to acquire it straight away so we poll every 10ms + while (mStatus == AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE && elapsedTimeFrom(start) < CAPTURE_TIMEOUT_SECONDS) { + std::this_thread::sleep_for(std::chrono::milliseconds(CAPTURE_POLL_INTERVAL_MS)); + mStatus = AImageReader_acquireLatestImage(imageReader.get(), &img); + } if (mStatus != AMEDIA_OK) { LOGE("Acquire image failed with error code: %d", mStatus); + if (elapsedTimeFrom(start) >= CAPTURE_TIMEOUT_SECONDS) { + LOGE("Image acquisition timed out"); + } return false; } } else { @@ -307,8 +322,11 @@ public: AImage_getPlaneData(image.get(), 1, &uPixel, &uLen); AImage_getPlaneData(image.get(), 2, &vPixel, &vLen); AImage_getPlanePixelStride(image.get(), 1, &uvPixelStride); + int32_t yBufferLen = yLen; - if ( (uvPixelStride == 2) && (uPixel == vPixel + 1) && (yLen == frameWidth * frameHeight) && (uLen == ((yLen / 2) - 1)) && (vLen == uLen) ) { + if ( (uvPixelStride == 2) && (uPixel == vPixel + 1) && (yLen == (yStride * (frameHeight - 1)) + frameWidth) && (uLen == (uvStride * ((frameHeight / 2) - 1)) + frameWidth - 1) && (uvStride == yStride) && (vLen == uLen) ) { + frameStride = yStride; + yBufferLen = frameStride * frameHeight; colorFormat = COLOR_FormatYUV420SemiPlanar; if (fourCC == FOURCC_UNKNOWN) { fourCC = FOURCC_NV21; @@ -326,8 +344,8 @@ public: } buffer.clear(); - buffer.insert(buffer.end(), yPixel, yPixel + yLen); - buffer.insert(buffer.end(), vPixel, vPixel + yLen / 2); + buffer.insert(buffer.end(), yPixel, yPixel + yBufferLen); + buffer.insert(buffer.end(), vPixel, vPixel + yBufferLen / 2); return true; } @@ -336,8 +354,8 @@ public: if (buffer.empty()) { return false; } - Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data()); if (colorFormat == COLOR_FormatYUV420Planar) { + Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data()); switch (fourCC) { case FOURCC_BGR: cv::cvtColor(yuv, out, cv::COLOR_YUV2BGR_YV12); @@ -356,18 +374,20 @@ public: break; } } else if (colorFormat == COLOR_FormatYUV420SemiPlanar) { + 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_BGR: - cv::cvtColor(yuv, out, cv::COLOR_YUV2BGR_NV21); + cv::cvtColor(tmp, out, cv::COLOR_YUV2BGR_NV21); break; case FOURCC_RGB: - cv::cvtColor(yuv, out, cv::COLOR_YUV2RGB_NV21); + cv::cvtColor(tmp, out, cv::COLOR_YUV2RGB_NV21); break; case FOURCC_GRAY: - cv::cvtColor(yuv, out, cv::COLOR_YUV2GRAY_NV21); + cv::cvtColor(tmp, out, cv::COLOR_YUV2GRAY_NV21); break; case FOURCC_NV21: - yuv.copyTo(out); + tmp.copyTo(out); break; default: LOGE("Unexpected FOURCC value: %d", fourCC); diff --git a/modules/videoio/src/cap_android_mediandk.cpp b/modules/videoio/src/cap_android_mediandk.cpp index c7f2855502..e92c7f1720 100644 --- a/modules/videoio/src/cap_android_mediandk.cpp +++ b/modules/videoio/src/cap_android_mediandk.cpp @@ -13,14 +13,17 @@ #include #include #include - +#include #include "media/NdkMediaCodec.h" +#include "media/NdkMediaMuxer.h" #include "media/NdkMediaExtractor.h" +#include "media/NdkMediaFormat.h" #define INPUT_TIMEOUT_MS 2000 #define COLOR_FormatYUV420Planar 19 #define COLOR_FormatYUV420SemiPlanar 21 +#define COLOR_FormatSurface 0x7f000789 //See https://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities for codes using namespace cv; @@ -48,15 +51,29 @@ class AndroidMediaNdkCapture : public IVideoCapture public: AndroidMediaNdkCapture(): sawInputEOS(false), sawOutputEOS(false), - frameWidth(0), frameHeight(0), colorFormat(0) {} + frameStride(0), frameWidth(0), frameHeight(0), colorFormat(0), + videoWidth(0), videoHeight(0), + videoFrameCount(0), + videoRotation(0), videoRotationCode(-1), + videoOrientationAuto(false) {} + std::shared_ptr mediaExtractor; std::shared_ptr mediaCodec; bool sawInputEOS; bool sawOutputEOS; + int32_t frameStride; int32_t frameWidth; int32_t frameHeight; int32_t colorFormat; + int32_t videoWidth; + int32_t videoHeight; + float videoFrameRate; + int32_t videoFrameCount; + int32_t videoRotation; + int32_t videoRotationCode; + bool videoOrientationAuto; std::vector buffer; + Mat frame; ~AndroidMediaNdkCapture() { cleanUp(); } @@ -89,6 +106,7 @@ public: size_t bufferSize = 0; auto mediaFormat = std::shared_ptr(AMediaCodec_getOutputFormat(mediaCodec.get()), deleter_AMediaFormat); AMediaFormat_getInt32(mediaFormat.get(), AMEDIAFORMAT_KEY_WIDTH, &frameWidth); + AMediaFormat_getInt32(mediaFormat.get(), AMEDIAFORMAT_KEY_STRIDE, &frameStride); AMediaFormat_getInt32(mediaFormat.get(), AMEDIAFORMAT_KEY_HEIGHT, &frameHeight); AMediaFormat_getInt32(mediaFormat.get(), AMEDIAFORMAT_KEY_COLOR_FORMAT, &colorFormat); uint8_t* codecBuffer = AMediaCodec_getOutputBuffer(mediaCodec.get(), bufferIndex, &bufferSize); @@ -96,30 +114,14 @@ public: LOGV("colorFormat: %d", colorFormat); LOGV("buffer size: %zu", bufferSize); LOGV("width (frame): %d", frameWidth); + LOGV("stride (frame): %d", frameStride); LOGV("height (frame): %d", frameHeight); if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) { LOGV("output EOS"); sawOutputEOS = true; } - if ((size_t)frameWidth * frameHeight * 3 / 2 > bufferSize) - { - if (bufferSize == 3110400 && frameWidth == 1920 && frameHeight == 1088) - { - frameHeight = 1080; - LOGV("Buffer size is too small, force using height = %d", frameHeight); - } - else if(bufferSize == 3110400 && frameWidth == 1088 && frameHeight == 1920) - { - frameWidth = 1080; - LOGV("Buffer size is too small, force using width = %d", frameWidth); - } - else - { - LOGE("Buffer size is too small. Frame is ignored. Enable verbose logging to see actual values of parameters"); - return false; - } - } + AMediaCodec_releaseOutputBuffer(mediaCodec.get(), bufferIndex, info.size != 0); return true; } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) { @@ -154,15 +156,25 @@ public: if (buffer.empty()) { return false; } - Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data()); + + Mat yuv(frameHeight + frameHeight/2, frameStride, CV_8UC1, buffer.data()); + if (colorFormat == COLOR_FormatYUV420Planar) { - cv::cvtColor(yuv, out, cv::COLOR_YUV2BGR_YV12); + cv::cvtColor(yuv, frame, cv::COLOR_YUV2BGR_YV12); } else if (colorFormat == COLOR_FormatYUV420SemiPlanar) { - cv::cvtColor(yuv, out, cv::COLOR_YUV2BGR_NV21); + cv::cvtColor(yuv, frame, cv::COLOR_YUV2BGR_NV21); } else { LOGE("Unsupported video format: %d", colorFormat); return false; } + + Mat croppedFrame = frame(Rect(0, 0, videoWidth, videoHeight)); + out.assign(croppedFrame); + + if (videoOrientationAuto && -1 != videoRotationCode) { + cv::rotate(out, out, videoRotationCode); + } + return true; } @@ -170,14 +182,32 @@ public: { switch (property_id) { - case CV_CAP_PROP_FRAME_WIDTH: return frameWidth; - case CV_CAP_PROP_FRAME_HEIGHT: return frameHeight; + case CV_CAP_PROP_FRAME_WIDTH: + return (( videoOrientationAuto && + (cv::ROTATE_90_CLOCKWISE == videoRotationCode || cv::ROTATE_90_COUNTERCLOCKWISE == videoRotationCode)) + ? videoHeight : videoWidth); + case CV_CAP_PROP_FRAME_HEIGHT: + return (( videoOrientationAuto && + (cv::ROTATE_90_CLOCKWISE == videoRotationCode || cv::ROTATE_90_COUNTERCLOCKWISE == videoRotationCode)) + ? videoWidth : videoHeight); + case CV_CAP_PROP_FPS: return videoFrameRate; + case CV_CAP_PROP_FRAME_COUNT: return videoFrameCount; + case CAP_PROP_ORIENTATION_META: return videoRotation; + case CAP_PROP_ORIENTATION_AUTO: return videoOrientationAuto ? 1 : 0; } return 0; } - bool setProperty(int /* property_id */, double /* value */) CV_OVERRIDE + bool setProperty(int property_id, double value) CV_OVERRIDE { + switch (property_id) + { + case CAP_PROP_ORIENTATION_AUTO: { + videoOrientationAuto = value != 0 ? true : false; + return true; + } + } + return false; } @@ -221,9 +251,20 @@ public: if (!AMediaFormat_getString(format.get(), AMEDIAFORMAT_KEY_MIME, &mime)) { LOGV("no mime type"); } else if (!strncmp(mime, "video/", 6)) { - int32_t trackWidth, trackHeight; + int32_t trackWidth, trackHeight, fps, frameCount = 0, rotation = 0; AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_WIDTH, &trackWidth); AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_HEIGHT, &trackHeight); + AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_FRAME_RATE, &fps); + + #if __ANDROID_API__ >= 28 + AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_ROTATION, &rotation); + LOGV("rotation (track): %d", rotation); + #endif + + #if __ANDROID_API__ >= 29 + AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_FRAME_COUNT, &frameCount); + #endif + LOGV("width (track): %d", trackWidth); LOGV("height (track): %d", trackHeight); if (AMediaExtractor_selectTrack(mediaExtractor.get(), i) != AMEDIA_OK) { @@ -241,6 +282,31 @@ public: if (AMediaCodec_start(mediaCodec.get()) != AMEDIA_OK) { continue; } + + videoWidth = trackWidth; + videoHeight = trackHeight; + videoFrameRate = fps; + videoFrameCount = frameCount; + videoRotation = rotation; + + switch(videoRotation) { + case 90: + videoRotationCode = cv::ROTATE_90_CLOCKWISE; + break; + + case 180: + videoRotationCode = cv::ROTATE_180; + break; + + case 270: + videoRotationCode = cv::ROTATE_90_COUNTERCLOCKWISE; + break; + + default: + videoRotationCode = -1; + break; + } + return true; } } @@ -251,12 +317,354 @@ public: void cleanUp() { sawInputEOS = true; sawOutputEOS = true; + frameStride = 0; frameWidth = 0; frameHeight = 0; colorFormat = 0; + videoWidth = 0; + videoHeight = 0; + videoFrameRate = 0; + videoFrameCount = 0; + videoRotation = 0; + videoRotationCode = -1; } }; + + +class AndroidMediaNdkVideoWriter CV_FINAL : + public cv::IVideoWriter +{ + typedef struct { + int fourcc; + const char* mime; + OutputFormat muxerFormat; + } + FourCCInfo; + + static const int64_t TIMEOUT = 1000L; + static const FourCCInfo FOURCC_INFO[]; + + static const FourCCInfo* findInfo(int fourcc) { + for( const FourCCInfo *it = FOURCC_INFO; NULL != it->mime; it++ ) { + if (fourcc == it->fourcc) return it; + } + return NULL; + } + + AMediaFormat* format; + AMediaCodec* encoder; + AMediaMuxer* muxer; + + #if __ANDROID_API__ >= 26 + ANativeWindow* surface; + #endif + + long frameIndex; + int width; + int height; + double frameRate; + ssize_t videoTrackIndex; + int fd; + + void drainEncoder(bool end) { + if (end) { + #if __ANDROID_API__ >= 26 + AMediaCodec_signalEndOfInputStream(encoder); + #else + writeBytes(NULL, 0); + #endif + } + + AMediaCodecBufferInfo bufferInfo; + ssize_t bufferIndex; + size_t bufferSize; + uint8_t *buffer; + + while (true) { + bufferIndex = AMediaCodec_dequeueOutputBuffer(encoder, &bufferInfo, TIMEOUT); + if (bufferIndex >= 0) { + buffer = AMediaCodec_getOutputBuffer(encoder, (size_t)bufferIndex, &bufferSize); + + if (NULL == buffer || 0 == bufferSize){ + LOGE("Can't get output buffer"); + break; + } + + if (videoTrackIndex >= 0) { + bufferInfo.presentationTimeUs = frameIndex * 1000000L / frameRate; + LOGV("Muxer write to track %d: %d byte(s)", (int)videoTrackIndex, (int)bufferInfo.size); + AMediaMuxer_writeSampleData(muxer, (size_t)videoTrackIndex, buffer, &bufferInfo); + } else { + LOGE("Invalid video track !"); + } + + AMediaCodec_releaseOutputBuffer(encoder, (size_t)bufferIndex, false); + if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) break; + } else if (AMEDIACODEC_INFO_TRY_AGAIN_LATER == bufferIndex) { + if (!end) break; + } else if (AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED == bufferIndex) { + videoTrackIndex = AMediaMuxer_addTrack(muxer, AMediaCodec_getOutputFormat(encoder)); + if (videoTrackIndex >= 0) { + AMediaMuxer_start(muxer); + } + LOGV("New videoTrackIndex: %d", (int)videoTrackIndex); + } + } + } + + #if __ANDROID_API__ < 26 + void writeBytes( uint8_t* inputBuffer, size_t inputBufferSize ) { + LOGV("[writeBytes] inputBufferSize=%u", (unsigned int)inputBufferSize); + + ssize_t bufferIndex; + size_t bufferSize; + uint8_t* buffer; + size_t partialSize; + bool firstCall = true; + uint32_t flags; + + while(inputBufferSize > 0 || firstCall) { + bufferIndex = AMediaCodec_dequeueInputBuffer(encoder, TIMEOUT); + + if (bufferIndex >= 0) { + firstCall = false; + buffer = AMediaCodec_getInputBuffer(encoder, (size_t)bufferIndex, &bufferSize); + if (NULL == buffer || 0 == bufferSize) break; + + flags = 0; + partialSize = (inputBufferSize > bufferSize) ? bufferSize : inputBufferSize; + if (partialSize > 0) { + memcpy(buffer, inputBuffer, partialSize); + inputBuffer += partialSize; + inputBufferSize -= partialSize; + if (inputBufferSize > 0) { + flags = AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME; + } + } else { + flags = AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM; + } + + LOGV( + "[writeBytes] partial - bufferIndex=%d, bufferSize=%u, partialSize=%u, remaining inputBufferSize=%u", + (int)bufferIndex, (unsigned int)bufferSize, (unsigned int)partialSize, (unsigned int)inputBufferSize + ); + + AMediaCodec_queueInputBuffer(encoder, (size_t)bufferIndex, 0, partialSize, frameIndex * 1000000L / frameRate, flags); + if (NULL != inputBuffer) drainEncoder(false); + } + } + } + #endif + +public: + AndroidMediaNdkVideoWriter(const cv::String& filename, int fourcc, double fps, cv::Size frameSize, const VideoWriterParameters& params) + : format(NULL), + encoder(NULL), + muxer(NULL), + #if __ANDROID_API__ >= 26 + surface(NULL), + #endif + frameIndex(0), + width(0), + height(0), + frameRate(0.), + videoTrackIndex(-1), + fd(-1) { + open(filename, fourcc, fps, frameSize, params); + } + virtual ~AndroidMediaNdkVideoWriter() { close(); } + + virtual int getCaptureDomain() const CV_OVERRIDE { return cv::CAP_ANDROID; } + + virtual void write(cv::InputArray image_ ) CV_OVERRIDE + { + if (!image_.isMat()) { + LOGE("Support only Mat input"); + return; + } + + Mat image = image_.getMat(); + if (CV_8UC3 != image.type() || image.cols > width || image.rows > height) { + 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, + image.cols, image.rows, image.type() + ); + return; + } + + #if __ANDROID_API__ >= 26 + 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); + } + #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 + Mat imageYV12; + cvtColor(image, imageYV12, COLOR_BGR2YUV_YV12); + + //convert from YV12 to NV12 + 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); + + writeBytes( imageYV12.ptr(), imageYV12.rows * imageYV12.cols ); + #endif + + drainEncoder(false); + + frameIndex++; + } + + virtual bool open( const cv::String& filename, int fourcc, double fps, cv::Size frameSize, const VideoWriterParameters& params ) + { + media_status_t status; + + close(); + + const FourCCInfo* info = findInfo(fourcc); + if (NULL == info) { + LOGE("ERROR: findInfo"); + return false; + } + + format = AMediaFormat_new(); + if (NULL == format) { + LOGE("ERROR: AMediaFormat_new"); + goto error; + } + + LOGV("mime: %s, width: %d, height: %d, fps: %f", info->mime, frameSize.width, frameSize.height, fps); + + AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, info->mime); + AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, frameSize.width); + AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, frameSize.height); + AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, (float)fps); + AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 5); + AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, frameSize.width * frameSize.height * 5); + AMediaFormat_setInt32( + format, AMEDIAFORMAT_KEY_COLOR_FORMAT, + #if __ANDROID_API__ >= 26 + COLOR_FormatSurface + #else + COLOR_FormatYUV420SemiPlanar + #endif + ); + + encoder = AMediaCodec_createEncoderByType(info->mime); + if (NULL == encoder) { + LOGE("ERROR: AMediaCodec_createEncoderByType"); + goto error; + } + + status = AMediaCodec_configure(encoder, format, NULL, NULL, AMEDIACODEC_CONFIGURE_FLAG_ENCODE); + if (AMEDIA_OK != status) { + LOGE("ERROR: AMediaCodec_configure (%d)", status); + goto error; + } + + #if __ANDROID_API__ >= 26 + status = AMediaCodec_createInputSurface(encoder, &surface); + if (AMEDIA_OK != status || NULL == surface) { + LOGE("ERROR: AMediaCodec_createInputSurface (%d)", status); + goto error; + } + #endif + + AMediaCodec_start(encoder); + + fd = ::open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + LOGE("ERROR: open"); + goto error; + } + + muxer = AMediaMuxer_new(fd, info->muxerFormat); + if (NULL == muxer) { + LOGE("ERROR: AMediaMuxer_new"); + goto error; + } + + AMediaMuxer_setOrientationHint(muxer, params.get(CAP_PROP_ORIENTATION_META, 0)); + + frameIndex = 0; + width = frameSize.width; + height = frameSize.height; + frameRate = fps; + videoTrackIndex = -1; + + return true; + + error: + close(); + return false; + } + + virtual void close() + { + if (videoTrackIndex >= 0 && NULL != muxer) { + drainEncoder(true); + AMediaMuxer_stop(muxer); + } + + if (NULL != encoder) AMediaCodec_delete(encoder); + if (NULL != muxer) AMediaMuxer_delete(muxer); + + #if __ANDROID_API__ >= 26 + if (NULL != surface) ANativeWindow_release(surface); + #endif + + if (fd >= 0) ::close(fd); + if (NULL != format) AMediaFormat_delete(format); + + format = NULL; + encoder = NULL; + muxer = NULL; + #if __ANDROID_API__ >= 26 + surface = NULL; + #endif + frameIndex = 0; + width = 0; + height = 0; + frameRate = 0.; + videoTrackIndex = -1; + fd = -1; + } + + virtual double getProperty(int) const CV_OVERRIDE { return 0.; } + virtual bool setProperty(int, double) CV_OVERRIDE { return false; } + virtual bool isOpened() const CV_OVERRIDE { return NULL != encoder; } +}; + + +const AndroidMediaNdkVideoWriter::FourCCInfo AndroidMediaNdkVideoWriter::FOURCC_INFO[] = { + { CV_FOURCC('H', '2', '6', '4'), "video/avc", AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4 }, + { CV_FOURCC('H', '2', '6', '5'), "video/hevc", AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4 }, + { CV_FOURCC('H', '2', '6', '3'), "video/3gpp", AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4 }, + { CV_FOURCC('M', 'P', '4', 'V'), "video/mp4v-es", AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4 }, + { 0, NULL }, +}; + + + /****************** Implementation of interface functions ********************/ Ptr cv::createAndroidCapture_file(const std::string &filename) { @@ -265,3 +673,14 @@ Ptr cv::createAndroidCapture_file(const std::string &filename) { return res; return Ptr(); } + + +Ptr cv::createAndroidVideoWriter( + const std::string& filename, int fourcc, + double fps, const cv::Size& frameSize, + const VideoWriterParameters& params) { + Ptr writer = makePtr(filename, fourcc, fps, frameSize, params); + if (writer && writer->isOpened()) + return writer; + return Ptr(); +} diff --git a/modules/videoio/src/cap_avfoundation.mm b/modules/videoio/src/cap_avfoundation.mm index 8959c124fa..12fa42f5b5 100644 --- a/modules/videoio/src/cap_avfoundation.mm +++ b/modules/videoio/src/cap_avfoundation.mm @@ -162,6 +162,7 @@ private: bool setupReadingAt(CMTime position); IplImage* retrieveFramePixelBuffer(); + int getPreferredOrientationDegrees() const; CMTime mFrameTimestamp; size_t mFrameNum; @@ -1098,6 +1099,13 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { return mOutImage; } +int CvCaptureFile::getPreferredOrientationDegrees() const { + if (mAssetTrack == nil) return 0; + + CGAffineTransform transform = mAssetTrack.preferredTransform; + double radians = atan2(transform.b, transform.a); + return static_cast(round(radians * 180 / M_PI)); +} IplImage* CvCaptureFile::retrieveFrame(int) { return retrieveFramePixelBuffer(); @@ -1129,6 +1137,8 @@ double CvCaptureFile::getProperty(int property_id) const{ return mFormat; case CV_CAP_PROP_FOURCC: return mMode; + case cv::CAP_PROP_ORIENTATION_META: + return getPreferredOrientationDegrees(); default: break; } diff --git a/modules/videoio/src/cap_avfoundation_mac.mm b/modules/videoio/src/cap_avfoundation_mac.mm index a41fad2cbe..c0ad4810d4 100644 --- a/modules/videoio/src/cap_avfoundation_mac.mm +++ b/modules/videoio/src/cap_avfoundation_mac.mm @@ -169,6 +169,7 @@ private: bool setupReadingAt(CMTime position); IplImage* retrieveFramePixelBuffer(); + int getPreferredOrientationDegrees() const; CMTime mFrameTimestamp; size_t mFrameNum; @@ -388,6 +389,15 @@ int CvCaptureCAM::startCaptureDevice(int cameraNum) { return 0; } + // Preserve devices ordering on the system + // see AVCaptureDevice::uniqueID property documentation for more info + devices = [devices + sortedArrayUsingComparator:^NSComparisonResult(AVCaptureDevice *d1, + AVCaptureDevice *d2) { + return [d1.uniqueID compare:d2.uniqueID]; + } + ]; + mCaptureDevice = devices[cameraNum]; if ( ! mCaptureDevice ) { @@ -1064,6 +1074,13 @@ IplImage* CvCaptureFile::retrieveFramePixelBuffer() { return mOutImage; } +int CvCaptureFile::getPreferredOrientationDegrees() const { + if (mAssetTrack == nil) return 0; + + CGAffineTransform transform = mAssetTrack.preferredTransform; + double radians = atan2(transform.b, transform.a); + return static_cast(round(radians * 180 / M_PI)); +} IplImage* CvCaptureFile::retrieveFrame(int) { return retrieveFramePixelBuffer(); @@ -1095,6 +1112,8 @@ double CvCaptureFile::getProperty(int property_id) const{ return mFormat; case CV_CAP_PROP_FOURCC: return mMode; + case cv::CAP_PROP_ORIENTATION_META: + return getPreferredOrientationDegrees(); default: break; } diff --git a/modules/videoio/src/cap_dshow.cpp b/modules/videoio/src/cap_dshow.cpp index db42601dab..3d7013d48f 100644 --- a/modules/videoio/src/cap_dshow.cpp +++ b/modules/videoio/src/cap_dshow.cpp @@ -3474,6 +3474,18 @@ bool VideoCapture_DShow::setProperty(int propIdx, double propVal) return g_VI.isDeviceSetup(m_index); } + case CV_CAP_PROP_AUTO_EXPOSURE: + { + // Flags are required to toggle auto exposure or not, but the setProperty interface does not support multiple parameters + bool enabled = cvRound(propVal) == 1; + long minExposure, maxExposure, delta, currentExposure, flags, defaultValue; + if (!g_VI.getVideoSettingCamera(m_index, CameraControl_Exposure, minExposure, maxExposure, delta, currentExposure, flags, defaultValue)) + { + return false; + } + return g_VI.setVideoSettingCamera(m_index, CameraControl_Exposure, currentExposure, enabled ? CameraControl_Flags_Auto | CameraControl_Flags_Manual : CameraControl_Flags_Manual, enabled ? true : false); + } + case CV_CAP_PROP_AUTOFOCUS: { // Flags are required to toggle autofocus or not, but the setProperty interface does not support multiple parameters diff --git a/modules/videoio/src/cap_ffmpeg.cpp b/modules/videoio/src/cap_ffmpeg.cpp index 7030d0e653..764bc33864 100644 --- a/modules/videoio/src/cap_ffmpeg.cpp +++ b/modules/videoio/src/cap_ffmpeg.cpp @@ -54,6 +54,7 @@ #define icvReleaseCapture_FFMPEG_p cvReleaseCapture_FFMPEG #define icvGrabFrame_FFMPEG_p cvGrabFrame_FFMPEG #define icvRetrieveFrame_FFMPEG_p cvRetrieveFrame_FFMPEG +#define icvRetrieveFrame2_FFMPEG_p cvRetrieveFrame2_FFMPEG #define icvSetCaptureProperty_FFMPEG_p cvSetCaptureProperty_FFMPEG #define icvGetCaptureProperty_FFMPEG_p cvGetCaptureProperty_FFMPEG #define icvCreateVideoWriter_FFMPEG_p cvCreateVideoWriter_FFMPEG @@ -90,7 +91,7 @@ public: virtual bool retrieveFrame(int flag, cv::OutputArray frame) CV_OVERRIDE { unsigned char* data = 0; - int step=0, width=0, height=0, cn=0; + int step=0, width=0, height=0, cn=0, depth=0; if (!ffmpegCapture) return false; @@ -103,16 +104,16 @@ public: } if (flag == 0) { - if (!icvRetrieveFrame_FFMPEG_p(ffmpegCapture, &data, &step, &width, &height, &cn)) + if (!icvRetrieveFrame2_FFMPEG_p(ffmpegCapture, &data, &step, &width, &height, &cn, &depth)) return false; } else { - if (!ffmpegCapture->retrieveFrame(flag, &data, &step, &width, &height, &cn)) + if (!ffmpegCapture->retrieveFrame(flag, &data, &step, &width, &height, &cn, &depth)) return false; } - cv::Mat tmp(height, width, CV_MAKETYPE(CV_8U, cn), data, step); - this->rotateFrame(tmp); + cv::Mat tmp(height, width, CV_MAKETYPE(depth, cn), data, step); + applyMetadataRotation(*this, tmp); tmp.copyTo(frame); return true; @@ -137,30 +138,6 @@ public: protected: CvCapture_FFMPEG* ffmpegCapture; - - void rotateFrame(cv::Mat &mat) const - { - bool rotation_auto = 0 != getProperty(CAP_PROP_ORIENTATION_AUTO); - int rotation_angle = static_cast(getProperty(CAP_PROP_ORIENTATION_META)); - - if(!rotation_auto || rotation_angle%360 == 0) - { - return; - } - - cv::RotateFlags flag; - if(rotation_angle == 90 || rotation_angle == -270) { // Rotate clockwise 90 degrees - flag = cv::ROTATE_90_CLOCKWISE; - } else if(rotation_angle == 270 || rotation_angle == -90) { // Rotate clockwise 270 degrees - flag = cv::ROTATE_90_COUNTERCLOCKWISE; - } else if(rotation_angle == 180 || rotation_angle == -180) { // Rotate clockwise 180 degrees - flag = cv::ROTATE_180; - } else { // Unsupported rotation - return; - } - - cv::rotate(mat, mat, flag); - } }; } // namespace @@ -189,7 +166,7 @@ public: { if(!ffmpegWriter) return; - CV_Assert(image.depth() == CV_8U); + CV_Assert(image.depth() == CV_8U || image.depth() == CV_16U); // if UMat, try GPU to GPU copy using OpenCL extensions if (image.isUMat()) { diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index d02b045a19..dfa272639d 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -82,7 +82,11 @@ extern "C" { #include #include +// https://github.com/FFmpeg/FFmpeg/blame/d79c240196f43b93bd204363f1facc270029f113/doc/APIchanges#L1689-L1695 +#if LIBAVUTIL_BUILD >= (LIBAVUTIL_VERSION_MICRO >= 100 \ + ? CALC_FFMPEG_VERSION(52, 85, 100) : CALC_FFMPEG_VERSION(53, 15, 0)) #include +#endif #if LIBAVUTIL_BUILD >= (LIBAVUTIL_VERSION_MICRO >= 100 \ ? CALC_FFMPEG_VERSION(51, 63, 100) : CALC_FFMPEG_VERSION(54, 6, 0)) @@ -91,6 +95,9 @@ extern "C" { #include #include +#ifdef HAVE_FFMPEG_LIBAVDEVICE +#include +#endif // https://github.com/FFmpeg/FFmpeg/blob/b6af56c034759b81985f8ea094e41cbd5f7fecfb/doc/APIchanges#L602-L605 #if LIBAVFORMAT_BUILD < CALC_FFMPEG_VERSION(58, 9, 100) @@ -107,6 +114,8 @@ extern "C" { #include #endif +#include + // https://github.com/FFmpeg/FFmpeg/blob/b6af56c034759b81985f8ea094e41cbd5f7fecfb/doc/APIchanges#L208-L210 #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(59, 0, 100) # define CV_FFMPEG_FMT_CONST const @@ -364,42 +373,6 @@ inline double get_monotonic_time_diff_ms(timespec time1, timespec time2) } #endif // USE_AV_INTERRUPT_CALLBACK -static int get_number_of_cpus(void) -{ -#if defined _WIN32 - SYSTEM_INFO sysinfo; - GetSystemInfo( &sysinfo ); - - return (int)sysinfo.dwNumberOfProcessors; -#elif defined __linux__ || defined __HAIKU__ - return (int)sysconf( _SC_NPROCESSORS_ONLN ); -#elif defined __APPLE__ - int numCPU=0; - int mib[4]; - size_t len = sizeof(numCPU); - - // set the mib for hw.ncpu - mib[0] = CTL_HW; - mib[1] = HW_AVAILCPU; // alternatively, try HW_NCPU; - - // get the number of CPUs from the system - sysctl(mib, 2, &numCPU, &len, NULL, 0); - - if( numCPU < 1 ) - { - mib[1] = HW_NCPU; - sysctl( mib, 2, &numCPU, &len, NULL, 0 ); - - if( numCPU < 1 ) - numCPU = 1; - } - - return (int)numCPU; -#else - return 1; -#endif -} - struct Image_FFMPEG { @@ -407,7 +380,6 @@ struct Image_FFMPEG int step; int width; int height; - int cn; }; @@ -549,7 +521,7 @@ struct CvCapture_FFMPEG double getProperty(int) const; bool setProperty(int, double); bool grabFrame(); - bool retrieveFrame(int flag, unsigned char** data, int* step, int* width, int* height, int* cn); + bool retrieveFrame(int flag, unsigned char** data, int* step, int* width, int* height, int* cn, int* depth); bool retrieveHWFrame(cv::OutputArray output); void rotateFrame(cv::Mat &mat) const; @@ -607,6 +579,7 @@ struct CvCapture_FFMPEG bool processRawPacket(); bool rawMode; bool rawModeInitialized; + bool convertRGB; AVPacket packet_filtered; #if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(58, 20, 100) AVBSFContext* bsfc; @@ -621,6 +594,10 @@ struct CvCapture_FFMPEG void CvCapture_FFMPEG::init() { +#ifdef HAVE_FFMPEG_LIBAVDEVICE + //libavdevice is available, so let's register all input and output devices (e.g v4l2) + avdevice_register_all(); +#endif ic = 0; video_stream = -1; video_st = 0; @@ -655,6 +632,7 @@ void CvCapture_FFMPEG::init() rawMode = false; rawModeInitialized = false; + convertRGB = true; memset(&packet_filtered, 0, sizeof(packet_filtered)); av_init_packet(&packet_filtered); bsfc = NULL; @@ -984,11 +962,17 @@ public: inline void fill_codec_context(AVCodecContext * enc, AVDictionary * dict) { -//#ifdef FF_API_THREAD_INIT -// avcodec_thread_init(enc, get_number_of_cpus()); -//#else - enc->thread_count = get_number_of_cpus(); -//#endif + if (!enc->thread_count) + { + int nCpus = cv::getNumberOfCPUs(); + int requestedThreads = std::min(nCpus, 16); // [OPENCV:FFMPEG:24] Application has requested XX threads. Using a thread count greater than 16 is not recommended. + char* threads_option = getenv("OPENCV_FFMPEG_THREADS"); + if (threads_option != NULL) + { + requestedThreads = atoi(threads_option); + } + enc->thread_count = requestedThreads; + } AVDictionaryEntry* avdiscard_entry = av_dict_get(dict, "avdiscard", NULL, 0); @@ -1024,11 +1008,21 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& unsigned i; bool valid = false; + int nThreads = 0; close(); if (!params.empty()) { + convertRGB = params.get(CAP_PROP_CONVERT_RGB, true); + if (!convertRGB) + { + CV_LOG_WARNING(NULL, "VIDEOIO/FFMPEG: BGR conversion turned OFF, decoded frame will be " + "returned in its original format. " + "Multiplanar formats are not supported by the backend. " + "Only GRAY8/GRAY16LE pixel formats have been tested. " + "Use at your own risk."); + } if (params.has(CAP_PROP_FORMAT)) { int value = params.get(CAP_PROP_FORMAT); @@ -1081,6 +1075,10 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& read_timeout = params.get(CAP_PROP_READ_TIMEOUT_MSEC); } #endif + if (params.has(CAP_PROP_N_THREADS)) + { + nThreads = params.get(CAP_PROP_N_THREADS); + } if (params.warnUnusedParameters()) { CV_LOG_ERROR(NULL, "VIDEOIO/FFMPEG: unsupported parameters in .open(), see logger INFO channel for details. Bailout"); @@ -1110,6 +1108,7 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& } else { + CV_LOG_DEBUG(NULL, "VIDEOIO/FFMPEG: using capture options from environment: " << options); #if LIBAVUTIL_BUILD >= (LIBAVUTIL_VERSION_MICRO >= 100 ? CALC_FFMPEG_VERSION(52, 17, 100) : CALC_FFMPEG_VERSION(52, 7, 0)) av_dict_parse_string(&dict, options, ";", "|", 0); #else @@ -1248,6 +1247,7 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& #endif continue; } + context->thread_count = nThreads; fill_codec_context(context, dict); #ifdef CV_FFMPEG_CODECPAR avcodec_parameters_to_context(context, par); @@ -1290,7 +1290,6 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& frame.width = context->width; frame.height = context->height; - frame.cn = 3; frame.step = 0; frame.data = NULL; get_rotation_angle(); @@ -1444,6 +1443,7 @@ bool CvCapture_FFMPEG::grabFrame() #if USE_AV_INTERRUPT_CALLBACK // activate interrupt callback + interrupt_metadata.timeout = 0; get_monotonic_time(&interrupt_metadata.value); interrupt_metadata.timeout_after_ms = read_timeout; #endif @@ -1510,10 +1510,6 @@ bool CvCapture_FFMPEG::grabFrame() ret = got_picture ? 0 : -1; #endif if (ret >= 0) { - //picture_pts = picture->best_effort_timestamp; - if( picture_pts == AV_NOPTS_VALUE_ ) - picture_pts = picture->CV_FFMPEG_PTS_FIELD != AV_NOPTS_VALUE_ && picture->CV_FFMPEG_PTS_FIELD != 0 ? picture->CV_FFMPEG_PTS_FIELD : picture->pkt_dts; - valid = true; } else if (ret == AVERROR(EAGAIN)) { continue; @@ -1526,8 +1522,11 @@ bool CvCapture_FFMPEG::grabFrame() } } - if (valid) + if (valid) { + if( picture_pts == AV_NOPTS_VALUE_ ) + picture_pts = picture->CV_FFMPEG_PTS_FIELD != AV_NOPTS_VALUE_ && picture->CV_FFMPEG_PTS_FIELD != 0 ? picture->CV_FFMPEG_PTS_FIELD : picture->pkt_dts; frame_number++; + } if (!rawMode && valid && first_frame_number < 0) first_frame_number = dts_to_frame_number(picture_pts); @@ -1541,7 +1540,7 @@ bool CvCapture_FFMPEG::grabFrame() return valid; } -bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, int* width, int* height, int* cn) +bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, int* width, int* height, int* cn, int* depth) { if (!video_st || !context) return false; @@ -1562,6 +1561,7 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, *width = *step; *height = 1; *cn = 1; + *depth = CV_8U; return ret; } @@ -1581,6 +1581,21 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, if (!sw_picture || !sw_picture->data[0]) return false; + CV_LOG_DEBUG(NULL, "Input picture format: " << av_get_pix_fmt_name((AVPixelFormat)sw_picture->format)); + const AVPixelFormat result_format = convertRGB ? AV_PIX_FMT_BGR24 : (AVPixelFormat)sw_picture->format; + switch (result_format) + { + case AV_PIX_FMT_BGR24: *depth = CV_8U; *cn = 3; break; + case AV_PIX_FMT_GRAY8: *depth = CV_8U; *cn = 1; break; + case AV_PIX_FMT_GRAY16LE: *depth = CV_16U; *cn = 1; break; + default: + CV_LOG_WARNING(NULL, "Unknown/unsupported picture format: " << av_get_pix_fmt_name(result_format) + << ", will be treated as 8UC1."); + *depth = CV_8U; + *cn = 1; + break; // TODO: return false? + } + if( img_convert_ctx == NULL || frame.width != video_st->CV_FFMPEG_CODEC_FIELD->width || frame.height != video_st->CV_FFMPEG_CODEC_FIELD->height || @@ -1595,7 +1610,7 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, buffer_width, buffer_height, (AVPixelFormat)sw_picture->format, buffer_width, buffer_height, - AV_PIX_FMT_BGR24, + result_format, SWS_BICUBIC, NULL, NULL, NULL ); @@ -1605,7 +1620,7 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, #if USE_AV_FRAME_GET_BUFFER av_frame_unref(&rgb_picture); - rgb_picture.format = AV_PIX_FMT_BGR24; + rgb_picture.format = result_format; rgb_picture.width = buffer_width; rgb_picture.height = buffer_height; if (0 != av_frame_get_buffer(&rgb_picture, 32)) @@ -1617,14 +1632,13 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, int aligns[AV_NUM_DATA_POINTERS]; avcodec_align_dimensions2(video_st->codec, &buffer_width, &buffer_height, aligns); rgb_picture.data[0] = (uint8_t*)realloc(rgb_picture.data[0], - _opencv_ffmpeg_av_image_get_buffer_size( AV_PIX_FMT_BGR24, + _opencv_ffmpeg_av_image_get_buffer_size( result_format, buffer_width, buffer_height )); _opencv_ffmpeg_av_image_fill_arrays(&rgb_picture, rgb_picture.data[0], - AV_PIX_FMT_BGR24, buffer_width, buffer_height ); + result_format, buffer_width, buffer_height ); #endif frame.width = video_st->CV_FFMPEG_CODEC_FIELD->width; frame.height = video_st->CV_FFMPEG_CODEC_FIELD->height; - frame.cn = 3; frame.data = rgb_picture.data[0]; frame.step = rgb_picture.linesize[0]; } @@ -1642,7 +1656,6 @@ bool CvCapture_FFMPEG::retrieveFrame(int flag, unsigned char** data, int* step, *step = frame.step; *width = frame.width; *height = frame.height; - *cn = frame.cn; #if USE_AV_HW_CODECS if (sw_picture != picture) @@ -1699,7 +1712,7 @@ double CvCapture_FFMPEG::getProperty( int property_id ) const return (double)av_get_picture_type_char(picture->pict_type); case CAP_PROP_FPS: return get_fps(); - case CAP_PROP_FOURCC: + case CAP_PROP_FOURCC: { codec_id = video_st->CV_FFMPEG_CODEC_FIELD->codec_id; codec_tag = (double) video_st->CV_FFMPEG_CODEC_FIELD->codec_tag; @@ -1709,12 +1722,26 @@ double CvCapture_FFMPEG::getProperty( int property_id ) const } codec_fourcc = _opencv_avcodec_get_name(codec_id); - if(!codec_fourcc || strlen(codec_fourcc) < 4 || strcmp(codec_fourcc, "unknown_codec") == 0) + if (!codec_fourcc || strcmp(codec_fourcc, "unknown_codec") == 0 || strlen(codec_fourcc) != 4) { - return codec_tag; + const struct AVCodecTag* fallback_tags[] = { + // APIchanges: + // 2012-01-31 - dd6d3b0 - lavf 54.01.0 + // Add avformat_get_riff_video_tags() and avformat_get_riff_audio_tags(). + avformat_get_riff_video_tags(), +#if LIBAVFORMAT_BUILD >= CALC_FFMPEG_VERSION(55, 25, 100) && defined LIBAVFORMAT_VERSION_MICRO && LIBAVFORMAT_VERSION_MICRO >= 100 + // APIchanges: ffmpeg only + // 2014-01-19 - 1a193c4 - lavf 55.25.100 - avformat.h + // Add avformat_get_mov_video_tags() and avformat_get_mov_audio_tags(). + avformat_get_mov_video_tags(), +#endif + codec_bmp_tags, // fallback for avformat < 54.1 + NULL }; + return av_codec_get_tag(fallback_tags, codec_id); } return (double) CV_FOURCC(codec_fourcc[0], codec_fourcc[1], codec_fourcc[2], codec_fourcc[3]); + } case CAP_PROP_SAR_NUM: return _opencv_ffmpeg_get_sample_aspect_ratio(ic->streams[video_stream]).num; case CAP_PROP_SAR_DEN: @@ -1733,6 +1760,8 @@ double CvCapture_FFMPEG::getProperty( int property_id ) const if (rawMode) return -1; break; + case CAP_PROP_CONVERT_RGB: + return convertRGB; case CAP_PROP_LRF_HAS_KEY_FRAME: { const AVPacket& p = bsfc ? packet_filtered : packet; return ((p.flags & AV_PKT_FLAG_KEY) != 0) ? 1 : 0; @@ -1760,6 +1789,8 @@ double CvCapture_FFMPEG::getProperty( int property_id ) const case CAP_PROP_STREAM_OPEN_TIME_USEC: //ic->start_time_realtime is in microseconds return ((double)ic->start_time_realtime); + case CAP_PROP_N_THREADS: + return static_cast(context->thread_count); default: break; } @@ -1949,6 +1980,9 @@ bool CvCapture_FFMPEG::setProperty( int property_id, double value ) if (value == -1) return setRaw(); return false; + case CAP_PROP_CONVERT_RGB: + convertRGB = (value != 0); + return true; case CAP_PROP_ORIENTATION_AUTO: #if LIBAVUTIL_BUILD >= CALC_FFMPEG_VERSION(52, 94, 100) rotation_auto = value != 0 ? true : false; @@ -2183,7 +2217,6 @@ static AVCodecContext * icv_configure_video_stream_FFMPEG(AVFormatContext *oc, c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = pixel_format; - if (c->codec_id == CV_CODEC(CODEC_ID_MPEG2VIDEO)) { c->max_b_frames = 2; } @@ -2316,12 +2349,15 @@ bool CvVideoWriter_FFMPEG::writeFrame( const unsigned char* data, int step, int return false; } } - else if (input_pix_fmt == AV_PIX_FMT_GRAY8) { + else if (input_pix_fmt == AV_PIX_FMT_GRAY8 || input_pix_fmt == AV_PIX_FMT_GRAY16LE) { if (cn != 1) { return false; } } else { + CV_LOG_WARNING(NULL, "Input data does not match selected pixel format: " + << av_get_pix_fmt_name(input_pix_fmt) + << ", number of channels: " << cn); CV_Assert(false); } @@ -2621,6 +2657,14 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, close(); const bool is_color = params.get(VIDEOWRITER_PROP_IS_COLOR, true); + const int depth = params.get(VIDEOWRITER_PROP_DEPTH, CV_8U); + const bool is_supported = depth == CV_8U || (depth == CV_16U && !is_color); + if (!is_supported) + { + CV_LOG_WARNING(NULL, "Unsupported depth/isColor combination is selected, " + "only CV_8UC1/CV_8UC3/CV_16UC1 are supported."); + return false; + } if (params.has(VIDEOWRITER_PROP_HW_ACCELERATION)) { va_type = params.get(VIDEOWRITER_PROP_HW_ACCELERATION, VIDEO_ACCELERATION_NONE); @@ -2678,12 +2722,28 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, return false; /* determine optimal pixel format */ - if (is_color) { - input_pix_fmt = AV_PIX_FMT_BGR24; + if (is_color) + { + switch (depth) + { + case CV_8U: input_pix_fmt = AV_PIX_FMT_BGR24; break; + default: + CV_LOG_WARNING(NULL, "Unsupported input depth for color image: " << depth); + return false; + } } - else { - input_pix_fmt = AV_PIX_FMT_GRAY8; + else + { + switch (depth) + { + case CV_8U: input_pix_fmt = AV_PIX_FMT_GRAY8; break; + case CV_16U: input_pix_fmt = AV_PIX_FMT_GRAY16LE; break; + default: + CV_LOG_WARNING(NULL, "Unsupported input depth for grayscale image: " << depth); + return false; + } } + CV_LOG_DEBUG(NULL, "Selected pixel format: " << av_get_pix_fmt_name(input_pix_fmt)); if (fourcc == -1) { @@ -2888,7 +2948,9 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, AVDictionary *dict = NULL; #if !defined(NO_GETENV) && (LIBAVUTIL_VERSION_MAJOR >= 53) char* options = getenv("OPENCV_FFMPEG_WRITER_OPTIONS"); - if (options) { + if (options) + { + CV_LOG_DEBUG(NULL, "VIDEOIO/FFMPEG: using writer options from environment: " << options); av_dict_parse_string(&dict, options, ";", "|", 0); } #endif @@ -3121,7 +3183,13 @@ int cvGrabFrame_FFMPEG(CvCapture_FFMPEG* capture) int cvRetrieveFrame_FFMPEG(CvCapture_FFMPEG* capture, unsigned char** data, int* step, int* width, int* height, int* cn) { - return capture->retrieveFrame(0, data, step, width, height, cn); + int depth = CV_8U; + return cvRetrieveFrame2_FFMPEG(capture, data, step, width, height, cn, &depth); +} + +int cvRetrieveFrame2_FFMPEG(CvCapture_FFMPEG* capture, unsigned char** data, int* step, int* width, int* height, int* cn, int* depth) +{ + return capture->retrieveFrame(0, data, step, width, height, cn, depth); } static CvVideoWriter_FFMPEG* cvCreateVideoWriterWithParams_FFMPEG( const char* filename, int fourcc, double fps, diff --git a/modules/videoio/src/cap_ffmpeg_legacy_api.hpp b/modules/videoio/src/cap_ffmpeg_legacy_api.hpp index 09ef0fb203..3db29090f0 100644 --- a/modules/videoio/src/cap_ffmpeg_legacy_api.hpp +++ b/modules/videoio/src/cap_ffmpeg_legacy_api.hpp @@ -31,6 +31,8 @@ OPENCV_FFMPEG_API double cvGetCaptureProperty_FFMPEG(struct CvCapture_FFMPEG* ca OPENCV_FFMPEG_API int cvGrabFrame_FFMPEG(struct CvCapture_FFMPEG* cap); OPENCV_FFMPEG_API int cvRetrieveFrame_FFMPEG(struct CvCapture_FFMPEG* capture, unsigned char** data, int* step, int* width, int* height, int* cn); +OPENCV_FFMPEG_API int cvRetrieveFrame2_FFMPEG(struct CvCapture_FFMPEG* capture, unsigned char** data, + int* step, int* width, int* height, int* cn, int* depth); OPENCV_FFMPEG_API void cvReleaseCapture_FFMPEG(struct CvCapture_FFMPEG** cap); OPENCV_FFMPEG_API struct CvVideoWriter_FFMPEG* cvCreateVideoWriter_FFMPEG(const char* filename, diff --git a/modules/videoio/src/cap_gstreamer.cpp b/modules/videoio/src/cap_gstreamer.cpp index a871b98650..fc031d2b5f 100644 --- a/modules/videoio/src/cap_gstreamer.cpp +++ b/modules/videoio/src/cap_gstreamer.cpp @@ -55,6 +55,9 @@ #include #include #include +#include +#include +#include #include #include @@ -77,6 +80,8 @@ #define COLOR_ELEM_NAME COLOR_ELEM #define CV_GST_FORMAT(format) (format) +#define GSTREAMER_INTERRUPT_OPEN_DEFAULT_TIMEOUT_NS (30 * GST_SECOND) +#define GSTREAMER_INTERRUPT_READ_DEFAULT_TIMEOUT_NS (30 * GST_SECOND) namespace cv { @@ -128,6 +133,7 @@ protected: T* ptr; public: inline GSafePtr() CV_NOEXCEPT : ptr(NULL) { } + inline GSafePtr(T* p) : ptr(p) { } inline ~GSafePtr() CV_NOEXCEPT { release(); } inline void release() CV_NOEXCEPT { @@ -316,27 +322,54 @@ static void find_hw_element(const GValue *item, gpointer va_type) class GStreamerCapture CV_FINAL : public IVideoCapture { private: + GSafePtr audiopipeline; GSafePtr pipeline; GSafePtr v4l2src; GSafePtr sink; - GSafePtr sample; - GSafePtr caps; + GSafePtr audiosink; + GSafePtr impendingVideoSample; + GSafePtr usedVideoSample; + GSafePtr audioSample; + GSafePtr caps; + GSafePtr audiocaps; + gint64 bufferedAudioDurationNS; + gint64 requiredAudioTimeNS; + gint64 impendingVideoSampleTimeNS; + gint64 usedVideoSampleTimeNS; + gint64 videoSampleDurationNS; + gint64 audioSampleDurationNS; + gint64 audioSampleTimeNS; + gint64 chunkLengthOfBytes; + gint64 givenAudioTimeNS; + gint64 numberOfAdditionalAudioBytes; + gint64 audioSamplePosInSamples; gint videoStream; gint audioStream; gint64 duration; gint width; gint height; double fps; + GstClockTime openTimeout; // measured in nanoseconds + GstClockTime readTimeout; // measured in nanoseconds bool isPosFramesSupported; bool isPosFramesEmulated; + bool vEOS; + bool aEOS; + bool syncLastFrame; + bool lastFrame; gint64 emulatedFrameNumber; gint outputAudioFormat; + gint audioBitPerSample; gint audioBaseIndex; gint nAudioChannels; gint audioSamplesPerSecond; + gint audioBitPerFrame; + gint audioSampleSize; + std::string audioFormat; Mat audioFrame; + std::deque bufferAudioData; VideoAccelerationType va_type; int hw_device; @@ -345,6 +378,8 @@ public: virtual ~GStreamerCapture() CV_OVERRIDE; virtual bool grabFrame() CV_OVERRIDE; virtual bool retrieveFrame(int /*unused*/, OutputArray dst) CV_OVERRIDE; + bool configureAudioFrame(); + bool grabVideoFrame(); bool grabAudioFrame(); bool retrieveVideoFrame(int /*unused*/, OutputArray dst); bool retrieveAudioFrame(int /*unused*/, OutputArray dst); @@ -356,7 +391,7 @@ public: bool open(const String &filename_, const cv::VideoCaptureParameters& params); static void newPad(GstElement * /*elem*/, GstPad *pad, gpointer data); bool configureHW(const cv::VideoCaptureParameters&); - bool configureStreams(const cv::VideoCaptureParameters&); + bool configureStreamsProperty(const cv::VideoCaptureParameters&); bool setAudioProperties(const cv::VideoCaptureParameters&); protected: @@ -369,20 +404,39 @@ protected: }; GStreamerCapture::GStreamerCapture() : + bufferedAudioDurationNS(0), + requiredAudioTimeNS(0), + impendingVideoSampleTimeNS(0), + usedVideoSampleTimeNS(0), + videoSampleDurationNS(0), audioSampleDurationNS(0), + audioSampleTimeNS(0), + chunkLengthOfBytes(0), + givenAudioTimeNS(0), + numberOfAdditionalAudioBytes(0), + audioSamplePosInSamples(0), videoStream(0), audioStream(-1), duration(-1), width(-1), height(-1), fps(-1), + openTimeout(GSTREAMER_INTERRUPT_OPEN_DEFAULT_TIMEOUT_NS), + readTimeout(GSTREAMER_INTERRUPT_READ_DEFAULT_TIMEOUT_NS), isPosFramesSupported(false), isPosFramesEmulated(false), + vEOS(false), + aEOS(false), + syncLastFrame(true), + lastFrame(false), emulatedFrameNumber(-1), outputAudioFormat(CV_16S), + audioBitPerSample(16), audioBaseIndex(1), nAudioChannels(0), - audioSamplesPerSecond(44100) + audioSamplesPerSecond(44100), + audioBitPerFrame(0), + audioSampleSize(0), + audioFormat("S16LE") , va_type(VIDEO_ACCELERATION_NONE) , hw_device(-1) -{ -} +{} /*! * \brief CvCapture_GStreamer::close @@ -427,7 +481,7 @@ bool GStreamerCapture::configureHW(const cv::VideoCaptureParameters& params) return true; } -bool GStreamerCapture::configureStreams(const cv::VideoCaptureParameters& params) +bool GStreamerCapture::configureStreamsProperty(const cv::VideoCaptureParameters& params) { if (params.has(CAP_PROP_VIDEO_STREAM)) { @@ -436,7 +490,7 @@ bool GStreamerCapture::configureStreams(const cv::VideoCaptureParameters& params videoStream = static_cast(value); else { - CV_LOG_ERROR(NULL, "VIDEOIO/MSMF: CAP_PROP_VIDEO_STREAM parameter value is invalid/unsupported: " << value); + CV_LOG_ERROR(NULL, "VIDEOIO/Gstreamer: CAP_PROP_VIDEO_STREAM parameter value is invalid/unsupported: " << value); return false; } } @@ -447,7 +501,7 @@ bool GStreamerCapture::configureStreams(const cv::VideoCaptureParameters& params audioStream = static_cast(value); else { - CV_LOG_ERROR(NULL, "VIDEOIO/MSMF: CAP_PROP_AUDIO_STREAM parameter value is invalid/unsupported: " << value); + CV_LOG_ERROR(NULL, "VIDEOIO/Gstreamer: CAP_PROP_AUDIO_STREAM parameter value is invalid/unsupported: " << value); return false; } } @@ -461,7 +515,7 @@ bool GStreamerCapture::setAudioProperties(const cv::VideoCaptureParameters& para gint value = static_cast(params.get(CAP_PROP_AUDIO_DATA_DEPTH)); if (value != CV_8S && value != CV_16S && value != CV_32S && value != CV_32F) { - CV_LOG_ERROR(NULL, "VIDEOIO/MSMF: CAP_PROP_AUDIO_DATA_DEPTH parameter value is invalid/unsupported: " << value); + CV_LOG_ERROR(NULL, "VIDEOIO/Gstreamer: CAP_PROP_AUDIO_DATA_DEPTH parameter value is invalid/unsupported: " << value); return false; } else @@ -474,7 +528,7 @@ bool GStreamerCapture::setAudioProperties(const cv::VideoCaptureParameters& para int value = static_cast(params.get(CAP_PROP_AUDIO_SAMPLES_PER_SECOND)); if (value < 0) { - CV_LOG_ERROR(NULL, "VIDEOIO/MSMF: CAP_PROP_AUDIO_SAMPLES_PER_SECOND parameter can't be negative: " << value); + CV_LOG_ERROR(NULL, "VIDEOIO/Gstreamer: CAP_PROP_AUDIO_SAMPLES_PER_SECOND parameter can't be negative: " << value); return false; } else @@ -482,6 +536,11 @@ bool GStreamerCapture::setAudioProperties(const cv::VideoCaptureParameters& para audioSamplesPerSecond = value; } } + if (params.has(CAP_PROP_AUDIO_SYNCHRONIZE)) + { + int value = static_cast(params.get(CAP_PROP_AUDIO_SYNCHRONIZE)); + syncLastFrame = (value != 0) ? true : false; + } return true; } @@ -499,112 +558,329 @@ bool GStreamerCapture::grabFrame() // start the pipeline if it was not in playing state yet if (!this->isPipelinePlaying()) this->startPipeline(); - // bail out if EOS - if (gst_app_sink_is_eos(GST_APP_SINK(sink.get()))) - return false; - sample.attach(gst_app_sink_pull_sample(GST_APP_SINK(sink.get()))); - if (!sample) + if (vEOS) + { return false; + } - if (isPosFramesEmulated) - emulatedFrameNumber++; + bool returnFlag = true; + if (videoStream >= 0) + { + if (!vEOS) + returnFlag &= grabVideoFrame(); + if (!returnFlag) + { + return false; + } + } if (audioStream >= 0) - return grabAudioFrame(); + { + bufferedAudioDurationNS = (gint64)(1e9*(((double)bufferAudioData.size()/((audioBitPerSample/8)*nAudioChannels))/audioSamplesPerSecond)); + audioFrame.release(); + if (!aEOS) + returnFlag &= grabAudioFrame(); + } - return true; + return returnFlag; +} + +bool GStreamerCapture::grabVideoFrame() +{ + usedVideoSample.release(); + + bool returnFlag = false; + bool stopFlag = false; + + if (audioStream != -1) + { + usedVideoSample.swap(impendingVideoSample); + std::swap(usedVideoSampleTimeNS, impendingVideoSampleTimeNS); + } + + while (!stopFlag) + { + if (gst_app_sink_is_eos(GST_APP_SINK(sink.get()))) + { + vEOS = true; + lastFrame = true; + stopFlag = true; + if (audioStream == -1) + { + returnFlag = false; + } + else if (usedVideoSample) + { + gst_element_query_position(sink.get(), CV_GST_FORMAT(GST_FORMAT_TIME), &impendingVideoSampleTimeNS); + videoSampleDurationNS = impendingVideoSampleTimeNS - usedVideoSampleTimeNS; + requiredAudioTimeNS = impendingVideoSampleTimeNS - givenAudioTimeNS; + givenAudioTimeNS += requiredAudioTimeNS; + returnFlag = true; + } + } + else + { + #if FULL_GST_VERSION >= VERSION_NUM(1,10,0) + impendingVideoSample.attach(gst_app_sink_try_pull_sample(GST_APP_SINK(sink.get()), readTimeout)); + #else + impendingVideoSample.attach(gst_app_sink_pull_sample(GST_APP_SINK(sink.get()))); + #endif + if (!impendingVideoSample) + { + if (!gst_app_sink_is_eos(GST_APP_SINK(sink.get()))) + { + CV_LOG_DEBUG(NULL, "videoio(Gstreamer): gst_app_sink_pull_sample() method is not succeeded"); + } + else + { + vEOS = true; + lastFrame = true; + stopFlag = true; + if (audioStream == -1) + { + return false; + } + else if (usedVideoSample) + { + gst_element_query_position(sink.get(), CV_GST_FORMAT(GST_FORMAT_TIME), &impendingVideoSampleTimeNS); + videoSampleDurationNS = impendingVideoSampleTimeNS - usedVideoSampleTimeNS; + requiredAudioTimeNS = impendingVideoSampleTimeNS - givenAudioTimeNS; + givenAudioTimeNS += requiredAudioTimeNS; + returnFlag = true; + } + } + } + gst_element_query_position(sink.get(), CV_GST_FORMAT(GST_FORMAT_TIME), &impendingVideoSampleTimeNS); + + if (audioStream != -1) + { + if (!usedVideoSample) + { + usedVideoSample.swap(impendingVideoSample); + std::swap(usedVideoSampleTimeNS, impendingVideoSampleTimeNS); + } + else + { + stopFlag = true; + } + if (impendingVideoSample) + { + emulatedFrameNumber++; + } + videoSampleDurationNS = impendingVideoSampleTimeNS - usedVideoSampleTimeNS; + requiredAudioTimeNS = impendingVideoSampleTimeNS - givenAudioTimeNS; + givenAudioTimeNS += requiredAudioTimeNS; + } + else + { + usedVideoSample.swap(impendingVideoSample); + std::swap(usedVideoSampleTimeNS, impendingVideoSampleTimeNS); + stopFlag = true; + emulatedFrameNumber++; + } + returnFlag = true; + } + } + return returnFlag; } bool GStreamerCapture::grabAudioFrame() { - GstCaps* frame_caps = gst_sample_get_caps(sample); // no lifetime transfer - if (!frame_caps) + audioSample.reset(NULL); + + bool returnFlag = false; + gint64 audioTimeNS = bufferedAudioDurationNS; + + if (bufferedAudioDurationNS > requiredAudioTimeNS) { - CV_LOG_ERROR(NULL, "GStreamer: gst_sample_get_caps() returns NULL"); - return false; + return true; } - if (!GST_CAPS_IS_SIMPLE(frame_caps)) + while ((!vEOS) ? audioTimeNS <= requiredAudioTimeNS : !aEOS) { - // bail out in no caps - CV_LOG_ERROR(NULL, "GStreamer: GST_CAPS_IS_SIMPLE(frame_caps) check is failed"); - return false; - } - - GstAudioInfo info = {}; - gboolean audio_info_res = gst_audio_info_from_caps(&info, frame_caps); - if (!audio_info_res) - { - CV_Error(Error::StsError, "GStreamer: gst_audio_info_from_caps() is failed. Can't handle unknown layout"); - } - int bpf = GST_AUDIO_INFO_BPF(&info); - CV_CheckGT(bpf, 0, ""); - - GstStructure* structure = gst_caps_get_structure(frame_caps, 0); // no lifetime transfer - if (!structure) - { - CV_LOG_ERROR(NULL, "GStreamer: Can't query 'structure'-0 from GStreamer sample"); - return false; - } - - const gchar* name_ = gst_structure_get_name(structure); - if (!name_) - { - CV_LOG_ERROR(NULL, "GStreamer: Can't query 'name' from GStreamer sample"); - return false; - } - std::string name = toLowerCase(std::string(name_)); - - GstBuffer* buf = gst_sample_get_buffer(sample); - if (!buf) - return false; - GstMapInfo map_info = {}; - if (!gst_buffer_map(buf, &map_info, GST_MAP_READ)) - { - CV_LOG_ERROR(NULL, "GStreamer: Failed to map GStreamer buffer to system memory"); - return false; - } - ScopeGuardGstMapInfo map_guard(buf, &map_info); - if (name == "audio/x-raw") - { - const gchar* format_ = gst_structure_get_string(structure, "format"); - if (!format_) + if (gst_app_sink_is_eos(GST_APP_SINK(audiosink.get()))) { - CV_LOG_ERROR(NULL, "GStreamer: Can't query 'format' of 'video/x-raw'"); - return false; + aEOS = true; + if (videoStream != -1 && !vEOS) + returnFlag = true; + if (videoStream == -1) + audioSamplePosInSamples += chunkLengthOfBytes/((audioBitPerSample/8)*nAudioChannels); + break; } - std::string format = toUpperCase(std::string(format_)); - cv::Mat data; - if (format == "S8") + else { - Mat(map_info.size/bpf, nAudioChannels, CV_8S, map_info.data).copyTo(audioFrame); - return true; + audioSample.attach(gst_app_sink_pull_sample(GST_APP_SINK(audiosink.get()))); + if (!audioSample) + { + if (!gst_app_sink_is_eos(GST_APP_SINK(audiosink.get()))) + { + CV_LOG_ERROR(NULL, "videoio(Gstreamer): gst_app_sink_pull_sample() method is not succeeded"); + return false; + } + else + { + aEOS = true; + break; + } + } + gst_element_query_position(audiosink.get(), CV_GST_FORMAT(GST_FORMAT_TIME), &audioSampleTimeNS); + + GstMapInfo map_info = {}; + GstCaps* frame_caps = gst_sample_get_caps(audioSample.get()); // no lifetime transfer + if (!frame_caps) + { + CV_LOG_ERROR(NULL, "GStreamer: gst_sample_get_caps() returns NULL"); + return false; + } + if (!GST_CAPS_IS_SIMPLE(frame_caps)) + { + // bail out in no caps + CV_LOG_ERROR(NULL, "GStreamer: GST_CAPS_IS_SIMPLE(frame_caps) check is failed"); + return false; + } + GstAudioInfo info = {}; + gboolean audio_info_res = gst_audio_info_from_caps(&info, frame_caps); + if (!audio_info_res) + { + CV_Error(Error::StsError, "GStreamer: gst_audio_info_from_caps() is failed. Can't handle unknown layout"); + } + audioBitPerFrame = GST_AUDIO_INFO_BPF(&info); + CV_CheckGT(audioBitPerFrame, 0, ""); + + GstStructure* structure = gst_caps_get_structure(frame_caps, 0); // no lifetime transfer + if (!structure) + { + CV_LOG_ERROR(NULL, "GStreamer: Can't query 'structure'-0 from GStreamer sample"); + return false; + } + + const gchar* name_ = gst_structure_get_name(structure); + if (!name_) + { + CV_LOG_ERROR(NULL, "GStreamer: Can't query 'name' from GStreamer sample"); + return false; + } + std::string name = toLowerCase(std::string(name_)); + if (name != "audio/x-raw") + { + CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer layer type: %s", name.c_str())); + return false; + } + + const gchar* format_ = gst_structure_get_string(structure, "format"); + if (!format_) + { + CV_LOG_ERROR(NULL, "GStreamer: Can't query 'format' of 'audio/x-raw'"); + return false; + } + std::string format = toUpperCase(std::string(format_)); + if (format != "S8" && format != "S16LE" && format != "S32LE" && format != "F32LE") + { + CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer audio format: %s", format.c_str())); + return false; + } + if (format != audioFormat) + { + return false; + } + + GstBuffer* buf = gst_sample_get_buffer(audioSample); + if (!buf) + { + CV_LOG_ERROR(NULL, "GStreamer: Failed. Buffer is empty"); + return false; + } + if (!gst_buffer_map(buf, &map_info, GST_MAP_READ)) + { + CV_LOG_ERROR(NULL, "GStreamer: Failed to map GStreamer buffer to system memory"); + return false; + } + ScopeGuardGstMapInfo map_guard(buf, &map_info); + gsize lastSize = bufferAudioData.size(); + audioSampleSize = map_info.size; + bufferAudioData.resize(lastSize+audioSampleSize); + for (gint j = 0; j < audioSampleSize; j++) + { + bufferAudioData[lastSize+j]=*(map_info.data + j); + } + audioSampleDurationNS = 1e9*(((double)audioSampleSize/((audioBitPerSample/8)*nAudioChannels))/audioSamplesPerSecond); + + CV_LOG_DEBUG(NULL, "videoio(Gstreamer): got audio frame with timestamp=" << audioSampleTimeNS << " duration=" << audioSampleDurationNS); + audioTimeNS += (int64_t)(audioSampleDurationNS); + + returnFlag = true; } - if (format == "S16LE") - { - Mat(map_info.size/bpf, nAudioChannels, CV_16S, map_info.data).copyTo(audioFrame); - return true; - } - if (format == "S32LE") - { - Mat(map_info.size/bpf, nAudioChannels, CV_32S, map_info.data).copyTo(audioFrame); - return true; - } - if (format == "F32LE") - { - Mat(map_info.size/bpf, nAudioChannels, CV_32F, map_info.data).copyTo(audioFrame); - return true; - } - CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer audio format: %s", format.c_str())); } - CV_Error_(Error::StsNotImplemented, ("Unsupported GStreamer layer type: %s", name.c_str())); + returnFlag &= configureAudioFrame(); + return returnFlag; +} + +bool GStreamerCapture::configureAudioFrame() +{ + if (bufferAudioData.empty() && aEOS) + { + return false; + } + std::vector audioDataInUse; + + audioSamplePosInSamples += chunkLengthOfBytes/((audioBitPerSample/8)*nAudioChannels); + chunkLengthOfBytes = (videoStream != -1) ? (int64_t)((requiredAudioTimeNS * 1e-9 * audioSamplesPerSecond*nAudioChannels*(audioBitPerSample)/8)) : audioSampleSize; + if ((videoStream != -1) && (chunkLengthOfBytes % ((int)(audioBitPerSample)/8* (int)nAudioChannels) != 0)) + { + if ( (double)audioSamplePosInSamples/audioSamplesPerSecond - usedVideoSampleTimeNS * 1e-9 >= 0 ) + chunkLengthOfBytes -= numberOfAdditionalAudioBytes; + numberOfAdditionalAudioBytes = ((int)(audioBitPerSample)/8* (int)nAudioChannels) + - chunkLengthOfBytes % ((int)(audioBitPerSample)/8* (int)nAudioChannels); + chunkLengthOfBytes += numberOfAdditionalAudioBytes; + } + if ((lastFrame && !syncLastFrame) || (aEOS && !vEOS)) + { + chunkLengthOfBytes = bufferAudioData.size(); + audioSamplePosInSamples += chunkLengthOfBytes/((audioBitPerSample/8)*nAudioChannels); + } + CV_Check((double)chunkLengthOfBytes, chunkLengthOfBytes >= INT_MIN || chunkLengthOfBytes <= INT_MAX, "GSTREAMER: The chunkLengthOfBytes is out of the allowed range"); + copy(bufferAudioData.begin(), bufferAudioData.begin() + (int)chunkLengthOfBytes, std::back_inserter(audioDataInUse)); + bufferAudioData.erase(bufferAudioData.begin(), bufferAudioData.begin() + (int)chunkLengthOfBytes); + + cv::Mat data; + + if (audioFormat == "S8") + { + Mat((int)chunkLengthOfBytes/audioBitPerFrame, nAudioChannels, CV_8S, audioDataInUse.data()).copyTo(audioFrame); + return true; + } + if (audioFormat == "S16LE") + { + Mat((int)chunkLengthOfBytes/audioBitPerFrame, nAudioChannels, CV_16S, audioDataInUse.data()).copyTo(audioFrame); + return true; + } + if (audioFormat == "S32LE") + { + Mat((int)chunkLengthOfBytes/audioBitPerFrame, nAudioChannels, CV_32S, audioDataInUse.data()).copyTo(audioFrame); + return true; + } + if (audioFormat == "F32LE") + { + Mat((int)chunkLengthOfBytes/audioBitPerFrame, nAudioChannels, CV_32F, audioDataInUse.data()).copyTo(audioFrame); + return true; + } + + audioDataInUse.clear(); + audioDataInUse.shrink_to_fit(); + return true; } bool GStreamerCapture::retrieveAudioFrame(int index, OutputArray dst) { + if (audioFrame.empty()) + { + dst.release(); + if (aEOS) + return true; + } CV_Check(index, index >= audioBaseIndex && index < audioBaseIndex + nAudioChannels, ""); index -= audioBaseIndex; @@ -643,7 +919,7 @@ bool GStreamerCapture::retrieveAudioFrame(int index, OutputArray dst) bool GStreamerCapture::retrieveVideoFrame(int, OutputArray dst) { - GstCaps* frame_caps = gst_sample_get_caps(sample); // no lifetime transfer + GstCaps* frame_caps = gst_sample_get_caps(usedVideoSample); // no lifetime transfer if (!frame_caps) { CV_LOG_ERROR(NULL, "GStreamer: gst_sample_get_caps() returns NULL"); @@ -666,7 +942,7 @@ bool GStreamerCapture::retrieveVideoFrame(int, OutputArray dst) // gstreamer expects us to handle the memory at this point // so we can just wrap the raw buffer and be done with it - GstBuffer* buf = gst_sample_get_buffer(sample); // no lifetime transfer + GstBuffer* buf = gst_sample_get_buffer(usedVideoSample); // no lifetime transfer if (!buf) return false; @@ -675,7 +951,12 @@ bool GStreamerCapture::retrieveVideoFrame(int, OutputArray dst) // the data. The gst_video_frame_map will parse the meta for us, or default to // 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); +#else + GstMapFlags flags = static_cast(GST_MAP_READ); +#endif + if (!gst_video_frame_map(&frame, &info, buf, flags)) { CV_LOG_ERROR(NULL, "GStreamer: Failed to map GStreamer buffer to system memory"); @@ -844,18 +1125,32 @@ bool GStreamerCapture::retrieveFrame(int index, OutputArray dst) { if (index < 0) return false; - if (!sample) - return false; - if (index == 0) + if ((gint)index < audioBaseIndex) { - CV_CheckGE(videoStream, 0, "No video stream configured"); - return retrieveVideoFrame(index, dst); + if (videoStream == -1) + { + dst.release(); + return false; + } + else + { + CV_CheckGE(videoStream, 0, "No video stream configured"); + return retrieveVideoFrame(index, dst); + } } - else if (index >= audioBaseIndex) + else { - CV_CheckGE(audioStream, 0, "No audio stream configured"); - return retrieveAudioFrame(index, dst); + if (audioStream == -1) + { + dst.release(); + return false; + } + else + { + CV_CheckGE(audioStream, 0, "No audio stream configured"); + return retrieveAudioFrame(index, dst); + } } CV_LOG_ERROR(NULL, "GStreamer(retrive): unrecognized index=" << index); @@ -895,7 +1190,7 @@ void GStreamerCapture::startPipeline() if (status == GST_STATE_CHANGE_ASYNC) { // wait for status update - status = gst_element_get_state(pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); + status = gst_element_get_state(pipeline, NULL, NULL, openTimeout); } if (status == GST_STATE_CHANGE_FAILURE) { @@ -1069,17 +1364,12 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam return false; } - if (!configureStreams(params)) + if (!configureStreamsProperty(params)) { CV_LOG_WARNING(NULL, "GStreamer: can't configure streams"); return false; } - if ((videoStream >= 0 && audioStream >= 0) || (videoStream < 0 && audioStream < 0)) - { - CV_LOG_ERROR(NULL, "GStreamer backend supports audio-only or video-only capturing. Only one of the properties CAP_PROP_AUDIO_STREAM=" << audioStream << " and CAP_PROP_VIDEO_STREAM=" << videoStream << " should be >= 0"); - return false; - } if (audioStream > 0) { CV_LOG_ERROR(NULL, "GStreamer backend supports the first audio stream only. CAP_PROP_AUDIO_STREAM=" << audioStream); @@ -1097,13 +1387,15 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam bool file = false; bool manualpipeline = false; GSafePtr uri; + GSafePtr bus; + GSafePtr uridecodebin; GSafePtr color; - GstStateChangeReturn status; - GSafePtr convert; GSafePtr resample; + GstStateChangeReturn status; + // test if we have a valid uri. If so, open it with an uridecodebin // else, we might have a file or a manual pipeline. // if gstreamer cannot parse the manual pipeline, we assume we were given and @@ -1166,6 +1458,11 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam CV_Assert(uridecodebin); g_object_set(G_OBJECT(uridecodebin.get()), "uri", uri.get(), NULL); } + if (!uridecodebin) + { + CV_WARN("Can not parse GStreamer URI bin"); + return false; + } } else if (audioStream >= 0) { @@ -1173,12 +1470,6 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam CV_Assert(uridecodebin); g_object_set(G_OBJECT(uridecodebin.get()), "uri", uri.get(), NULL); } - - if (!uridecodebin) - { - CV_WARN("Can not parse GStreamer URI bin"); - return false; - } } if (manualpipeline) @@ -1241,10 +1532,10 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam pipeline.reset(gst_pipeline_new(NULL)); CV_Assert(pipeline); - sink.reset(gst_element_factory_make("appsink", NULL)); - CV_Assert(sink); if (videoStream >= 0) { + sink.reset(gst_element_factory_make("appsink", NULL)); + CV_Assert(sink); // videoconvert (in 0.10: ffmpegcolorspace, in 1.x autovideoconvert) //automatically selects the correct colorspace conversion based on caps. color.reset(gst_element_factory_make(COLOR_ELEM, NULL)); @@ -1273,13 +1564,17 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam return false; } } - else if (audioStream >= 0) + if (audioStream >= 0) { convert.reset(gst_element_factory_make("audioconvert", NULL)); resample.reset(gst_element_factory_make("audioresample", NULL)); + audiosink.reset(gst_element_factory_make("appsink", NULL)); + CV_Assert(convert); + CV_Assert(resample); + CV_Assert(audiosink); - gst_bin_add_many (GST_BIN (pipeline.get()), uridecodebin.get(), convert.get(), resample.get(), sink.get(), NULL); - if (!gst_element_link_many (convert.get(), resample.get(), sink.get(), NULL)) + gst_bin_add_many (GST_BIN (pipeline.get()), uridecodebin.get(), convert.get(), resample.get(), audiosink.get(), NULL); + if (!gst_element_link_many (convert.get(), resample.get(), audiosink.get(), NULL)) { CV_WARN("GStreamer(audio): cannot link convert -> resample -> sink"); pipeline.release(); @@ -1292,68 +1587,89 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam if (!manualpipeline || strstr(filename, " max-buffers=") == NULL) { //TODO: is 1 single buffer really high enough? - gst_app_sink_set_max_buffers(GST_APP_SINK(sink.get()), 1); + if (videoStream >= 0) + gst_app_sink_set_max_buffers(GST_APP_SINK(sink.get()), 1); + if (audioStream >= 0) + gst_app_sink_set_max_buffers(GST_APP_SINK(audiosink.get()), 1); } if (!manualpipeline) { - gst_base_sink_set_sync(GST_BASE_SINK(sink.get()), FALSE); + if (videoStream >= 0) + gst_base_sink_set_sync(GST_BASE_SINK(sink.get()), FALSE); + if (audioStream >= 0) + gst_base_sink_set_sync(GST_BASE_SINK(audiosink.get()), FALSE); } - //do not emit signals: all calls will be synchronous and blocking - gst_app_sink_set_emit_signals (GST_APP_SINK(sink.get()), FALSE); - if (videoStream >= 0) { + //do not emit signals: all calls will be synchronous and blocking + gst_app_sink_set_emit_signals (GST_APP_SINK(sink.get()), FALSE); caps.attach(gst_caps_from_string("video/x-raw, format=(string){BGR, GRAY8}; video/x-bayer,format=(string){rggb,bggr,grbg,gbrg}; image/jpeg")); } - else if (audioStream >= 0) + if (audioStream >= 0) { - std::string audioFormat; + gst_app_sink_set_emit_signals(GST_APP_SINK(audiosink.get()), FALSE); switch (outputAudioFormat) { case CV_8S: + { + audioBitPerSample = 8; audioFormat = "S8"; break; + } case CV_16S: + { + audioBitPerSample = 16; audioFormat = "S16LE"; break; + } case CV_32S: + { + audioBitPerSample = 32; audioFormat = "S32LE"; break; + } case CV_32F: + { + audioBitPerSample = 32; audioFormat = "F32LE"; break; + } default: audioFormat = "S16LE"; break; } std::string stringCaps = "audio/x-raw, format=(string)" + audioFormat + ", rate=(int)" + std::to_string(audioSamplesPerSecond) + ", channels=(int){1, 2}, layout=(string)interleaved"; - caps.attach(gst_caps_from_string(stringCaps.c_str())); - } + audiocaps.attach(gst_caps_from_string(stringCaps.c_str())); + gst_app_sink_set_caps(GST_APP_SINK(audiosink.get()), audiocaps); + audiocaps.release(); + } if (manualpipeline) { GSafePtr peer_caps; GSafePtr sink_pad; sink_pad.attach(gst_element_get_static_pad(sink, "sink")); peer_caps.attach(gst_pad_peer_query_caps(sink_pad, NULL)); - if (!gst_caps_can_intersect(caps, peer_caps)) { + if (!gst_caps_can_intersect(caps, peer_caps)) + { caps.attach(gst_caps_from_string("video/x-raw, format=(string){UYVY,YUY2,YVYU,NV12,NV21,YV12,I420,BGRA,RGBA,BGRx,RGBx,GRAY16_LE,GRAY16_BE}")); CV_Assert(caps); } } - - gst_app_sink_set_caps(GST_APP_SINK(sink.get()), caps); - caps.release(); - + if (videoStream >= 0) + { + gst_app_sink_set_caps(GST_APP_SINK(sink.get()), caps); + caps.release(); + } { GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pipeline.get()), GST_DEBUG_GRAPH_SHOW_ALL, "pipeline-init"); status = gst_element_set_state(GST_ELEMENT(pipeline.get()), - file ? GST_STATE_PAUSED : GST_STATE_PLAYING); + file ? GST_STATE_PAUSED : GST_STATE_PLAYING); if (status == GST_STATE_CHANGE_ASYNC) { // wait for status update - status = gst_element_get_state(pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); + status = gst_element_get_state(pipeline, NULL, NULL, openTimeout); } if (status == GST_STATE_CHANGE_FAILURE) { @@ -1364,14 +1680,14 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam return false; } - GSafePtr pad; - pad.attach(gst_element_get_static_pad(sink, "sink")); - - GSafePtr buffer_caps; - buffer_caps.attach(gst_pad_get_current_caps(pad)); - if (videoStream >= 0) { + GSafePtr pad; + pad.attach(gst_element_get_static_pad(sink, "sink")); + + GSafePtr buffer_caps; + buffer_caps.attach(gst_pad_get_current_caps(pad)); + GstFormat format; format = GST_FORMAT_DEFAULT; @@ -1418,8 +1734,15 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam isPosFramesSupported = true; } } - else if (audioStream >= 0) + + if (audioStream >= 0) { + GSafePtr pad; + pad.attach(gst_element_get_static_pad(audiosink, "sink")); + + GSafePtr buffer_caps; + buffer_caps.attach(gst_pad_get_current_caps(pad)); + GstAudioInfo info = {}; if (gst_audio_info_from_caps(&info, buffer_caps)) { @@ -1435,8 +1758,10 @@ bool GStreamerCapture::open(const String &filename_, const cv::VideoCaptureParam } std::vector unused_params = params.getUnused(); - for (int key : unused_params) { - if (!setProperty(key, params.get(key))) { + for (int key : unused_params) + { + if (!setProperty(key, params.get(key))) + { CV_LOG_ERROR(NULL, "VIDEOIO/GStreamer: can't set property " << key); return false; } @@ -1491,6 +1816,10 @@ double GStreamerCapture::getProperty(int propId) const case CV_CAP_PROP_POS_MSEC: CV_LOG_ONCE_WARNING(NULL, "OpenCV | GStreamer: CAP_PROP_POS_MSEC property result may be unrealiable: " "https://github.com/opencv/opencv/issues/19025"); + if (audioStream != -1) + { + return usedVideoSampleTimeNS * 1e-6; + } format = GST_FORMAT_TIME; status = gst_element_query_position(sink.get(), CV_GST_FORMAT(format), &value); if(!status) { @@ -1565,6 +1894,22 @@ double GStreamerCapture::getProperty(int propId) const return outputAudioFormat; case CAP_PROP_AUDIO_BASE_INDEX: return audioBaseIndex; + case CAP_PROP_AUDIO_TOTAL_STREAMS: + CV_LOG_ONCE_WARNING(NULL, "OpenCV | GStreamer: CAP_PROP_AUDIO_TOTAL_STREAMS property is not supported"); + return 0; + case CAP_PROP_AUDIO_POS: + return audioSamplePosInSamples; + case CAP_PROP_AUDIO_SHIFT_NSEC: + CV_LOG_ONCE_WARNING(NULL, "OpenCV | GStreamer: CAP_PROP_AUDIO_SHIFT_NSEC property is not supported"); + return 0; + case CAP_PROP_OPEN_TIMEOUT_MSEC: + return GST_TIME_AS_MSECONDS(openTimeout); + case CAP_PROP_READ_TIMEOUT_MSEC: +#if FULL_GST_VERSION >= VERSION_NUM(1,10,0) + return GST_TIME_AS_MSECONDS(readTimeout); +#else + return 0; +#endif default: CV_WARN("unhandled property: " << propId); break; @@ -1719,6 +2064,37 @@ bool GStreamerCapture::setProperty(int propId, double value) gst_app_sink_set_max_buffers(GST_APP_SINK(sink.get()), (guint) value); return true; } + case CAP_PROP_OPEN_TIMEOUT_MSEC: + { + if(value > 0) + { + openTimeout = GstClockTime(value * GST_MSECOND); // convert from ms to ns + return true; + } + else + { + CV_WARN("GStreamer open timeout should be positive"); + return false; + } + } + case CAP_PROP_READ_TIMEOUT_MSEC: + { +#if FULL_GST_VERSION >= VERSION_NUM(1,10,0) + if(value > 0) + { + readTimeout = GstClockTime(value * GST_MSECOND); // convert from ms to ns + return true; + } + else + { + CV_WARN("GStreamer read timeout should be positive"); + return false; + } +#else + CV_WARN("GStreamer before 1.10 does not support read timeout"); + return false; +#endif + } default: CV_WARN("GStreamer: unhandled property"); } diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index ebc07d77b0..d9a1148e90 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -243,27 +243,92 @@ public: //=================================================== +// Utility + +static inline void applyMetadataRotation(const IVideoCapture& cap, OutputArray mat) +{ + bool rotation_auto = 0 != cap.getProperty(CAP_PROP_ORIENTATION_AUTO); + int rotation_angle = static_cast(cap.getProperty(CAP_PROP_ORIENTATION_META)); + + if(!rotation_auto || rotation_angle%360 == 0) + { + return; + } + + cv::RotateFlags flag; + if(rotation_angle == 90 || rotation_angle == -270) { // Rotate clockwise 90 degrees + flag = cv::ROTATE_90_CLOCKWISE; + } else if(rotation_angle == 270 || rotation_angle == -90) { // Rotate clockwise 270 degrees + flag = cv::ROTATE_90_COUNTERCLOCKWISE; + } else if(rotation_angle == 180 || rotation_angle == -180) { // Rotate clockwise 180 degrees + flag = cv::ROTATE_180; + } else { // Unsupported rotation + return; + } + + cv::rotate(mat, mat, flag); +} + +//=================================================== + // Wrapper class LegacyCapture : public IVideoCapture { private: CvCapture * cap; + bool autorotate; LegacyCapture(const LegacyCapture &); LegacyCapture& operator=(const LegacyCapture &); + + bool shouldSwapWidthHeight() const + { + if (!autorotate) + return false; + int rotation = static_cast(cap->getProperty(cv::CAP_PROP_ORIENTATION_META)); + return std::abs(rotation % 180) == 90; + } + public: - LegacyCapture(CvCapture * cap_) : cap(cap_) {} + LegacyCapture(CvCapture * cap_) : cap(cap_), autorotate(true) {} ~LegacyCapture() { cvReleaseCapture(&cap); } double getProperty(int propId) const CV_OVERRIDE { - return cap ? cap->getProperty(propId) : 0; + if (!cap) + return 0; + + switch(propId) + { + case cv::CAP_PROP_ORIENTATION_AUTO: + return static_cast(autorotate); + + case cv::CAP_PROP_FRAME_WIDTH: + return shouldSwapWidthHeight() ? cap->getProperty(cv::CAP_PROP_FRAME_HEIGHT) : cap->getProperty(cv::CAP_PROP_FRAME_WIDTH); + + case cv::CAP_PROP_FRAME_HEIGHT: + return shouldSwapWidthHeight() ? cap->getProperty(cv::CAP_PROP_FRAME_WIDTH) : cap->getProperty(cv::CAP_PROP_FRAME_HEIGHT); + + default: + return cap->getProperty(propId); + } } bool setProperty(int propId, double value) CV_OVERRIDE { - return cvSetCaptureProperty(cap, propId, value) != 0; + if (!cap) + return false; + + switch(propId) + { + case cv::CAP_PROP_ORIENTATION_AUTO: + autorotate = (value != 0); + return true; + + default: + return cvSetCaptureProperty(cap, propId, value) != 0; + } } bool grabFrame() CV_OVERRIDE { @@ -286,6 +351,7 @@ public: Mat temp = cv::cvarrToMat(_img); flip(temp, image, 0); } + applyMetadataRotation(*this, image); return true; } bool isOpened() const CV_OVERRIDE @@ -410,6 +476,9 @@ Ptr createXINECapture(const std::string &filename); Ptr createAndroidCapture_cam( int index ); Ptr createAndroidCapture_file(const std::string &filename); +Ptr createAndroidVideoWriter(const std::string& filename, int fourcc, + double fps, const Size& frameSize, + const VideoWriterParameters& params); Ptr create_obsensor_capture(int index); diff --git a/modules/videoio/src/cap_msmf.cpp b/modules/videoio/src/cap_msmf.cpp index f28c0bbdb3..505814e96c 100644 --- a/modules/videoio/src/cap_msmf.cpp +++ b/modules/videoio/src/cap_msmf.cpp @@ -348,6 +348,12 @@ struct MediaType } return false; } + bool VideoIsAvailable() const + { + return ((subType == MFVideoFormat_RGB32) || + (subType == MFVideoFormat_RGB24) || + (subType == MFVideoFormat_YUY2)); + } }; void printFormat(std::ostream& out, const GUID& fmt) @@ -628,7 +634,7 @@ public: { if (i->second.majorType == MFMediaType_Video) { - if (best.second.isEmpty() || i->second.VideoIsBetterThan(best.second, newType)) + if (best.second.isEmpty() || (i->second.VideoIsBetterThan(best.second, newType) && i->second.VideoIsAvailable())) { best = *i; } diff --git a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_interface.hpp b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_interface.hpp index 58e182ac0a..ac0cf1259e 100644 --- a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_interface.hpp +++ b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_interface.hpp @@ -32,6 +32,11 @@ namespace cv { namespace obsensor { + +#define OBSENSOR_CAM_VID 0x2bc5 // usb vid +#define OBSENSOR_ASTRA2_PID 0x0660 // pid of Orbbec Astra 2 Camera +#define OBSENSOR_GEMINI2_PID 0x0670 // pid of Orbbec Gemini 2 Camera + enum StreamType { OBSENSOR_STREAM_IR = 1, @@ -45,6 +50,7 @@ enum FrameFormat FRAME_FORMAT_YUYV = 0, FRAME_FORMAT_MJPG = 5, FRAME_FORMAT_Y16 = 8, + FRAME_FORMAT_Y14 = 9, }; enum PropertyId @@ -93,6 +99,7 @@ public: virtual bool getProperty(int propId, uint8_t* recvData, uint32_t* recvDataSize) = 0; virtual StreamType streamType() const = 0; + virtual uint16_t getPid() const =0; }; // "StreamChannelGroup" mean a group of stream channels from same one physical device diff --git a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp index 65d73497ea..7d984b63de 100644 --- a/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp +++ b/modules/videoio/src/cap_obsensor/obsensor_stream_channel_msmf.cpp @@ -474,13 +474,13 @@ STDMETHODIMP MSMFStreamChannel::OnReadSample(HRESULT hrStatus, DWORD dwStreamInd if (sample) { ComPtr buffer = nullptr; - DWORD max_length, current_length; + DWORD maxLength, currentLength; byte* byte_buffer = nullptr; HR_FAILED_EXEC(sample->GetBufferByIndex(0, &buffer), { return S_OK; }); - buffer->Lock(&byte_buffer, &max_length, ¤t_length); - Frame fo = { currentProfile_.format, currentProfile_.width, currentProfile_.height, current_length, (uint8_t*)byte_buffer }; + buffer->Lock(&byte_buffer, &maxLength, ¤tLength); + Frame fo = { currentProfile_.format, currentProfile_.width, currentProfile_.height, currentLength, (uint8_t*)byte_buffer }; if (depthFrameProcessor_) { depthFrameProcessor_->process(&fo); diff --git a/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.cpp b/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.cpp index aa2b7f9dd0..1bcb6ddf76 100644 --- a/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.cpp +++ b/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.cpp @@ -41,6 +41,11 @@ const uint8_t OB_EXT_CMD2[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0x56, 0x00 const uint8_t OB_EXT_CMD3[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0x58, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; const uint8_t OB_EXT_CMD4[16] = { 0x47, 0x4d, 0x02, 0x00, 0x03, 0x00, 0x60, 0x00, 0xed, 0x03, 0x00, 0x00 }; const uint8_t OB_EXT_CMD5[16] = { 0x47, 0x4d, 0x02, 0x00, 0x03, 0x00, 0x62, 0x00, 0xe9, 0x03, 0x00, 0x00 }; +const uint8_t OB_EXT_CMD6[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0x7c, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}; +const uint8_t OB_EXT_CMD7[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfe, 0x12, 0x55, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; +const uint8_t OB_EXT_CMD8[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfe, 0x13, 0x3f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; +const uint8_t OB_EXT_CMD9[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfa, 0x13, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; +const uint8_t OB_EXT_CMD10[16] = { 0x47, 0x4d, 0x04, 0x00, 0x02, 0x00, 0xfa, 0x13, 0x3f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 }; #if defined(HAVE_OBSENSOR_V4L2) #define fourCc2Int(a, b, c, d) \ @@ -54,6 +59,7 @@ const std::map fourccToOBFormat = { {fourCc2Int('Y', 'U', 'Y', '2'), FRAME_FORMAT_YUYV}, {fourCc2Int('M', 'J', 'P', 'G'), FRAME_FORMAT_MJPG}, {fourCc2Int('Y', '1', '6', ' '), FRAME_FORMAT_Y16}, + {fourCc2Int('Y', '1', '4', ' '), FRAME_FORMAT_Y14}, }; StreamType parseUvcDeviceNameToStreamType(const std::string& devName) @@ -187,6 +193,54 @@ void DepthFrameProcessor::process(Frame* frame) } } + +DepthFrameUnpacker::DepthFrameUnpacker(){ + outputDataBuf_ = new uint8_t[OUT_DATA_SIZE]; +} + +DepthFrameUnpacker::~DepthFrameUnpacker(){ + delete[] outputDataBuf_; +} + + +#define ON_BITS(count) ((1 << count) - 1) +#define CREATE_MASK(count, offset) (ON_BITS(count) << offset) +#define TAKE_BITS(source, count, offset) ((source & CREATE_MASK(count, offset)) >> offset) +void DepthFrameUnpacker::process(Frame *frame){ + const uint8_t tarStep = 16; + const uint8_t srcStep = 28; + uint16_t *tar = (uint16_t *)outputDataBuf_; + uint8_t *src = frame->data; + + uint32_t pixelSize = frame->width * frame->height; + for(uint32_t i = 0; i < pixelSize; i += tarStep) { + tar[0] = (TAKE_BITS(src[0], 8, 0) << 6) | TAKE_BITS(src[1], 6, 2); + tar[1] = (TAKE_BITS(src[1], 2, 0) << 12) | (TAKE_BITS(src[2], 8, 0) << 4) | TAKE_BITS(src[3], 4, 4); + tar[2] = (TAKE_BITS(src[3], 4, 0) << 10) | (TAKE_BITS(src[4], 8, 0) << 2) | TAKE_BITS(src[5], 2, 6); + tar[3] = (TAKE_BITS(src[5], 6, 0) << 8) | TAKE_BITS(src[6], 8, 0); + + tar[4] = (TAKE_BITS(src[7], 8, 0) << 6) | TAKE_BITS(src[8], 6, 2); + tar[5] = (TAKE_BITS(src[8], 2, 0) << 12) | (TAKE_BITS(src[9], 8, 0) << 4) | TAKE_BITS(src[10], 4, 4); + tar[6] = (TAKE_BITS(src[10], 4, 0) << 10) | (TAKE_BITS(src[11], 8, 0) << 2) | TAKE_BITS(src[12], 2, 6); + tar[7] = (TAKE_BITS(src[12], 6, 0) << 8) | TAKE_BITS(src[13], 8, 0); + + tar[8] = (TAKE_BITS(src[14], 8, 0) << 6) | TAKE_BITS(src[15], 6, 2); + tar[9] = (TAKE_BITS(src[15], 2, 0) << 12) | (TAKE_BITS(src[16], 8, 0) << 4) | TAKE_BITS(src[17], 4, 4); + tar[10] = (TAKE_BITS(src[17], 4, 0) << 10) | (TAKE_BITS(src[18], 8, 0) << 2) | TAKE_BITS(src[19], 2, 6); + tar[11] = (TAKE_BITS(src[19], 6, 0) << 8) | TAKE_BITS(src[20], 8, 0); + + tar[12] = (TAKE_BITS(src[21], 8, 0) << 6) | TAKE_BITS(src[22], 6, 2); + tar[13] = (TAKE_BITS(src[22], 2, 0) << 12) | (TAKE_BITS(src[23], 8, 0) << 4) | TAKE_BITS(src[24], 4, 4); + tar[14] = (TAKE_BITS(src[24], 4, 0) << 10) | (TAKE_BITS(src[25], 8, 0) << 2) | TAKE_BITS(src[26], 2, 6); + tar[15] = (TAKE_BITS(src[26], 6, 0) << 8) | TAKE_BITS(src[27], 8, 0); + + src += srcStep; + tar += tarStep; + } + frame->data = outputDataBuf_; + frame->format = FRAME_FORMAT_Y16; +} + IUvcStreamChannel::IUvcStreamChannel(const UvcDeviceInfo& devInfo) : devInfo_(devInfo), streamType_(parseUvcDeviceNameToStreamType(devInfo_.name)) @@ -198,6 +252,10 @@ StreamType IUvcStreamChannel::streamType() const { return streamType_; } +uint16_t IUvcStreamChannel::getPid() const { + return devInfo_.pid; +}; + bool IUvcStreamChannel::setProperty(int propId, const uint8_t* /*data*/, uint32_t /*dataSize*/) { uint8_t* rcvData; @@ -206,15 +264,28 @@ bool IUvcStreamChannel::setProperty(int propId, const uint8_t* /*data*/, uint32_ switch (propId) { case DEPTH_TO_COLOR_ALIGN: - // todo: value filling - rst &= setXu(2, OB_EXT_CMD0, sizeof(OB_EXT_CMD0)); - rst &= getXu(2, &rcvData, &rcvLen); - rst &= setXu(2, OB_EXT_CMD1, sizeof(OB_EXT_CMD1)); - rst &= getXu(2, &rcvData, &rcvLen); - rst &= setXu(2, OB_EXT_CMD2, sizeof(OB_EXT_CMD2)); - rst &= getXu(2, &rcvData, &rcvLen); - rst &= setXu(2, OB_EXT_CMD3, sizeof(OB_EXT_CMD3)); - rst &= getXu(2, &rcvData, &rcvLen); + if(OBSENSOR_GEMINI2_PID == devInfo_.pid ){ + rst &= setXu(2, OB_EXT_CMD8, sizeof(OB_EXT_CMD8)); + rst &= getXu(2, &rcvData, &rcvLen); + rst &= setXu(2, OB_EXT_CMD6, sizeof(OB_EXT_CMD6)); + rst &= getXu(2, &rcvData, &rcvLen); + } + else if(OBSENSOR_ASTRA2_PID == devInfo_.pid ){ + rst &= setXu(2, OB_EXT_CMD10, sizeof(OB_EXT_CMD8)); + rst &= getXu(2, &rcvData, &rcvLen); + rst &= setXu(2, OB_EXT_CMD6, sizeof(OB_EXT_CMD6)); + rst &= getXu(2, &rcvData, &rcvLen); + } + else{ + rst &= setXu(2, OB_EXT_CMD0, sizeof(OB_EXT_CMD0)); + rst &= getXu(2, &rcvData, &rcvLen); + rst &= setXu(2, OB_EXT_CMD1, sizeof(OB_EXT_CMD1)); + rst &= getXu(2, &rcvData, &rcvLen); + rst &= setXu(2, OB_EXT_CMD2, sizeof(OB_EXT_CMD2)); + rst &= getXu(2, &rcvData, &rcvLen); + rst &= setXu(2, OB_EXT_CMD3, sizeof(OB_EXT_CMD3)); + rst &= getXu(2, &rcvData, &rcvLen); + } break; default: rst = false; @@ -231,12 +302,50 @@ bool IUvcStreamChannel::getProperty(int propId, uint8_t* recvData, uint32_t* rec switch (propId) { case CAMERA_PARAM: - rst &= setXu(2, OB_EXT_CMD5, sizeof(OB_EXT_CMD5)); - rst &= getXu(2, &rcvData, &rcvLen); - if (rst && OB_EXT_CMD5[6] == rcvData[6] && rcvData[8] == 0 && rcvData[9] == 0) - { - memcpy(recvData, rcvData + 10, rcvLen - 10); - *recvDataSize = rcvLen - 10; + if(OBSENSOR_GEMINI2_PID == devInfo_.pid){ + // return default param + CameraParam param; + param.p0[0] = 516.652f; + param.p0[1] = 516.692f; + param.p0[2] = 322.988f; + param.p0[3] = 235.787f; + param.p1[0] = 516.652f; + param.p1[1] = 516.692f; + param.p1[2] = 322.988f; + param.p1[3] = 235.787f; + param.p6[0] = 640; + param.p6[1] = 480; + param.p7[0] = 640; + param.p7[1] = 480; + *recvDataSize = sizeof(CameraParam); + memcpy(recvData, ¶m, *recvDataSize); + } + else if(OBSENSOR_ASTRA2_PID == devInfo_.pid){ + // return default param + CameraParam param; + param.p0[0] = 558.151f; + param.p0[1] = 558.003f; + param.p0[2] = 312.546f; + param.p0[3] = 241.169f; + param.p1[0] = 558.151f; + param.p1[1] = 558.003f; + param.p1[2] = 312.546f; + param.p1[3] = 241.169f; + param.p6[0] = 640; + param.p6[1] = 480; + param.p7[0] = 640; + param.p7[1] = 480; + *recvDataSize = sizeof(CameraParam); + memcpy(recvData, ¶m, *recvDataSize); + } + else{ + rst &= setXu(2, OB_EXT_CMD5, sizeof(OB_EXT_CMD5)); + rst &= getXu(2, &rcvData, &rcvLen); + if (rst && OB_EXT_CMD5[6] == rcvData[6] && rcvData[8] == 0 && rcvData[9] == 0) + { + memcpy(recvData, rcvData + 10, rcvLen - 10); + *recvDataSize = rcvLen - 10; + } } break; default: @@ -249,7 +358,20 @@ bool IUvcStreamChannel::getProperty(int propId, uint8_t* recvData, uint32_t* rec bool IUvcStreamChannel::initDepthFrameProcessor() { - if (streamType_ == OBSENSOR_STREAM_DEPTH && setXu(2, OB_EXT_CMD4, sizeof(OB_EXT_CMD4))) + if(OBSENSOR_GEMINI2_PID == devInfo_.pid || OBSENSOR_ASTRA2_PID == devInfo_.pid){ + uint8_t* rcvData; + uint32_t rcvLen; + + setXu(2, OB_EXT_CMD7, sizeof(OB_EXT_CMD7)); + getXu(2, &rcvData, &rcvLen); + + setXu(2, OB_EXT_CMD9, sizeof(OB_EXT_CMD9)); + getXu(2, &rcvData, &rcvLen); + + depthFrameProcessor_ = makePtr(); + return true; + } + else if (streamType_ == OBSENSOR_STREAM_DEPTH && setXu(2, OB_EXT_CMD4, sizeof(OB_EXT_CMD4))) { uint8_t* rcvData; uint32_t rcvLen; diff --git a/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp b/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp index dfbb36d7e7..02f4040101 100644 --- a/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp +++ b/modules/videoio/src/cap_obsensor/obsensor_uvc_stream_channel.hpp @@ -26,8 +26,6 @@ #ifdef HAVE_OBSENSOR namespace cv { namespace obsensor { - -#define OBSENSOR_CAM_VID 0x2bc5 // usb vid #define XU_MAX_DATA_LENGTH 1024 #define XU_UNIT_ID 4 @@ -60,17 +58,34 @@ struct OBExtensionParam { float ps; }; -class DepthFrameProcessor { +class IFrameProcessor{ +public: + virtual void process(Frame* frame) = 0; + virtual ~IFrameProcessor() = default; +}; + +class DepthFrameProcessor: public IFrameProcessor { public: DepthFrameProcessor(const OBExtensionParam& parma); - ~DepthFrameProcessor() noexcept; - void process(Frame* frame); + virtual ~DepthFrameProcessor() noexcept; + virtual void process(Frame* frame) override; private: const OBExtensionParam param_; uint16_t* lookUpTable_; }; +class DepthFrameUnpacker: public IFrameProcessor { +public: + DepthFrameUnpacker(); + virtual ~DepthFrameUnpacker() noexcept; + virtual void process(Frame* frame) override; +private: + const uint32_t OUT_DATA_SIZE = 1280*800*2; + uint8_t *outputDataBuf_; +}; + + class IUvcStreamChannel : public IStreamChannel { public: IUvcStreamChannel(const UvcDeviceInfo& devInfo); @@ -79,6 +94,7 @@ public: virtual bool setProperty(int propId, const uint8_t* data, uint32_t dataSize) override; virtual bool getProperty(int propId, uint8_t* recvData, uint32_t* recvDataSize) override; virtual StreamType streamType() const override; + virtual uint16_t getPid() const override; protected: virtual bool setXu(uint8_t ctrl, const uint8_t* data, uint32_t len) = 0; @@ -89,7 +105,7 @@ protected: protected: const UvcDeviceInfo devInfo_; StreamType streamType_; - Ptr depthFrameProcessor_; + Ptr depthFrameProcessor_; }; }} // namespace cv::obsensor:: #endif // HAVE_OBSENSOR diff --git a/modules/videoio/src/cap_obsensor_capture.cpp b/modules/videoio/src/cap_obsensor_capture.cpp index 632f75dc6c..ccbfd61a5c 100644 --- a/modules/videoio/src/cap_obsensor_capture.cpp +++ b/modules/videoio/src/cap_obsensor_capture.cpp @@ -34,6 +34,8 @@ VideoCapture_obsensor::VideoCapture_obsensor(int index) : isOpened_(false) { static const obsensor::StreamProfile colorProfile = { 640, 480, 30, obsensor::FRAME_FORMAT_MJPG }; static const obsensor::StreamProfile depthProfile = {640, 480, 30, obsensor::FRAME_FORMAT_Y16}; + static const obsensor::StreamProfile gemini2depthProfile = {1280, 800, 30, obsensor::FRAME_FORMAT_Y14}; + static const obsensor::StreamProfile astra2depthProfile = {640, 480, 30, obsensor::FRAME_FORMAT_Y14}; streamChannelGroup_ = obsensor::getStreamChannelGroup(index); if (!streamChannelGroup_.empty()) @@ -47,15 +49,27 @@ VideoCapture_obsensor::VideoCapture_obsensor(int index) : isOpened_(false) channel->start(colorProfile, [&](obsensor::Frame* frame) { std::unique_lock lk(frameMutex_); colorFrame_ = Mat(1, frame->dataSize, CV_8UC1, frame->data).clone(); + frameCv_.notify_all(); }); break; case obsensor::OBSENSOR_STREAM_DEPTH: { uint8_t data = 1; channel->setProperty(obsensor::DEPTH_TO_COLOR_ALIGN, &data, 1); - channel->start(depthProfile, [&](obsensor::Frame* frame) { + + obsensor::StreamProfile profile = depthProfile; + if(OBSENSOR_GEMINI2_PID == channel->getPid()){ + profile = gemini2depthProfile; + } + else if(OBSENSOR_ASTRA2_PID == channel->getPid()){ + + profile = astra2depthProfile; + } + + channel->start(profile, [&](obsensor::Frame* frame) { std::unique_lock lk(frameMutex_); depthFrame_ = Mat(frame->height, frame->width, CV_16UC1, frame->data, frame->width * 2).clone(); + frameCv_.notify_all(); }); uint32_t len; @@ -72,10 +86,21 @@ VideoCapture_obsensor::VideoCapture_obsensor(int index) : isOpened_(false) } } +VideoCapture_obsensor::~VideoCapture_obsensor(){ + for (auto& channel : streamChannelGroup_) + { + channel->stop(); + } + streamChannelGroup_.clear(); +} + bool VideoCapture_obsensor::grabFrame() { std::unique_lock lk(frameMutex_); + // Try waiting for 33 milliseconds to ensure that both depth and color frame have been received! + frameCv_.wait_for(lk, std::chrono::milliseconds(33), [&](){ return !depthFrame_.empty() && !colorFrame_.empty(); }); + grabbedDepthFrame_ = depthFrame_; grabbedColorFrame_ = colorFrame_; @@ -93,7 +118,18 @@ bool VideoCapture_obsensor::retrieveFrame(int outputType, OutputArray frame) case CAP_OBSENSOR_DEPTH_MAP: if (!grabbedDepthFrame_.empty()) { - grabbedDepthFrame_.copyTo(frame); + if(OBSENSOR_GEMINI2_PID == streamChannelGroup_.front()->getPid()){ + grabbedDepthFrame_ = grabbedDepthFrame_*0.8; + Rect rect(320, 160, 640, 480); + grabbedDepthFrame_(rect).copyTo(frame); + } + else if(OBSENSOR_ASTRA2_PID == streamChannelGroup_.front()->getPid()){ + grabbedDepthFrame_ = grabbedDepthFrame_*0.8; + grabbedDepthFrame_.copyTo(frame); + } + else{ + grabbedDepthFrame_.copyTo(frame); + } grabbedDepthFrame_.release(); return true; } diff --git a/modules/videoio/src/cap_obsensor_capture.hpp b/modules/videoio/src/cap_obsensor_capture.hpp index 6256876737..821e6193a0 100644 --- a/modules/videoio/src/cap_obsensor_capture.hpp +++ b/modules/videoio/src/cap_obsensor_capture.hpp @@ -24,6 +24,7 @@ #include #include +#include #include "cap_obsensor/obsensor_stream_channel_interface.hpp" @@ -33,7 +34,7 @@ class VideoCapture_obsensor : public IVideoCapture { public: VideoCapture_obsensor(int index); - virtual ~VideoCapture_obsensor() {} + virtual ~VideoCapture_obsensor(); virtual double getProperty(int propIdx) const CV_OVERRIDE; virtual bool setProperty(int propIdx, double propVal) CV_OVERRIDE; @@ -51,6 +52,7 @@ private: std::vector> streamChannelGroup_; std::mutex frameMutex_; + std::condition_variable frameCv_; Mat depthFrame_; Mat colorFrame_; diff --git a/modules/videoio/src/cap_v4l.cpp b/modules/videoio/src/cap_v4l.cpp index 2fc41ce05e..a5d8561c6b 100644 --- a/modules/videoio/src/cap_v4l.cpp +++ b/modules/videoio/src/cap_v4l.cpp @@ -286,6 +286,12 @@ typedef uint32_t __u32; #define MAX_V4L_BUFFERS 10 #define DEFAULT_V4L_BUFFERS 4 +// types of memory in 'special' buffer +enum { + MEMORY_ORIG = 0, // Image data in original format. + MEMORY_RGB = 1, // Image data converted to RGB format. +}; + // if enabled, then bad JPEG warnings become errors and cause NULL returned instead of image #define V4L_ABORT_BADJPEG @@ -317,17 +323,27 @@ static const char* decode_ioctl_code(unsigned long ioctlCode) return "unknown"; } +struct Memory +{ + void * start; + size_t length; + + Memory() : start(NULL), length(0) {} +}; + /* Device Capture Objects */ /* V4L2 structure */ struct Buffer { - void * start; - size_t length; + Memory memories[VIDEO_MAX_PLANES]; + v4l2_plane planes[VIDEO_MAX_PLANES] = {}; + // Total number of bytes occupied by data in the all planes (payload) + __u32 bytesused; // This is dequeued buffer. It used for to put it back in the queue. // The buffer is valid only if capture->bufferIndex >= 0 v4l2_buffer buffer; - Buffer() : start(NULL), length(0) + Buffer() { buffer = v4l2_buffer(); } @@ -374,6 +390,7 @@ struct CvCaptureCAM_V4L CV_FINAL : public CvCapture v4l2_format form; v4l2_requestbuffers req; v4l2_buf_type type; + unsigned char num_planes; timeval timestamp; @@ -430,6 +447,7 @@ CvCaptureCAM_V4L::CvCaptureCAM_V4L() : fps(0), convert_rgb(0), frame_allocated(false), returnFrame(false), channelNumber(-1), normalizePropRange(false), type(V4L2_BUF_TYPE_VIDEO_CAPTURE), + num_planes(0), havePendingFrame(false) { frame = cvIplImage(); @@ -472,15 +490,24 @@ bool CvCaptureCAM_V4L::isOpened() const bool CvCaptureCAM_V4L::try_palette_v4l2() { form = v4l2_format(); - form.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - form.fmt.pix.pixelformat = palette; - form.fmt.pix.field = V4L2_FIELD_ANY; - form.fmt.pix.width = width; - form.fmt.pix.height = height; + form.type = type; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + form.fmt.pix_mp.pixelformat = palette; + form.fmt.pix_mp.field = V4L2_FIELD_ANY; + form.fmt.pix_mp.width = width; + form.fmt.pix_mp.height = height; + } else { + form.fmt.pix.pixelformat = palette; + form.fmt.pix.field = V4L2_FIELD_ANY; + form.fmt.pix.width = width; + form.fmt.pix.height = height; + } if (!tryIoctl(VIDIOC_S_FMT, &form, true)) { return false; } + if (V4L2_TYPE_IS_MULTIPLANAR(type)) + return palette == form.fmt.pix_mp.pixelformat; return palette == form.fmt.pix.pixelformat; } @@ -534,12 +561,15 @@ bool CvCaptureCAM_V4L::try_init_v4l2() return false; } - if ((capability.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) + if ((capability.capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE)) == 0) { /* Nope. */ - CV_LOG_INFO(NULL, "VIDEOIO(V4L2:" << deviceName << "): not supported - device is unable to capture video (missing V4L2_CAP_VIDEO_CAPTURE)"); + CV_LOG_INFO(NULL, "VIDEOIO(V4L2:" << deviceName << "): not supported - device is unable to capture video (missing V4L2_CAP_VIDEO_CAPTURE or V4L2_CAP_VIDEO_CAPTURE_MPLANE)"); return false; } + + if (capability.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) + type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; return true; } @@ -603,7 +633,7 @@ bool CvCaptureCAM_V4L::setFps(int value) return false; v4l2_streamparm streamparm = v4l2_streamparm(); - streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + streamparm.type = type; streamparm.parm.capture.timeperframe.numerator = 1; streamparm.parm.capture.timeperframe.denominator = __u32(value); if (!tryIoctl(VIDIOC_S_PARM, &streamparm) || !tryIoctl(VIDIOC_G_PARM, &streamparm)) @@ -652,12 +682,21 @@ bool CvCaptureCAM_V4L::convertableToRgb() const void CvCaptureCAM_V4L::v4l2_create_frame() { - CV_Assert(form.fmt.pix.width <= (uint)std::numeric_limits::max()); - CV_Assert(form.fmt.pix.height <= (uint)std::numeric_limits::max()); - CvSize size = {(int)form.fmt.pix.width, (int)form.fmt.pix.height}; + CvSize size; int channels = 3; int depth = IPL_DEPTH_8U; + + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + CV_Assert(form.fmt.pix_mp.width <= (uint)std::numeric_limits::max()); + CV_Assert(form.fmt.pix_mp.height <= (uint)std::numeric_limits::max()); + size = {(int)form.fmt.pix_mp.width, (int)form.fmt.pix_mp.height}; + } else { + CV_Assert(form.fmt.pix.width <= (uint)std::numeric_limits::max()); + CV_Assert(form.fmt.pix.height <= (uint)std::numeric_limits::max()); + size = {(int)form.fmt.pix.width, (int)form.fmt.pix.height}; + } + if (!convert_rgb) { switch (palette) { case V4L2_PIX_FMT_BGR24: @@ -689,9 +728,19 @@ void CvCaptureCAM_V4L::v4l2_create_frame() default: channels = 1; if(bufferIndex < 0) - size = cvSize(buffers[MAX_V4L_BUFFERS].length, 1); - else - size = cvSize(buffers[bufferIndex].buffer.bytesused, 1); + size = cvSize(buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].length, 1); + else { + __u32 bytesused = 0; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + __u32 data_offset; + for (unsigned char n_planes = 0; n_planes < num_planes; n_planes++) { + data_offset = buffers[bufferIndex].planes[n_planes].data_offset; + bytesused += buffers[bufferIndex].planes[n_planes].bytesused - data_offset; + } + } else + bytesused = buffers[bufferIndex].buffer.bytesused; + size = cvSize(bytesused, 1); + } break; } } @@ -723,7 +772,7 @@ bool CvCaptureCAM_V4L::initCapture() /* Find Window info */ form = v4l2_format(); - form.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + form.type = type; if (!tryIoctl(VIDIOC_G_FMT, &form)) { @@ -743,18 +792,27 @@ bool CvCaptureCAM_V4L::initCapture() /* try to set framerate */ setFps(fps); - unsigned int min; - /* Buggy driver paranoia. */ - min = form.fmt.pix.width * 2; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + // TODO: add size adjustment if needed + } else { + unsigned int min; - if (form.fmt.pix.bytesperline < min) - form.fmt.pix.bytesperline = min; + min = form.fmt.pix.width * 2; - min = form.fmt.pix.bytesperline * form.fmt.pix.height; + if (form.fmt.pix.bytesperline < min) + form.fmt.pix.bytesperline = min; - if (form.fmt.pix.sizeimage < min) - form.fmt.pix.sizeimage = min; + min = form.fmt.pix.bytesperline * form.fmt.pix.height; + + if (form.fmt.pix.sizeimage < min) + form.fmt.pix.sizeimage = min; + } + + if (V4L2_TYPE_IS_MULTIPLANAR(type)) + num_planes = form.fmt.pix_mp.num_planes; + else + num_planes = 1; if (!requestBuffers()) return false; @@ -800,7 +858,7 @@ bool CvCaptureCAM_V4L::requestBuffers(unsigned int buffer_number) req = v4l2_requestbuffers(); req.count = buffer_number; - req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.type = type; req.memory = V4L2_MEMORY_MMAP; if (!tryIoctl(VIDIOC_REQBUFS, &req)) { @@ -824,34 +882,56 @@ bool CvCaptureCAM_V4L::createBuffers() size_t maxLength = 0; for (unsigned int n_buffers = 0; n_buffers < req.count; ++n_buffers) { v4l2_buffer buf = v4l2_buffer(); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_plane mplanes[VIDEO_MAX_PLANES]; + size_t length = 0; + off_t offset = 0; + buf.type = type; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + buf.m.planes = mplanes; + buf.length = VIDEO_MAX_PLANES; + } if (!tryIoctl(VIDIOC_QUERYBUF, &buf)) { CV_LOG_WARNING(NULL, "VIDEOIO(V4L2:" << deviceName << "): failed VIDIOC_QUERYBUF: errno=" << errno << " (" << strerror(errno) << ")"); return false; } - buffers[n_buffers].length = buf.length; - buffers[n_buffers].start = - mmap(NULL /* start anywhere */, - buf.length, - PROT_READ /* required */, - MAP_SHARED /* recommended */, - deviceHandle, buf.m.offset); + CV_Assert(1 <= num_planes && num_planes <= VIDEO_MAX_PLANES); + for (unsigned char n_planes = 0; n_planes < num_planes; n_planes++) { + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + length = buf.m.planes[n_planes].length; + offset = buf.m.planes[n_planes].m.mem_offset; + } else { + length = buf.length; + offset = buf.m.offset; + } - if (MAP_FAILED == buffers[n_buffers].start) { - CV_LOG_WARNING(NULL, "VIDEOIO(V4L2:" << deviceName << "): failed mmap(" << buf.length << "): errno=" << errno << " (" << strerror(errno) << ")"); - return false; + buffers[n_buffers].memories[n_planes].length = length; + buffers[n_buffers].memories[n_planes].start = + mmap(NULL /* start anywhere */, + length, + PROT_READ /* required */, + MAP_SHARED /* recommended */, + deviceHandle, offset); + if (MAP_FAILED == buffers[n_buffers].memories[n_planes].start) { + CV_LOG_WARNING(NULL, "VIDEOIO(V4L2:" << deviceName << "): failed mmap(" << length << "): errno=" << errno << " (" << strerror(errno) << ")"); + return false; + } } - maxLength = maxLength > buf.length ? maxLength : buf.length; + + maxLength = maxLength > length ? maxLength : length; } if (maxLength > 0) { - buffers[MAX_V4L_BUFFERS].start = malloc(maxLength); - buffers[MAX_V4L_BUFFERS].length = maxLength; + maxLength *= num_planes; + buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start = malloc(maxLength); + buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].length = maxLength; + buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start = malloc(maxLength); + buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].length = maxLength; } - return buffers[MAX_V4L_BUFFERS].start != 0; + return (buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start != 0) && + (buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start != 0); } /** @@ -933,8 +1013,13 @@ bool CvCaptureCAM_V4L::open(const char* _deviceName) bool CvCaptureCAM_V4L::read_frame_v4l2() { v4l2_buffer buf = v4l2_buffer(); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_plane mplanes[VIDEO_MAX_PLANES]; + buf.type = type; buf.memory = V4L2_MEMORY_MMAP; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + buf.m.planes = mplanes; + buf.length = VIDEO_MAX_PLANES; + } while (!tryIoctl(VIDIOC_DQBUF, &buf)) { int err = errno; @@ -951,12 +1036,33 @@ bool CvCaptureCAM_V4L::read_frame_v4l2() } CV_Assert(buf.index < req.count); - CV_Assert(buffers[buf.index].length == buf.length); + + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + for (unsigned char n_planes = 0; n_planes < num_planes; n_planes++) + CV_Assert(buffers[buf.index].memories[n_planes].length == buf.m.planes[n_planes].length); + } else + CV_Assert(buffers[buf.index].memories[MEMORY_ORIG].length == buf.length); //We shouldn't use this buffer in the queue while not retrieve frame from it. buffers[buf.index].buffer = buf; bufferIndex = buf.index; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + __u32 offset = 0; + + buffers[buf.index].buffer.m.planes = buffers[buf.index].planes; + memcpy(buffers[buf.index].planes, buf.m.planes, sizeof(mplanes)); + + for (unsigned char n_planes = 0; n_planes < num_planes; n_planes++) { + __u32 bytesused; + bytesused = buffers[buf.index].planes[n_planes].bytesused - + buffers[buf.index].planes[n_planes].data_offset; + offset += bytesused; + } + buffers[buf.index].bytesused = offset; + } else + buffers[buf.index].bytesused = buffers[buf.index].buffer.bytesused; + //set timestamp in capture struct to be timestamp of most recent frame timestamp = buf.timestamp; return true; @@ -1042,10 +1148,15 @@ bool CvCaptureCAM_V4L::grabFrame() bufferIndex = -1; for (__u32 index = 0; index < req.count; ++index) { v4l2_buffer buf = v4l2_buffer(); + v4l2_plane mplanes[VIDEO_MAX_PLANES]; - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.type = type; buf.memory = V4L2_MEMORY_MMAP; buf.index = index; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + buf.m.planes = mplanes; + buf.length = VIDEO_MAX_PLANES; + } if (!tryIoctl(VIDIOC_QBUF, &buf)) { CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << deviceName << "): failed VIDIOC_QBUF (buffer=" << index << "): errno=" << errno << " (" << strerror(errno) << ")"); @@ -1534,35 +1645,51 @@ static int sonix_decompress(int width, int height, unsigned char *inp, unsigned void CvCaptureCAM_V4L::convertToRgb(const Buffer ¤tBuffer) { - cv::Size imageSize(form.fmt.pix.width, form.fmt.pix.height); + cv::Size imageSize; + unsigned char *start; + + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + __u32 offset = 0; + start = (unsigned char*)buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start; + for (unsigned char n_planes = 0; n_planes < num_planes; n_planes++) { + __u32 data_offset, bytesused; + data_offset = currentBuffer.planes[n_planes].data_offset; + bytesused = currentBuffer.planes[n_planes].bytesused - data_offset; + memcpy(start + offset, (char *)currentBuffer.memories[n_planes].start + data_offset, + std::min(currentBuffer.memories[n_planes].length, (size_t)bytesused)); + offset += bytesused; + } + + imageSize = cv::Size(form.fmt.pix_mp.width, form.fmt.pix_mp.height); + } else { + start = (unsigned char*)currentBuffer.memories[MEMORY_ORIG].start; + + imageSize = cv::Size(form.fmt.pix.width, form.fmt.pix.height); + } // Not found conversion switch (palette) { case V4L2_PIX_FMT_YUV411P: yuv411p_to_rgb24(imageSize.width, imageSize.height, - (unsigned char*)(currentBuffer.start), - (unsigned char*)frame.imageData); + start, (unsigned char*)frame.imageData); return; case V4L2_PIX_FMT_SBGGR8: bayer2rgb24(imageSize.width, imageSize.height, - (unsigned char*)currentBuffer.start, - (unsigned char*)frame.imageData); + start, (unsigned char*)frame.imageData); return; case V4L2_PIX_FMT_SN9C10X: sonix_decompress_init(); sonix_decompress(imageSize.width, imageSize.height, - (unsigned char*)currentBuffer.start, - (unsigned char*)buffers[MAX_V4L_BUFFERS].start); + start, (unsigned char*)buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start); bayer2rgb24(imageSize.width, imageSize.height, - (unsigned char*)buffers[MAX_V4L_BUFFERS].start, + (unsigned char*)buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start, (unsigned char*)frame.imageData); return; case V4L2_PIX_FMT_SGBRG8: sgbrg2rgb24(imageSize.width, imageSize.height, - (unsigned char*)currentBuffer.start, - (unsigned char*)frame.imageData); + start, (unsigned char*)frame.imageData); return; default: break; @@ -1571,69 +1698,69 @@ void CvCaptureCAM_V4L::convertToRgb(const Buffer ¤tBuffer) cv::Mat destination(imageSize, CV_8UC3, frame.imageData); switch (palette) { case V4L2_PIX_FMT_YVU420: - cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, currentBuffer.start), destination, + cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, start), destination, COLOR_YUV2BGR_YV12); return; case V4L2_PIX_FMT_YUV420: - cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, currentBuffer.start), destination, + cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, start), destination, COLOR_YUV2BGR_IYUV); return; case V4L2_PIX_FMT_NV12: - cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, currentBuffer.start), destination, + cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, start), destination, COLOR_YUV2RGB_NV12); return; case V4L2_PIX_FMT_NV21: - cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, currentBuffer.start), destination, + cv::cvtColor(cv::Mat(imageSize.height * 3 / 2, imageSize.width, CV_8U, start), destination, COLOR_YUV2RGB_NV21); return; #ifdef HAVE_JPEG case V4L2_PIX_FMT_MJPEG: case V4L2_PIX_FMT_JPEG: - CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << deviceName << "): decoding JPEG frame: size=" << currentBuffer.buffer.bytesused); - cv::imdecode(Mat(1, currentBuffer.buffer.bytesused, CV_8U, currentBuffer.start), IMREAD_COLOR, &destination); + CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << deviceName << "): decoding JPEG frame: size=" << currentBuffer.bytesused); + cv::imdecode(Mat(1, currentBuffer.bytesused, CV_8U, start), IMREAD_COLOR, &destination); return; #endif case V4L2_PIX_FMT_YUYV: - cv::cvtColor(cv::Mat(imageSize, CV_8UC2, currentBuffer.start), destination, COLOR_YUV2BGR_YUYV); + cv::cvtColor(cv::Mat(imageSize, CV_8UC2, start), destination, COLOR_YUV2BGR_YUYV); return; case V4L2_PIX_FMT_UYVY: - cv::cvtColor(cv::Mat(imageSize, CV_8UC2, currentBuffer.start), destination, COLOR_YUV2BGR_UYVY); + cv::cvtColor(cv::Mat(imageSize, CV_8UC2, start), destination, COLOR_YUV2BGR_UYVY); return; case V4L2_PIX_FMT_RGB24: - cv::cvtColor(cv::Mat(imageSize, CV_8UC3, currentBuffer.start), destination, COLOR_RGB2BGR); + cv::cvtColor(cv::Mat(imageSize, CV_8UC3, start), destination, COLOR_RGB2BGR); return; case V4L2_PIX_FMT_Y16: { - cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].start); - cv::Mat(imageSize, CV_16UC1, currentBuffer.start).convertTo(temp, CV_8U, 1.0 / 256); + cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start); + cv::Mat(imageSize, CV_16UC1, start).convertTo(temp, CV_8U, 1.0 / 256); cv::cvtColor(temp, destination, COLOR_GRAY2BGR); return; } case V4L2_PIX_FMT_Y12: { - cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].start); - cv::Mat(imageSize, CV_16UC1, currentBuffer.start).convertTo(temp, CV_8U, 1.0 / 16); + cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start); + cv::Mat(imageSize, CV_16UC1, start).convertTo(temp, CV_8U, 1.0 / 16); cv::cvtColor(temp, destination, COLOR_GRAY2BGR); return; } case V4L2_PIX_FMT_Y10: { - cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].start); - cv::Mat(imageSize, CV_16UC1, currentBuffer.start).convertTo(temp, CV_8U, 1.0 / 4); + cv::Mat temp(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start); + cv::Mat(imageSize, CV_16UC1, start).convertTo(temp, CV_8U, 1.0 / 4); cv::cvtColor(temp, destination, COLOR_GRAY2BGR); return; } case V4L2_PIX_FMT_GREY: - cv::cvtColor(cv::Mat(imageSize, CV_8UC1, currentBuffer.start), destination, COLOR_GRAY2BGR); + cv::cvtColor(cv::Mat(imageSize, CV_8UC1, start), destination, COLOR_GRAY2BGR); break; case V4L2_PIX_FMT_XBGR32: case V4L2_PIX_FMT_ABGR32: - cv::cvtColor(cv::Mat(imageSize, CV_8UC4, currentBuffer.start), destination, COLOR_BGRA2BGR); + cv::cvtColor(cv::Mat(imageSize, CV_8UC4, start), destination, COLOR_BGRA2BGR); break; case V4L2_PIX_FMT_BGR24: default: - memcpy((char *)frame.imageData, (char *)currentBuffer.start, - std::min(frame.imageSize, (int)currentBuffer.buffer.bytesused)); + memcpy((char *)frame.imageData, start, + std::min(frame.imageSize, (int)currentBuffer.bytesused)); break; } } @@ -1904,9 +2031,15 @@ double CvCaptureCAM_V4L::getProperty(int property_id) const { switch (property_id) { case cv::CAP_PROP_FRAME_WIDTH: - return form.fmt.pix.width; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) + return form.fmt.pix_mp.width; + else + return form.fmt.pix.width; case cv::CAP_PROP_FRAME_HEIGHT: - return form.fmt.pix.height; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) + return form.fmt.pix_mp.height; + else + return form.fmt.pix.height; case cv::CAP_PROP_FOURCC: return palette; case cv::CAP_PROP_FORMAT: @@ -1922,7 +2055,7 @@ double CvCaptureCAM_V4L::getProperty(int property_id) const case cv::CAP_PROP_FPS: { v4l2_streamparm sp = v4l2_streamparm(); - sp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + sp.type = type; if (!tryIoctl(VIDIOC_G_PARM, &sp)) { CV_LOG_WARNING(NULL, "VIDEOIO(V4L2:" << deviceName << "): Unable to get camera FPS"); return -1; @@ -2063,9 +2196,14 @@ void CvCaptureCAM_V4L::releaseBuffers() { releaseFrame(); - if (buffers[MAX_V4L_BUFFERS].start) { - free(buffers[MAX_V4L_BUFFERS].start); - buffers[MAX_V4L_BUFFERS].start = 0; + if (buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start) { + free(buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start); + buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start = 0; + } + + if (buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start) { + free(buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start); + buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start = 0; } bufferIndex = -1; @@ -2076,11 +2214,14 @@ void CvCaptureCAM_V4L::releaseBuffers() v4l_buffersRequested = false; for (unsigned int n_buffers = 0; n_buffers < MAX_V4L_BUFFERS; ++n_buffers) { - if (buffers[n_buffers].start) { - if (-1 == munmap(buffers[n_buffers].start, buffers[n_buffers].length)) { - CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << deviceName << "): failed munmap(): errno=" << errno << " (" << strerror(errno) << ")"); - } else { - buffers[n_buffers].start = 0; + for (unsigned char n_planes = 0; n_planes < num_planes; n_planes++) { + if (buffers[n_buffers].memories[n_planes].start) { + if (-1 == munmap(buffers[n_buffers].memories[n_planes].start, + buffers[n_buffers].memories[n_planes].length)) { + CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << deviceName << "): failed munmap(): errno=" << errno << " (" << strerror(errno) << ")"); + } else { + buffers[n_buffers].memories[n_planes].start = 0; + } } } } @@ -2100,7 +2241,6 @@ bool CvCaptureCAM_V4L::streaming(bool startStream) return !startStream; } - type = V4L2_BUF_TYPE_VIDEO_CAPTURE; bool result = tryIoctl(startStream ? VIDIOC_STREAMON : VIDIOC_STREAMOFF, &type); if (result) { @@ -2133,13 +2273,26 @@ IplImage *CvCaptureCAM_V4L::retrieveFrame(int) } else { // for mjpeg streams the size might change in between, so we have to change the header // We didn't allocate memory when not convert_rgb, but we have to recreate the header - CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << deviceName << "): buffer input size=" << currentBuffer.buffer.bytesused); - if (frame.imageSize != (int)currentBuffer.buffer.bytesused) + CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << deviceName << "): buffer input size=" << currentBuffer.bytesused); + if (frame.imageSize != (int)currentBuffer.bytesused) v4l2_create_frame(); - frame.imageData = (char *)buffers[MAX_V4L_BUFFERS].start; - memcpy(buffers[MAX_V4L_BUFFERS].start, currentBuffer.start, - std::min(buffers[MAX_V4L_BUFFERS].length, (size_t)currentBuffer.buffer.bytesused)); + frame.imageData = (char *)buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start; + if (V4L2_TYPE_IS_MULTIPLANAR(type)) { + __u32 offset = 0; + for (unsigned char n_planes = 0; n_planes < num_planes; n_planes++) { + __u32 data_offset, bytesused; + data_offset = currentBuffer.planes[n_planes].data_offset; + bytesused = currentBuffer.planes[n_planes].bytesused - data_offset; + memcpy((unsigned char*)buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start + offset, + (char *)currentBuffer.memories[n_planes].start + data_offset, + std::min(currentBuffer.memories[n_planes].length, (size_t)bytesused)); + offset += bytesused; + } + } else { + memcpy(buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].start, currentBuffer.memories[MEMORY_ORIG].start, + std::min(buffers[MAX_V4L_BUFFERS].memories[MEMORY_ORIG].length, (size_t)currentBuffer.buffer.bytesused)); + } } //Revert buffer to the queue if (!tryIoctl(VIDIOC_QBUF, &buffers[bufferIndex].buffer)) diff --git a/modules/videoio/src/videoio_registry.cpp b/modules/videoio/src/videoio_registry.cpp index 52f4227396..fb4f2fc26f 100644 --- a/modules/videoio/src/videoio_registry.cpp +++ b/modules/videoio/src/videoio_registry.cpp @@ -149,7 +149,7 @@ static const struct VideoBackendInfo builtin_backends[] = #if defined(HAVE_ANDROID_MEDIANDK) || defined(HAVE_ANDROID_NATIVE_CAMERA) DECLARE_STATIC_BACKEND(CAP_ANDROID, "ANDROID_NATIVE", #ifdef HAVE_ANDROID_MEDIANDK - MODE_CAPTURE_BY_FILENAME + MODE_CAPTURE_BY_FILENAME | MODE_WRITER #else 0 #endif @@ -169,8 +169,12 @@ static const struct VideoBackendInfo builtin_backends[] = #else 0, #endif +#ifdef HAVE_ANDROID_MEDIANDK + createAndroidVideoWriter) +#else 0) #endif +#endif #ifdef HAVE_OBSENSOR DECLARE_STATIC_BACKEND(CAP_OBSENSOR, "OBSENSOR", MODE_CAPTURE_BY_INDEX, 0, create_obsensor_capture, 0) diff --git a/modules/videoio/test/test_audio.cpp b/modules/videoio/test/test_audio.cpp index 4731a76749..efe5f67346 100644 --- a/modules/videoio/test/test_audio.cpp +++ b/modules/videoio/test/test_audio.cpp @@ -170,7 +170,6 @@ public: const int samplePerSecond = (int)cap.get(CAP_PROP_AUDIO_SAMPLES_PER_SECOND); ASSERT_EQ(44100, samplePerSecond); int samplesPerFrame = (int)(1./fps*samplePerSecond); - int audioSamplesTolerance = samplesPerFrame / 2; double audio0_timestamp = 0; @@ -182,7 +181,6 @@ public: SCOPED_TRACE(cv::format("frame=%d", frame)); ASSERT_TRUE(cap.grab()); - if (frame == 0) { double audio_shift = cap.get(CAP_PROP_AUDIO_SHIFT_NSEC); @@ -190,7 +188,6 @@ public: audio0_timestamp = video0_timestamp + audio_shift * 1e-9; std::cout << "video0 timestamp: " << video0_timestamp << " audio0 timestamp: " << audio0_timestamp << " (audio shift nanoseconds: " << audio_shift << " , seconds: " << audio_shift * 1e-9 << ")" << std::endl; } - ASSERT_TRUE(cap.retrieve(videoFrame)); if (epsilon >= 0) { @@ -238,8 +235,12 @@ public: } if (frame != 0 && frame != numberOfFrames-1 && audioData[0].size() != (size_t)numberOfSamples) { - // validate audio frame size - EXPECT_NEAR(audioFrame.cols, samplesPerFrame, audioSamplesTolerance); + if (backend == cv::CAP_MSMF) + { + int audioSamplesTolerance = samplesPerFrame / 2; + // validate audio frame size + EXPECT_NEAR(audioFrame.cols, samplesPerFrame, audioSamplesTolerance); + } } } ASSERT_FALSE(cap.grab()); @@ -265,15 +266,17 @@ TEST_P(Media, audio) { if (!videoio_registry::hasBackend(cv::VideoCaptureAPIs(backend))) throw SkipTestException(cv::videoio_registry::getBackendName(backend) + " backend was not found"); + if (cvtest::skipUnstableTests && backend == CAP_GSTREAMER) + throw SkipTestException("Unstable GStreamer test"); doTest(); } -#ifdef _WIN32 const paramCombination mediaParams[] = { + paramCombination("test_audio.mp4", 1, 0.15, CV_8UC3, 240, 320, 90, 132299, 30, 30., cv::CAP_GSTREAMER) #ifdef _WIN32 - paramCombination("test_audio.mp4", 1, 0.15, CV_8UC3, 240, 320, 90, 131819, 30, 30., cv::CAP_MSMF) + , paramCombination("test_audio.mp4", 1, 0.15, CV_8UC3, 240, 320, 90, 131819, 30, 30., cv::CAP_MSMF) #if 0 // https://filesamples.com/samples/video/mp4/sample_960x400_ocean_with_audio.mp4 , paramCombination("sample_960x400_ocean_with_audio.mp4", 2, -1/*eplsilon*/, CV_8UC3, 400, 960, 1116, 2056588, 30, 30., cv::CAP_MSMF) @@ -282,10 +285,12 @@ const paramCombination mediaParams[] = }; INSTANTIATE_TEST_CASE_P(/**/, Media, testing::ValuesIn(mediaParams)); -#endif // _WIN32 TEST(AudioOpenCheck, bad_arg_invalid_audio_stream) { + if (!videoio_registry::hasBackend(cv::VideoCaptureAPIs(cv::CAP_MSMF))) + throw SkipTestException("CAP_MSMF backend was not found"); + std::string fileName = "audio/test_audio.wav"; std::vector params { CAP_PROP_AUDIO_STREAM, 1, @@ -293,12 +298,15 @@ TEST(AudioOpenCheck, bad_arg_invalid_audio_stream) CAP_PROP_AUDIO_DATA_DEPTH, CV_16S }; VideoCapture cap; - cap.open(findDataFile(fileName), cv::CAP_ANY, params); + cap.open(findDataFile(fileName), cv::CAP_MSMF, params); ASSERT_FALSE(cap.isOpened()); } TEST(AudioOpenCheck, bad_arg_invalid_audio_stream_video) { + if (!videoio_registry::hasBackend(cv::VideoCaptureAPIs(cv::CAP_MSMF))) + throw SkipTestException("CAP_MSMF backend was not found"); + std::string fileName = "audio/test_audio.mp4"; std::vector params { CAP_PROP_AUDIO_STREAM, 1, @@ -306,13 +314,16 @@ TEST(AudioOpenCheck, bad_arg_invalid_audio_stream_video) CAP_PROP_AUDIO_DATA_DEPTH, CV_16S }; VideoCapture cap; - cap.open(findDataFile(fileName), cv::CAP_ANY, params); + cap.open(findDataFile(fileName), cv::CAP_MSMF, params); ASSERT_FALSE(cap.isOpened()); } -#ifdef _WIN32 + TEST(AudioOpenCheck, MSMF_bad_arg_invalid_audio_sample_per_second) { + if (!videoio_registry::hasBackend(cv::VideoCaptureAPIs(cv::CAP_MSMF))) + throw SkipTestException("CAP_MSMF backend was not found"); + std::string fileName = "audio/test_audio.mp4"; std::vector params { CAP_PROP_AUDIO_STREAM, 0, @@ -323,7 +334,6 @@ TEST(AudioOpenCheck, MSMF_bad_arg_invalid_audio_sample_per_second) cap.open(findDataFile(fileName), cv::CAP_MSMF, params); ASSERT_FALSE(cap.isOpened()); } -#endif TEST(AudioOpenCheck, bad_arg_invalid_audio_sample_per_second) { diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index 115705760c..906b215ab4 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -55,7 +55,11 @@ TEST_P(videoio_ffmpeg, write_big) remove(filename.c_str()); } +#if defined(OPENCV_32BIT_CONFIGURATION) +static const Size bigSize(1920, 1080); +#else static const Size bigSize(4096, 4096); +#endif const FourCC_Ext_Size entries[] = { @@ -69,7 +73,9 @@ const FourCC_Ext_Size entries[] = make_tuple("mp4v", "avi", bigSize), make_tuple("MPEG", "avi", Size(720, 576)), make_tuple("XVID", "avi", bigSize), - make_tuple("H264", "mp4", Size(4096, 2160)) + make_tuple("H264", "mp4", Size(4096, 2160)), + make_tuple("FFV1", "avi", bigSize), + make_tuple("FFV1", "mkv", bigSize) }; INSTANTIATE_TEST_CASE_P(videoio, videoio_ffmpeg, testing::ValuesIn(entries)); @@ -95,6 +101,54 @@ TEST(videoio_ffmpeg, image) //========================================================================== +typedef tuple videoio_read_params_t; +typedef testing::TestWithParam< testing::tuple> videoio_read; + +TEST_P(videoio_read, threads) +{ + const VideoCaptureAPIs api = CAP_FFMPEG; + if (!videoio_registry::hasBackend(api)) + throw SkipTestException("Backend was not found"); + const string fileName = get<0>(get<0>(GetParam())); + const int nFrames = get<1>(get<0>(GetParam())); + const bool fixedThreadCount = get<2>(get<0>(GetParam())); + const int nThreads = get<1>(GetParam()); + const bool rawRead = get<2>(GetParam()); + VideoCapture cap(findDataFile(fileName), api, { CAP_PROP_N_THREADS, nThreads }); + if (!cap.isOpened()) + throw SkipTestException("Video stream is not supported"); + if (nThreads == 0 || fixedThreadCount) + EXPECT_EQ(cap.get(CAP_PROP_N_THREADS), VideoCapture(findDataFile(fileName), api).get(CAP_PROP_N_THREADS)); + else + EXPECT_EQ(cap.get(CAP_PROP_N_THREADS), nThreads); + if (rawRead && !cap.set(CAP_PROP_FORMAT, -1)) // turn off video decoder (extract stream) + throw SkipTestException("Fetching of RAW video streams is not supported"); + Mat frame; + int n = 0; + while (cap.read(frame)) { + ASSERT_FALSE(frame.empty()); + n++; + } + ASSERT_EQ(n, nFrames); +} + +const videoio_read_params_t videoio_read_params[] = +{ + videoio_read_params_t("video/big_buck_bunny.h264", 125, false), + //videoio_read_params_t("video/big_buck_bunny.h265", 125, false), + videoio_read_params_t("video/big_buck_bunny.mjpg.avi", 125, true), + //videoio_read_params_t("video/big_buck_bunny.mov", 125, false), + //videoio_read_params_t("video/big_buck_bunny.mp4", 125, false), + //videoio_read_params_t("video/big_buck_bunny.mpg", 125, false), + //videoio_read_params_t("video/big_buck_bunny.wmv", 125, true), +}; + +INSTANTIATE_TEST_CASE_P(/**/, videoio_read, testing::Combine(testing::ValuesIn(videoio_read_params), + testing::Values(0, 1, 2, 50), + testing::Values(true, false))); + +//========================================================================== + typedef tuple videoio_container_params_t; typedef testing::TestWithParam< videoio_container_params_t > videoio_container; @@ -338,27 +392,6 @@ typedef std::vector cap_properties_t; typedef std::pair ffmpeg_cap_properties_param_t; typedef testing::TestWithParam ffmpeg_cap_properties; -#ifdef _WIN32 -namespace { -::testing::AssertionResult IsOneOf(double value, double expected1, double expected2) -{ - // internal floating point class is used to perform accurate floating point types comparison - typedef ::testing::internal::FloatingPoint FloatingPoint; - - FloatingPoint val(value); - if (val.AlmostEquals(FloatingPoint(expected1)) || val.AlmostEquals(FloatingPoint(expected2))) - { - return ::testing::AssertionSuccess(); - } - else - { - return ::testing::AssertionFailure() - << value << " is neither equal to " << expected1 << " nor " << expected2; - } -} -} -#endif - TEST_P(ffmpeg_cap_properties, can_read_property) { if (!videoio_registry::hasBackend(CAP_FFMPEG)) @@ -375,13 +408,8 @@ TEST_P(ffmpeg_cap_properties, can_read_property) { const cap_property_t& prop = properties[i]; const double actualValue = cap.get(static_cast(prop.first)); - #ifndef _WIN32 EXPECT_DOUBLE_EQ(actualValue, prop.second) << "Property " << static_cast(prop.first) << " has wrong value"; - #else - EXPECT_TRUE(IsOneOf(actualValue, prop.second, 0.0)) - << "Property " << static_cast(prop.first) << " has wrong value"; - #endif } } @@ -399,71 +427,33 @@ const ffmpeg_cap_properties_param_t videoio_ffmpeg_properties[] = { INSTANTIATE_TEST_CASE_P(videoio, ffmpeg_cap_properties, testing::ValuesIn(videoio_ffmpeg_properties)); +typedef tuple ffmpeg_get_fourcc_param_t; +typedef testing::TestWithParam ffmpeg_get_fourcc; - -// related issue: https://github.com/opencv/opencv/issues/15499 -TEST(videoio, mp4_orientation_meta_auto) +TEST_P(ffmpeg_get_fourcc, check_short_codecs) { - if (!videoio_registry::hasBackend(CAP_FFMPEG)) - throw SkipTestException("FFmpeg backend was not found"); - - string video_file = string(cvtest::TS::ptr()->get_data_path()) + "video/big_buck_bunny_rotated.mp4"; - - VideoCapture cap; - EXPECT_NO_THROW(cap.open(video_file, CAP_FFMPEG)); - ASSERT_TRUE(cap.isOpened()) << "Can't open the video: " << video_file << " with backend " << CAP_FFMPEG << std::endl; - -#ifndef _WIN32 // TODO: FFmpeg wrapper update - // related issue: https://github.com/opencv/opencv/issues/22088 - EXPECT_EQ(90, cap.get(CAP_PROP_ORIENTATION_META)); -#endif - - cap.set(CAP_PROP_ORIENTATION_AUTO, true); - if (cap.get(CAP_PROP_ORIENTATION_AUTO) == 0) - throw SkipTestException("FFmpeg frame rotation metadata is not supported"); - - Size actual; - EXPECT_NO_THROW(actual = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), - (int)cap.get(CAP_PROP_FRAME_HEIGHT))); - EXPECT_EQ(384, actual.width); - EXPECT_EQ(672, actual.height); - - Mat frame; - - cap >> frame; - - ASSERT_EQ(384, frame.cols); - ASSERT_EQ(672, frame.rows); + const VideoCaptureAPIs api = CAP_FFMPEG; + if (!videoio_registry::hasBackend(api)) + throw SkipTestException("Backend was not found"); + const string fileName = get<0>(GetParam()); + const string fourcc_string = get<1>(GetParam()); + VideoCapture cap(findDataFile(fileName), api); + if (!cap.isOpened()) + throw SkipTestException("Video stream is not supported"); + const double fourcc = cap.get(CAP_PROP_FOURCC); + ASSERT_EQ(fourcc_string, fourccToString((int)fourcc)); } -// related issue: https://github.com/opencv/opencv/issues/15499 -TEST(videoio, mp4_orientation_no_rotation) +const ffmpeg_get_fourcc_param_t ffmpeg_get_fourcc_param[] = { - if (!videoio_registry::hasBackend(CAP_FFMPEG)) - throw SkipTestException("FFmpeg backend was not found"); - - string video_file = string(cvtest::TS::ptr()->get_data_path()) + "video/big_buck_bunny_rotated.mp4"; - - VideoCapture cap; - EXPECT_NO_THROW(cap.open(video_file, CAP_FFMPEG)); - cap.set(CAP_PROP_ORIENTATION_AUTO, 0); - ASSERT_TRUE(cap.isOpened()) << "Can't open the video: " << video_file << " with backend " << CAP_FFMPEG << std::endl; - ASSERT_FALSE(cap.get(CAP_PROP_ORIENTATION_AUTO)); - - Size actual; - EXPECT_NO_THROW(actual = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), - (int)cap.get(CAP_PROP_FRAME_HEIGHT))); - EXPECT_EQ(672, actual.width); - EXPECT_EQ(384, actual.height); - - Mat frame; - - cap >> frame; - - ASSERT_EQ(672, frame.cols); - ASSERT_EQ(384, frame.rows); -} + ffmpeg_get_fourcc_param_t("../cv/tracking/faceocc2/data/faceocc2.webm", "VP80"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libvpx-vp9.mp4", "vp09"), + ffmpeg_get_fourcc_param_t("video/sample_322x242_15frames.yuv420p.libaom-av1.mp4", "av01"), + ffmpeg_get_fourcc_param_t("video/big_buck_bunny.h265", "hevc"), + ffmpeg_get_fourcc_param_t("video/big_buck_bunny.h264", "h264") +}; +INSTANTIATE_TEST_CASE_P(videoio, ffmpeg_get_fourcc, testing::ValuesIn(ffmpeg_get_fourcc_param)); static void ffmpeg_check_read_raw(VideoCapture& cap) { @@ -491,10 +481,6 @@ TEST(videoio_ffmpeg, ffmpeg_check_extra_data) EXPECT_NO_THROW(cap.open(video_file, CAP_FFMPEG)); ASSERT_TRUE(cap.isOpened()) << "Can't open the video"; const int codecExtradataIdx = (int)cap.get(CAP_PROP_CODEC_EXTRADATA_INDEX); -#ifdef _WIN32 // handle old FFmpeg backend - if (codecExtradataIdx <= 0) - throw SkipTestException("Codec extra data is not supported by backend or video stream"); -#endif Mat data; ASSERT_TRUE(cap.retrieve(data, codecExtradataIdx)); EXPECT_EQ(CV_8UC1, data.type()) << "CV_8UC1 != " << typeToString(data.type()); @@ -554,4 +540,89 @@ TEST(videoio_ffmpeg, DISABLED_open_from_web) EXPECT_EQ((int)14315, n_frames); } + +typedef tuple FourCC_Ext_Color_Support; +typedef testing::TestWithParam< FourCC_Ext_Color_Support > videoio_ffmpeg_16bit; + +TEST_P(videoio_ffmpeg_16bit, basic) +{ + if (!videoio_registry::hasBackend(CAP_FFMPEG)) + throw SkipTestException("FFmpeg backend was not found"); + + const int fourcc = fourccFromString(get<0>(GetParam())); + const string ext = string(".") + get<1>(GetParam()); + const bool isColor = get<2>(GetParam()); + const bool isSupported = get<3>(GetParam()); + const int cn = isColor ? 3 : 1; + const int dataType = CV_16UC(cn); + + const string filename = tempfile(ext.c_str()); + const Size sz(640, 480); + const double fps = 30.0; + const double time_sec = 1; + const int numFrames = static_cast(fps * time_sec); + + { + VideoWriter writer; + writer.open(filename, CAP_FFMPEG, fourcc, fps, sz, + { + VIDEOWRITER_PROP_DEPTH, CV_16U, + VIDEOWRITER_PROP_IS_COLOR, isColor + }); + + ASSERT_EQ(isSupported, writer.isOpened()); + if (isSupported) + { + Mat img(sz, dataType, Scalar::all(0)); + const int coeff = cvRound(min(sz.width, sz.height)/(fps * time_sec)); + for (int i = 0 ; i < numFrames; i++ ) + { + rectangle(img, + Point2i(coeff * i, coeff * i), + Point2i(coeff * (i + 1), coeff * (i + 1)), + Scalar::all(255 * (1.0 - static_cast(i) / (fps * time_sec * 2))), + -1); + writer << img; + } + writer.release(); + EXPECT_GT(getFileSize(filename), 8192); + } + } + + if (isSupported) + { + VideoCapture cap; + ASSERT_TRUE(cap.open(filename, CAP_FFMPEG, {CAP_PROP_CONVERT_RGB, false})); + ASSERT_TRUE(cap.isOpened()); + Mat img; + bool res = true; + int numRead = 0; + while(res) + { + res = cap.read(img); + if (res) + { + ++numRead; + ASSERT_EQ(img.type(), dataType); + ASSERT_EQ(img.size(), sz); + } + } + ASSERT_EQ(numRead, numFrames); + remove(filename.c_str()); + } +} + +const FourCC_Ext_Color_Support sixteen_bit_modes[] = +{ + // 16-bit grayscale is supported + make_tuple("FFV1", "avi", false, true), + make_tuple("FFV1", "mkv", false, true), + // 16-bit color formats are NOT supported + make_tuple("FFV1", "avi", true, false), + make_tuple("FFV1", "mkv", true, false), + +}; + +INSTANTIATE_TEST_CASE_P(/**/, videoio_ffmpeg_16bit, testing::ValuesIn(sixteen_bit_modes)); + }} // namespace diff --git a/modules/videoio/test/test_gstreamer.cpp b/modules/videoio/test/test_gstreamer.cpp index 207f6de50b..a8c24be438 100644 --- a/modules/videoio/test/test_gstreamer.cpp +++ b/modules/videoio/test/test_gstreamer.cpp @@ -151,4 +151,31 @@ TEST(videoio_gstreamer, gray16_writing) EXPECT_EQ(0, remove(temp_file.c_str())); } +TEST(videoio_gstreamer, timeout_property) +{ + if (!videoio_registry::hasBackend(CAP_GSTREAMER)) + throw SkipTestException("GStreamer backend was not found"); + + VideoCapture cap; + cap.open("videotestsrc ! appsink", CAP_GSTREAMER); + ASSERT_TRUE(cap.isOpened()); + const double default_timeout = 30000; // 30 seconds + const double open_timeout = 5678; // 3 seconds + const double read_timeout = 1234; // 1 second + EXPECT_NEAR(default_timeout, cap.get(CAP_PROP_OPEN_TIMEOUT_MSEC), 1e-3); + const double current_read_timeout = cap.get(CAP_PROP_READ_TIMEOUT_MSEC); + const bool read_timeout_supported = current_read_timeout > 0.0; + if (read_timeout_supported) + { + EXPECT_NEAR(default_timeout, current_read_timeout, 1e-3); + } + cap.set(CAP_PROP_OPEN_TIMEOUT_MSEC, open_timeout); + EXPECT_NEAR(open_timeout, cap.get(CAP_PROP_OPEN_TIMEOUT_MSEC), 1e-3); + if (read_timeout_supported) + { + cap.set(CAP_PROP_READ_TIMEOUT_MSEC, read_timeout); + EXPECT_NEAR(read_timeout, cap.get(CAP_PROP_READ_TIMEOUT_MSEC), 1e-3); + } +} + }} // namespace diff --git a/modules/videoio/test/test_orientation.cpp b/modules/videoio/test/test_orientation.cpp new file mode 100644 index 0000000000..96530e2a03 --- /dev/null +++ b/modules/videoio/test/test_orientation.cpp @@ -0,0 +1,76 @@ +// 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" + +using namespace std; + +namespace opencv_test { namespace { + +typedef TestWithParam VideoCaptureAPITests; + +// related issue: https://github.com/opencv/opencv/issues/15499 +TEST_P(VideoCaptureAPITests, mp4_orientation_meta_auto) +{ + cv::VideoCaptureAPIs api = GetParam(); + if (!videoio_registry::hasBackend(api)) + throw SkipTestException("backend " + std::to_string(int(api)) + " was not found"); + + string video_file = string(cvtest::TS::ptr()->get_data_path()) + "video/rotated_metadata.mp4"; + + VideoCapture cap; + EXPECT_NO_THROW(cap.open(video_file, api)); + ASSERT_TRUE(cap.isOpened()) << "Can't open the video: " << video_file << " with backend " << api << std::endl; + + // related issue: https://github.com/opencv/opencv/issues/22088 + EXPECT_EQ(90, cap.get(CAP_PROP_ORIENTATION_META)); + + EXPECT_TRUE(cap.set(CAP_PROP_ORIENTATION_AUTO, true)); + + Size actual; + EXPECT_NO_THROW(actual = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), + (int)cap.get(CAP_PROP_FRAME_HEIGHT))); + EXPECT_EQ(270, actual.width); + EXPECT_EQ(480, actual.height); + + Mat frame; + + cap >> frame; + + ASSERT_EQ(270, frame.cols); + ASSERT_EQ(480, frame.rows); +} + +// related issue: https://github.com/opencv/opencv/issues/15499 +TEST_P(VideoCaptureAPITests, mp4_orientation_no_rotation) +{ + cv::VideoCaptureAPIs api = GetParam(); + if (!videoio_registry::hasBackend(api)) + throw SkipTestException("backend " + std::to_string(int(api)) + " was not found"); + + string video_file = string(cvtest::TS::ptr()->get_data_path()) + "video/rotated_metadata.mp4"; + + VideoCapture cap; + EXPECT_NO_THROW(cap.open(video_file, api)); + cap.set(CAP_PROP_ORIENTATION_AUTO, 0); + ASSERT_TRUE(cap.isOpened()) << "Can't open the video: " << video_file << " with backend " << api << std::endl; + ASSERT_FALSE(cap.get(CAP_PROP_ORIENTATION_AUTO)); + + Size actual; + EXPECT_NO_THROW(actual = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), + (int)cap.get(CAP_PROP_FRAME_HEIGHT))); + EXPECT_EQ(480, actual.width); + EXPECT_EQ(270, actual.height); + + Mat frame; + + cap >> frame; + + ASSERT_EQ(480, frame.cols); + ASSERT_EQ(270, frame.rows); +} + +INSTANTIATE_TEST_CASE_P(videoio, VideoCaptureAPITests, testing::Values(CAP_FFMPEG, CAP_AVFOUNDATION)); + +}} // namespace diff --git a/modules/videoio/test/test_video_io.cpp b/modules/videoio/test/test_video_io.cpp index 84d5aae32d..bf027c10e9 100644 --- a/modules/videoio/test/test_video_io.cpp +++ b/modules/videoio/test/test_video_io.cpp @@ -215,8 +215,24 @@ public: throw SkipTestException(cv::String("Backend ") + cv::videoio_registry::getBackendName(apiPref) + cv::String(" can't open the video: ") + video_file); + int frame_count = (int)cap.get(CAP_PROP_FRAME_COUNT); + + // HACK: Video consists of 125 frames, but cv::VideoCapture with FFmpeg reports only 122 frames for mpg video. + // mpg file reports 5.08 sec * 24 fps => property returns 122 frames,but actual number of frames returned is 125 + // HACK: CAP_PROP_FRAME_COUNT is not supported for vmw + MSMF. Just force check for all 125 frames + if (ext == "mpg") + EXPECT_GT(frame_count, 121); + else if ((ext == "wmv") && (apiPref == CAP_MSMF)) + frame_count = 125; + else + EXPECT_EQ(frame_count, 125); Mat img; - for(int i = 0; i < 10; i++) + + // HACK: FFmpeg reports picture_pts = AV_NOPTS_VALUE_ for the last frame for AVI container by some reason + if ((ext == "avi") && (apiPref == CAP_FFMPEG)) + frame_count--; + + for (int i = 0; i < frame_count; i++) { double timestamp = 0; ASSERT_NO_THROW(cap >> img); @@ -391,6 +407,8 @@ static Ext_Fourcc_PSNR synthetic_params[] = { {"mkv", "XVID", 30.f, CAP_FFMPEG}, {"mkv", "MPEG", 30.f, CAP_FFMPEG}, {"mkv", "MJPG", 30.f, CAP_FFMPEG}, + {"avi", "FFV1", 30.f, CAP_FFMPEG}, + {"mkv", "FFV1", 30.f, CAP_FFMPEG}, {"avi", "MPEG", 28.f, CAP_GSTREAMER}, {"avi", "MJPG", 30.f, CAP_GSTREAMER}, @@ -775,8 +793,8 @@ static const VideoCaptureAccelerationInput hw_filename[] = { { "sample_322x242_15frames.yuv420p.libxvid.mp4", 28.0 }, { "sample_322x242_15frames.yuv420p.mjpeg.mp4", 20.0 }, { "sample_322x242_15frames.yuv420p.mpeg2video.mp4", 24.0 }, // GSTREAMER on Ubuntu 18.04 - { "sample_322x242_15frames.yuv420p.libx264.mp4", 24.0 }, // GSTREAMER on Ubuntu 18.04 - { "sample_322x242_15frames.yuv420p.libx265.mp4", 30.0 }, + { "sample_322x242_15frames.yuv420p.libx264.mp4", 20.0 }, // 20 - D3D11 (i7-11800H), 23 - D3D11 on GHA/Windows, GSTREAMER on Ubuntu 18.04 + { "sample_322x242_15frames.yuv420p.libx265.mp4", 20.0 }, // 20 - D3D11 (i7-11800H), 23 - D3D11 on GHA/Windows { "sample_322x242_15frames.yuv420p.libvpx-vp9.mp4", 30.0 }, { "sample_322x242_15frames.yuv420p.libaom-av1.mp4", 30.0 } }; diff --git a/modules/world/CMakeLists.txt b/modules/world/CMakeLists.txt index 2f4b1a4eb1..b14378599e 100644 --- a/modules/world/CMakeLists.txt +++ b/modules/world/CMakeLists.txt @@ -59,7 +59,7 @@ ocv_module_include_directories() #message(STATUS "${OPENCV_MODULE_${the_module}_SOURCES}") ocv_create_module(${link_deps}) -if(";${OPENCV_MODULES_BUILD};" MATCHES ";opencv_viz;" AND OPENCV_MODULE_opencv_viz_IS_PART_OF_WORLD AND VTK_VERSION VERSION_GREATER_EQUAL "8.90.0") +if(";${OPENCV_MODULES_BUILD};" MATCHES ";opencv_viz;" AND OPENCV_MODULE_opencv_viz_IS_PART_OF_WORLD AND NOT (VTK_VERSION VERSION_LESS "8.90.0")) vtk_module_autoinit(TARGETS opencv_world MODULES ${VTK_LIBRARIES}) endif() diff --git a/platforms/android/android.toolchain.cmake b/platforms/android/android.toolchain.cmake index 1dca060fdf..69c5598a97 100644 --- a/platforms/android/android.toolchain.cmake +++ b/platforms/android/android.toolchain.cmake @@ -399,7 +399,7 @@ if( NOT ANDROID_NDK ) __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN ) if( NOT ANDROID_STANDALONE_TOOLCHAIN ) - #try to find Android NDK in one of the the default locations + #try to find Android NDK in one of the default locations set( __ndkSearchPaths ) foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) @@ -413,7 +413,7 @@ if( NOT ANDROID_NDK ) message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" ) message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" ) else() - #try to find Android standalone toolchain in one of the the default locations + #try to find Android standalone toolchain in one of the default locations __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) if( ANDROID_STANDALONE_TOOLCHAIN ) diff --git a/platforms/android/ndk-25.config.py b/platforms/android/ndk-25.config.py new file mode 100644 index 0000000000..b6b12126eb --- /dev/null +++ b/platforms/android/ndk-25.config.py @@ -0,0 +1,19 @@ +# Docs: https://developer.android.com/ndk/guides/cmake#android_native_api_level +ANDROID_NATIVE_API_LEVEL = int(os.environ.get('ANDROID_NATIVE_API_LEVEL', 32)) +cmake_common_vars = { + # Docs: https://source.android.com/docs/setup/about/build-numbers + # Docs: https://developer.android.com/studio/publish/versioning + 'ANDROID_COMPILE_SDK_VERSION': os.environ.get('ANDROID_COMPILE_SDK_VERSION', 32), + 'ANDROID_TARGET_SDK_VERSION': os.environ.get('ANDROID_TARGET_SDK_VERSION', 32), + 'ANDROID_MIN_SDK_VERSION': os.environ.get('ANDROID_MIN_SDK_VERSION', ANDROID_NATIVE_API_LEVEL), + # Docs: https://developer.android.com/studio/releases/gradle-plugin + 'ANDROID_GRADLE_PLUGIN_VERSION': '7.3.1', + 'GRADLE_VERSION': '7.5.1', + 'KOTLIN_PLUGIN_VERSION': '1.5.20', +} +ABIs = [ + ABI("2", "armeabi-v7a", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars), + ABI("3", "arm64-v8a", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars), + ABI("5", "x86_64", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars), + ABI("4", "x86", None, ndk_api_level=ANDROID_NATIVE_API_LEVEL, cmake_vars=cmake_common_vars), +] diff --git a/platforms/js/opencv_js.config.py b/platforms/js/opencv_js.config.py index 7ab624a439..53afc87d3f 100644 --- a/platforms/js/opencv_js.config.py +++ b/platforms/js/opencv_js.config.py @@ -111,7 +111,11 @@ imgproc = { objdetect = {'': ['groupRectangles'], 'HOGDescriptor': ['load', 'HOGDescriptor', 'getDefaultPeopleDetector', 'getDaimlerPeopleDetector', 'setSVMDetector', 'detectMultiScale'], 'CascadeClassifier': ['load', 'detectMultiScale2', 'CascadeClassifier', 'detectMultiScale3', 'empty', 'detectMultiScale'], - 'QRCodeDetector': ['QRCodeDetector', 'decode', 'decodeCurved', 'detect', 'detectAndDecode', 'detectMulti', 'setEpsX', 'setEpsY']} + 'QRCodeDetector': ['QRCodeDetector', 'decode', 'decodeCurved', 'detect', 'detectAndDecode', 'detectMulti', 'setEpsX', 'setEpsY'], + 'ArucoDetector': ['getPredefinedDictionary', 'detectMarkers', 'refineDetectedMarkers', 'getDictionary', 'stetDictionary', 'getDetectorParameters', 'setDetectorParameters', 'getRefineParameters', 'setRefineParameters'], + 'GridBoard': ['create','generateImage', 'getGridSize', 'getMarkerLength', 'getMarkerSeparation'], + 'CharucoBoard': ['create', 'generateImage', 'getChessboardCorners', 'getNearestMarkerCorners', 'checkCharucoCornersCollinear'] +} video = { '': [ @@ -135,7 +139,7 @@ dnn = {'dnn_Net': ['setInput', 'forward', 'setPreferableBackend'], features2d = {'Feature2D': ['detect', 'compute', 'detectAndCompute', 'descriptorSize', 'descriptorType', 'defaultNorm', 'empty', 'getDefaultName'], 'BRISK': ['create', 'getDefaultName'], - 'ORB': ['create', 'setMaxFeatures', 'setScaleFactor', 'setNLevels', 'setEdgeThreshold', 'setFirstLevel', 'setWTA_K', 'setScoreType', 'setPatchSize', 'getFastThreshold', 'getDefaultName'], + 'ORB': ['create', 'setMaxFeatures', 'setScaleFactor', 'setNLevels', 'setEdgeThreshold', 'setFastThreshold', 'setFirstLevel', 'setWTA_K', 'setScoreType', 'setPatchSize', 'getFastThreshold', 'getDefaultName'], 'MSER': ['create', 'detectRegions', 'setDelta', 'getDelta', 'setMinArea', 'getMinArea', 'setMaxArea', 'getMaxArea', 'setPass2Only', 'getPass2Only', 'getDefaultName'], 'FastFeatureDetector': ['create', 'setThreshold', 'getThreshold', 'setNonmaxSuppression', 'getNonmaxSuppression', 'setType', 'getType', 'getDefaultName'], 'AgastFeatureDetector': ['create', 'setThreshold', 'getThreshold', 'setNonmaxSuppression', 'getNonmaxSuppression', 'setType', 'getType', 'getDefaultName'], @@ -168,14 +172,6 @@ photo = {'': ['createAlignMTB', 'createCalibrateDebevec', 'createCalibrateRobert 'getColorAdaptation', 'setColorAdaptation'] } -aruco = {'': ['detectMarkers', 'drawDetectedMarkers', 'drawAxis', 'estimatePoseSingleMarkers', 'estimatePoseBoard', 'estimatePoseCharucoBoard', 'interpolateCornersCharuco', 'drawDetectedCornersCharuco'], - 'aruco_Dictionary': ['get', 'drawMarker'], - 'aruco_Board': ['create'], - 'aruco_GridBoard': ['create', 'draw'], - 'aruco_CharucoBoard': ['create', 'draw'], - 'aruco_DetectorParameters': ['create'] - } - _3d = { '': [ 'findHomography', @@ -203,7 +199,7 @@ calib = { } -white_list = makeWhiteList([core, imgproc, objdetect, video, dnn, features2d, photo, aruco, _3d, calib]) +white_list = makeWhiteList([core, imgproc, objdetect, video, dnn, features2d, photo, _3d, calib]) # namespace_prefix_override['dnn'] = '' # compatibility stuff (enabled by default) # namespace_prefix_override['aruco'] = '' # compatibility stuff (enabled by default) diff --git a/platforms/linux/riscv-gnu.toolchain.cmake b/platforms/linux/riscv-gnu.toolchain.cmake new file mode 100644 index 0000000000..662fb6bddb --- /dev/null +++ b/platforms/linux/riscv-gnu.toolchain.cmake @@ -0,0 +1,60 @@ +if(COMMAND toolchain_save_config) + return() # prevent recursive call +endif() + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_VERSION 1) +if(NOT DEFINED CMAKE_SYSTEM_PROCESSOR) + set(CMAKE_SYSTEM_PROCESSOR riscv64) +else() + #message("CMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/gnu.toolchain.cmake") + +if(NOT "x${GCC_COMPILER_VERSION}" STREQUAL "x") + set(__GCC_VER_SUFFIX "-${GCC_COMPILER_VERSION}") +endif() + +if(NOT DEFINED GNU_MACHINE) + set(GNU_MACHINE riscv64-unknown-linux-gnu CACHE STRING "GNU compiler triple") +endif() + +if(NOT DEFINED TOOLCHAIN_COMPILER_LOCATION_HINT) + set(TOOLCHAIN_COMPILER_LOCATION_HINT PATHS /opt/riscv/bin ENV PATH) +endif() + +if(NOT DEFINED CMAKE_C_COMPILER) + find_program(CMAKE_C_COMPILER NAMES ${GNU_MACHINE}-gcc${__GCC_VER_SUFFIX} ${TOOLCHAIN_COMPILER_LOCATION_HINT}) +else() + #message(WARNING "CMAKE_C_COMPILER=${CMAKE_C_COMPILER} is defined") +endif() +if(NOT DEFINED CMAKE_CXX_COMPILER) + find_program(CMAKE_CXX_COMPILER NAMES ${GNU_MACHINE}-g++${__GCC_VER_SUFFIX} ${TOOLCHAIN_COMPILER_LOCATION_HINT}) +else() + #message(WARNING "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} is defined") +endif() +if(NOT DEFINED CMAKE_LINKER) + find_program(CMAKE_LINKER NAMES ${GNU_MACHINE}-ld${__GCC_VER_SUFFIX} ${GNU_MACHINE}-ld ${TOOLCHAIN_COMPILER_LOCATION_HINT}) +else() + #message(WARNING "CMAKE_LINKER=${CMAKE_LINKER} is defined") +endif() +if(NOT DEFINED CMAKE_AR) + find_program(CMAKE_AR NAMES ${GNU_MACHINE}-ar${__GCC_VER_SUFFIX} ${GNU_MACHINE}-ar ${TOOLCHAIN_COMPILER_LOCATION_HINT}) +else() + #message(WARNING "CMAKE_AR=${CMAKE_AR} is defined") +endif() + +if(NOT DEFINED RISCV_SYSROOT) + get_filename_component(_base_dir ${CMAKE_C_COMPILER} DIRECTORY) + get_filename_component(_base_dir ${_base_dir} DIRECTORY) + set(RISCV_SYSROOT ${_base_dir}/sysroot CACHE PATH "RISC-V sysroot") +endif() + +set(CMAKE_SYSROOT "${RISCV_SYSROOT}") +set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${RISCV_SYSROOT}) + +set(TOOLCHAIN_CONFIG_VARS ${TOOLCHAIN_CONFIG_VARS} + RISCV_SYSROOT +) +toolchain_save_config() diff --git a/platforms/linux/riscv.toolchain.cmake b/platforms/linux/riscv.toolchain.cmake deleted file mode 100644 index cea80bd9ba..0000000000 --- a/platforms/linux/riscv.toolchain.cmake +++ /dev/null @@ -1,62 +0,0 @@ -cmake_minimum_required(VERSION 3.5) - -if(COMMAND toolchain_save_config) - return() # prevent recursive call -endif() - -set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_VERSION 1) - -include("${CMAKE_CURRENT_LIST_DIR}/gnu.toolchain.cmake") - -MESSAGE(STATUS "Debug: CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}") - -if(NOT "x${GCC_COMPILER_VERSION}" STREQUAL "x") - set(__GCC_VER_SUFFIX "-${GCC_COMPILER_VERSION}") -endif() - -if(NOT DEFINED CMAKE_C_COMPILER) - MESSAGE("Looking for compler.. ${GNU_MACHINE}-gcc${__GCC_VER_SUFFIX}") - find_program(CMAKE_C_COMPILER NAMES ${GNU_MACHINE}-gcc${__GCC_VER_SUFFIX}) -else() - #message(WARNING "CMAKE_C_COMPILER=${CMAKE_C_COMPILER} is defined") -endif() -if(NOT DEFINED CMAKE_CXX_COMPILER) - find_program(CMAKE_CXX_COMPILER NAMES ${GNU_MACHINE}-g++${__GCC_VER_SUFFIX}) -else() - #message(WARNING "CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} is defined") -endif() -if(NOT DEFINED CMAKE_LINKER) - find_program(CMAKE_LINKER NAMES ${GNU_MACHINE}-ld${__GCC_VER_SUFFIX} ${GNU_MACHINE}-ld) -else() - #message(WARNING "CMAKE_LINKER=${CMAKE_LINKER} is defined") -endif() -if(NOT DEFINED CMAKE_AR) - find_program(CMAKE_AR NAMES ${GNU_MACHINE}-ar${__GCC_VER_SUFFIX} ${GNU_MACHINE}-ar) -else() - #message(WARNING "CMAKE_AR=${CMAKE_AR} is defined") -endif() - -if(NOT DEFINED RISCV_LINUX_SYSROOT AND DEFINED GNU_MACHINE) - set(RISCV_LINUX_SYSROOT /usr/${GNU_MACHINE}) -endif() - -if(NOT DEFINED CMAKE_CXX_FLAGS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdata-sections -Wa,--noexecstack -fsigned-char -Wno-psabi") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,nocopyreloc") - - set(RISCV_LINKER_FLAGS "-Wl,--no-undefined -Wl,--gc-sections -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now") - set(CMAKE_SHARED_LINKER_FLAGS "${RISCV_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}") - set(CMAKE_MODULE_LINKER_FLAGS "${RISCV_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${RISCV_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}") -else() - message(STATUS "User provided flags are used instead of defaults") -endif() - -set(CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH} ${RISCV_LINUX_SYSROOT}) - -set(TOOLCHAIN_CONFIG_VARS ${TOOLCHAIN_CONFIG_VARS} - RISCV_LINUX_SYSROOT -) -toolchain_save_config() diff --git a/platforms/linux/riscv64-clang.toolchain.cmake b/platforms/linux/riscv64-clang.toolchain.cmake index 2efd67ad93..cec5f7d734 100644 --- a/platforms/linux/riscv64-clang.toolchain.cmake +++ b/platforms/linux/riscv64-clang.toolchain.cmake @@ -2,7 +2,7 @@ set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR riscv64) set(RISCV_CLANG_BUILD_ROOT /opt/rvv-llvm CACHE PATH "Path to CLANG for RISC-V cross compiler build directory") -set(RISCV_GCC_INSTALL_ROOT /opt/RISCV CACHE PATH "Path to GCC for RISC-V cross compiler installation directory") +set(RISCV_GCC_INSTALL_ROOT /opt/riscv CACHE PATH "Path to GCC for RISC-V cross compiler installation directory") set(CMAKE_SYSROOT ${RISCV_GCC_INSTALL_ROOT}/sysroot CACHE PATH "RISC-V sysroot") set(CLANG_TARGET_TRIPLE riscv64-unknown-linux-gnu) @@ -17,15 +17,8 @@ set(CMAKE_ASM_COMPILER_TARGET ${CLANG_TARGET_TRIPLE}) # Don't run the linker on compiler check set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) -set(CMAKE_C_FLAGS "-march=rv64gcv --gcc-toolchain=${RISCV_GCC_INSTALL_ROOT} -w ${CMAKE_C_FLAGS}") -set(CMAKE_CXX_FLAGS "-march=rv64gcv --gcc-toolchain=${RISCV_GCC_INSTALL_ROOT} -w ${CXX_FLAGS}") - -set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2") -OPTION(RISCV_RVV_SCALABLE "Use scalable RVV API on RISC-V" ON) # Enabled by default -IF(RISCV_RVV_SCALABLE) - ADD_DEFINITIONS(-DCV_RVV_SCALABLE) -ENDIF() +set(CMAKE_C_FLAGS "-march=rv64gc --gcc-toolchain=${RISCV_GCC_INSTALL_ROOT} -w ${CMAKE_C_FLAGS}") +set(CMAKE_CXX_FLAGS "-march=rv64gc --gcc-toolchain=${RISCV_GCC_INSTALL_ROOT} -w ${CXX_FLAGS}") set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) diff --git a/platforms/linux/riscv64-gcc.toolchain.cmake b/platforms/linux/riscv64-gcc.toolchain.cmake index 675879f86b..c3a0e161e3 100644 --- a/platforms/linux/riscv64-gcc.toolchain.cmake +++ b/platforms/linux/riscv64-gcc.toolchain.cmake @@ -1,20 +1,10 @@ set(CMAKE_SYSTEM_NAME Linux) -set(CMAKE_SYSTEM_PROCESSOR riscv64) +set(CMAKE_SYSTEM_VERSION 1) +set(GNU_MACHINE riscv64-unknown-linux-gnu CACHE STRING "GNU compiler triple") -set(RISCV_GCC_INSTALL_ROOT /opt/RISCV CACHE PATH "Path to GCC for RISC-V cross compiler installation directory") -set(CMAKE_SYSROOT ${RISCV_GCC_INSTALL_ROOT}/sysroot CACHE PATH "RISC-V sysroot") +if(NOT DEFINED CMAKE_CXX_FLAGS) # guards toolchain multiple calls + set(CMAKE_C_FLAGS "-march=rv64gc") + set(CMAKE_CXX_FLAGS "-march=rv64gc") +endif() -set(CMAKE_C_COMPILER ${RISCV_GCC_INSTALL_ROOT}/bin/riscv64-unknown-linux-gnu-gcc) -set(CMAKE_CXX_COMPILER ${RISCV_GCC_INSTALL_ROOT}/bin/riscv64-unknown-linux-gnu-g++) - -# Don't run the linker on compiler check -set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) - -set(CMAKE_C_FLAGS "-march=rv64gcv_zfh ${CMAKE_C_FLAGS}") -set(CMAKE_CXX_FLAGS "-march=rv64gcv_zfh ${CXX_FLAGS}") - -set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) \ No newline at end of file +include("${CMAKE_CURRENT_LIST_DIR}/riscv-gnu.toolchain.cmake") diff --git a/platforms/linux/riscv64-gnu.toolchain.cmake b/platforms/linux/riscv64-gnu.toolchain.cmake deleted file mode 100644 index 3263a1f99b..0000000000 --- a/platforms/linux/riscv64-gnu.toolchain.cmake +++ /dev/null @@ -1,5 +0,0 @@ -set(CMAKE_SYSTEM_PROCESSOR riscv64) -set(GNU_MACHINE riscv64-linux-gnu CACHE STRING "GNU compiler triple") -set(GCC_COMPILER_VERSION "" CACHE STRING "GCC Compiler version") - -include("${CMAKE_CURRENT_LIST_DIR}/riscv.toolchain.cmake") diff --git a/platforms/scripts/valgrind_3rdparty.supp b/platforms/scripts/valgrind_3rdparty.supp index 8ca0afbecf..9e9e8e5895 100644 --- a/platforms/scripts/valgrind_3rdparty.supp +++ b/platforms/scripts/valgrind_3rdparty.supp @@ -215,3 +215,16 @@ fun:start_thread fun:clone } + +{ + avcodec57-ffv1-16bit-ubuntu18 + Memcheck:Cond + ... + obj:/usr/lib/x86_64-linux-gnu/libavcodec.so.57.107.100 + fun:avcodec_default_execute + obj:/usr/lib/x86_64-linux-gnu/libavcodec.so.57.107.100 + fun:avcodec_encode_video2 + obj:/usr/lib/x86_64-linux-gnu/libavcodec.so.57.107.100 + fun:avcodec_send_frame + ... +} diff --git a/samples/_winpack_run_python_sample.cmd b/samples/_winpack_run_python_sample.cmd index c24bd0f5a4..80f38725ae 100644 --- a/samples/_winpack_run_python_sample.cmd +++ b/samples/_winpack_run_python_sample.cmd @@ -23,6 +23,8 @@ IF %ERRORLEVEL% EQU 0 ( GOTO :PYTHON_FOUND ) +CALL :QUERY_PYTHON 3.11 +IF %ERRORLEVEL% EQU 0 GOTO :PYTHON_FOUND CALL :QUERY_PYTHON 3.10 IF %ERRORLEVEL% EQU 0 GOTO :PYTHON_FOUND CALL :QUERY_PYTHON 3.9 diff --git a/samples/android/15-puzzle/gradle/AndroidManifest.xml b/samples/android/15-puzzle/gradle/AndroidManifest.xml index 6d1d2b3bc5..1495649220 100644 --- a/samples/android/15-puzzle/gradle/AndroidManifest.xml +++ b/samples/android/15-puzzle/gradle/AndroidManifest.xml @@ -8,6 +8,7 @@ android:label="@string/app_name"> if (project.pluginManager.hasPlugin('com.android.application') @@ -90,5 +91,14 @@ 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) { + gradle.println("Override ndkVersion for the project ${project.name}") + project.android { + ndkVersion '@ANDROID_NDK_REVISION@' + } + } } } diff --git a/samples/android/camera-calibration/gradle/AndroidManifest.xml b/samples/android/camera-calibration/gradle/AndroidManifest.xml index eaade4a7ba..d1640199d7 100644 --- a/samples/android/camera-calibration/gradle/AndroidManifest.xml +++ b/samples/android/camera-calibration/gradle/AndroidManifest.xml @@ -7,7 +7,9 @@ android:label="@string/app_name" android:icon="@drawable/icon"> - diff --git a/samples/android/color-blob-detection/gradle/AndroidManifest.xml b/samples/android/color-blob-detection/gradle/AndroidManifest.xml index 1f09a5e3ca..4fb8b58210 100644 --- a/samples/android/color-blob-detection/gradle/AndroidManifest.xml +++ b/samples/android/color-blob-detection/gradle/AndroidManifest.xml @@ -8,7 +8,9 @@ android:icon="@drawable/icon" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" > - diff --git a/samples/android/face-detection/gradle/AndroidManifest.xml b/samples/android/face-detection/gradle/AndroidManifest.xml index e3a46ac5a0..157b318d3b 100644 --- a/samples/android/face-detection/gradle/AndroidManifest.xml +++ b/samples/android/face-detection/gradle/AndroidManifest.xml @@ -7,7 +7,9 @@ android:label="@string/app_name" android:icon="@drawable/icon"> - diff --git a/samples/android/image-manipulations/gradle/AndroidManifest.xml b/samples/android/image-manipulations/gradle/AndroidManifest.xml index bc89d9615d..2aa9c09ad6 100644 --- a/samples/android/image-manipulations/gradle/AndroidManifest.xml +++ b/samples/android/image-manipulations/gradle/AndroidManifest.xml @@ -7,7 +7,9 @@ android:label="@string/app_name" android:icon="@drawable/icon"> - diff --git a/samples/android/mobilenet-objdetect/gradle/AndroidManifest.xml b/samples/android/mobilenet-objdetect/gradle/AndroidManifest.xml index d17e633b41..473a5db3d0 100644 --- a/samples/android/mobilenet-objdetect/gradle/AndroidManifest.xml +++ b/samples/android/mobilenet-objdetect/gradle/AndroidManifest.xml @@ -10,8 +10,10 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.AppCompat.NoActionBar"> - + diff --git a/samples/android/tutorial-1-camerapreview/gradle/AndroidManifest.xml b/samples/android/tutorial-1-camerapreview/gradle/AndroidManifest.xml index 490dd6a1e7..79aaf3f5d4 100644 --- a/samples/android/tutorial-1-camerapreview/gradle/AndroidManifest.xml +++ b/samples/android/tutorial-1-camerapreview/gradle/AndroidManifest.xml @@ -8,7 +8,9 @@ android:icon="@drawable/icon" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" > - diff --git a/samples/android/tutorial-2-mixedprocessing/gradle/AndroidManifest.xml b/samples/android/tutorial-2-mixedprocessing/gradle/AndroidManifest.xml index 574facc3d5..90d4bbf8dc 100644 --- a/samples/android/tutorial-2-mixedprocessing/gradle/AndroidManifest.xml +++ b/samples/android/tutorial-2-mixedprocessing/gradle/AndroidManifest.xml @@ -7,7 +7,9 @@ android:label="@string/app_name" android:icon="@drawable/icon"> - diff --git a/samples/android/tutorial-3-cameracontrol/gradle/AndroidManifest.xml b/samples/android/tutorial-3-cameracontrol/gradle/AndroidManifest.xml index d8baa0859c..87cbc760b0 100644 --- a/samples/android/tutorial-3-cameracontrol/gradle/AndroidManifest.xml +++ b/samples/android/tutorial-3-cameracontrol/gradle/AndroidManifest.xml @@ -7,7 +7,9 @@ android:label="@string/app_name" android:icon="@drawable/icon"> - diff --git a/samples/android/tutorial-4-opencl/gradle/AndroidManifest.xml b/samples/android/tutorial-4-opencl/gradle/AndroidManifest.xml index f44d9a5dcc..7cef5cc675 100644 --- a/samples/android/tutorial-4-opencl/gradle/AndroidManifest.xml +++ b/samples/android/tutorial-4-opencl/gradle/AndroidManifest.xml @@ -1,31 +1,32 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/samples/cpp/calibration.cpp b/samples/cpp/calibration.cpp index c5ac757f54..d80564c22c 100644 --- a/samples/cpp/calibration.cpp +++ b/samples/cpp/calibration.cpp @@ -39,9 +39,6 @@ const char * usage = "\n" "\n"; - - - const char* liveCaptureHelp = "When the live video from camera is used as input, the following hot-keys may be used:\n" " , 'q' - quit the program\n" @@ -60,19 +57,25 @@ static void help(char** argv) " # of board views actually available)\n" " [-d=] # a minimum delay in ms between subsequent attempts to capture a next view\n" " # (used only for video capturing)\n" - " [-s=] # square size in some user-defined units (1 by default)\n" + " [-s=] # square size in some user-defined units (1 by default)\n" " [-o=] # the output filename for intrinsic [and extrinsic] parameters\n" " [-op] # write detected feature points\n" " [-oe] # write extrinsic parameters\n" " [-oo] # write refined 3D object points\n" " [-zt] # assume zero tangential distortion\n" - " [-a=] # fix aspect ratio (fx/fy)\n" + " [-a=] # fix aspect ratio (fx/fy)\n" " [-p] # fix the principal point at the center\n" " [-v] # flip the captured images around the horizontal axis\n" " [-V] # use a video file, and not an image list, uses\n" " # [input_data] string for the video file name\n" " [-su] # show undistorted images after calibration\n" - " [-ws=] # Half of search window for cornerSubPix (11 by default)\n" + " [-ws=] # half of search window for cornerSubPix (11 by default)\n" + " [-fx=] # focal length in X-dir as an initial intrinsic guess (if this flag is used, fx, fy, cx, cy must be set)\n" + " [-fy=] # focal length in Y-dir as an initial intrinsic guess (if this flag is used, fx, fy, cx, cy must be set)\n" + " [-cx=] # camera center point in X-dir as an initial intrinsic guess (if this flag is used, fx, fy, cx, cy must be set)\n" + " [-cy=] # camera center point in Y-dir as an initial intrinsic guess (if this flag is used, fx, fy, cx, cy must be set)\n" + " [-imshow-scale # image resize scaling factor when displaying the results (must be >= 1)\n" + " [-enable-k3=<0/1> # to enable (1) or disable (0) K3 coefficient for the distortion model\n" " [-dt=] # actual distance between top-left and top-right corners of\n" " # the calibration grid. If this parameter is specified, a more\n" " # accurate calibration method will be used which may be better\n" @@ -152,7 +155,6 @@ static bool runCalibration( vector > imagePoints, vector& newObjPoints, double& totalAvgErr) { - cameraMatrix = Mat::eye(3, 3, CV_64F); if( flags & CALIB_FIX_ASPECT_RATIO ) cameraMatrix.at(0,0) = aspectRatio; @@ -171,7 +173,7 @@ static bool runCalibration( vector > imagePoints, iFixedPoint = boardSize.width - 1; rms = calibrateCameraRO(objectPoints, imagePoints, imageSize, iFixedPoint, cameraMatrix, distCoeffs, rvecs, tvecs, newObjPoints, - flags | CALIB_FIX_K3 | CALIB_USE_LU); + flags | CALIB_USE_LU); printf("RMS error reported by calibrateCamera: %g\n", rms); bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs); @@ -192,7 +194,6 @@ static bool runCalibration( vector > imagePoints, return ok; } - static void saveCameraParams( const string& filename, Size imageSize, Size boardSize, float squareSize, float aspectRatio, int flags, @@ -347,7 +348,6 @@ static bool runAndSave(const string& outputFilename, return ok; } - int main( int argc, char** argv ) { Size boardSize, imageSize; @@ -376,6 +376,8 @@ int main( int argc, char** argv ) "{help ||}{w||}{h||}{pt|chessboard|}{n|10|}{d|1000|}{s|1|}{o|out_camera_data.yml|}" "{op||}{oe||}{zt||}{a||}{p||}{v||}{V||}{su||}" "{oo||}{ws|11|}{dt||}" + "{fx||}{fy||}{cx||}{cy||}" + "{imshow-scale|1|}{enable-k3|0|}" "{@input_data|0|}"); if (parser.has("help")) { @@ -420,6 +422,23 @@ int main( int argc, char** argv ) else inputFilename = parser.get("@input_data"); int winSize = parser.get("ws"); + cameraMatrix = Mat::eye(3, 3, CV_64F); + if (parser.has("fx") && parser.has("fy") && parser.has("cx") && parser.has("cy")) + { + cameraMatrix.at(0,0) = parser.get("fx"); + cameraMatrix.at(0,2) = parser.get("cx"); + cameraMatrix.at(1,1) = parser.get("fy"); + cameraMatrix.at(1,2) = parser.get("cy"); + flags |= CALIB_USE_INTRINSIC_GUESS; + std::cout << "Use the following camera matrix as an initial guess:\n" << cameraMatrix << std::endl; + } + int viewScaleFactor = parser.get("imshow-scale"); + bool useK3 = parser.get("enable-k3"); + std::cout << "Use K3 distortion coefficient? " << useK3 << std::endl; + if (!useK3) + { + flags |= CALIB_FIX_K3; + } float grid_width = squareSize * (boardSize.width - 1); bool release_object = false; if (parser.has("dt")) { @@ -555,8 +574,17 @@ int main( int argc, char** argv ) Mat temp = view.clone(); undistort(temp, view, cameraMatrix, distCoeffs); } + if (viewScaleFactor > 1) + { + Mat viewScale; + resize(view, viewScale, Size(), 1.0/viewScaleFactor, 1.0/viewScaleFactor, INTER_AREA); + imshow("Image View", viewScale); + } + else + { + imshow("Image View", view); + } - imshow("Image View", view); char key = (char)waitKey(capture.isOpened() ? 50 : 500); if( key == 27 ) @@ -597,9 +625,17 @@ int main( int argc, char** argv ) view = imread(imageList[i], 1); if(view.empty()) continue; - //undistort( view, rview, cameraMatrix, distCoeffs, cameraMatrix ); remap(view, rview, map1, map2, INTER_LINEAR); - imshow("Image View", rview); + if (viewScaleFactor > 1) + { + Mat rviewScale; + resize(rview, rviewScale, Size(), 1.0/viewScaleFactor, 1.0/viewScaleFactor, INTER_AREA); + imshow("Image View", rviewScale); + } + else + { + imshow("Image View", rview); + } char c = (char)waitKey(); if( c == 27 || c == 'q' || c == 'Q' ) break; diff --git a/samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp b/samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp new file mode 100644 index 0000000000..dca52b1339 --- /dev/null +++ b/samples/cpp/tutorial_code/ImgTrans/generalizedHoughTransform.cpp @@ -0,0 +1,108 @@ +/** + @file generalizedHoughTransform.cpp + @author Markus Heck + @brief Detects an object, given by a template, in an image using GeneralizedHoughBallard and GeneralizedHoughGuil. +*/ + +#include "opencv2/highgui.hpp" +#include "opencv2/imgproc.hpp" + +using namespace cv; +using namespace std; + +int main() { + //! [generalized-hough-transform-load-and-setup] +// load source image and grayscale template + Mat image = imread("images/generalized_hough_mini_image.jpg"); + Mat templ = imread("images/generalized_hough_mini_template.jpg", IMREAD_GRAYSCALE); + +// create grayscale image + Mat grayImage; + cvtColor(image, grayImage, COLOR_RGB2GRAY); + +// create variable for location, scale and rotation of detected templates + vector positionBallard, positionGuil; + +// template width and height + int w = templ.cols; + int h = templ.rows; + //! [generalized-hough-transform-load-and-setup] + + + //! [generalized-hough-transform-setup-parameters] +// create ballard and set options + Ptr ballard = createGeneralizedHoughBallard(); + ballard->setMinDist(10); + ballard->setLevels(360); + ballard->setDp(2); + ballard->setMaxBufferSize(1000); + ballard->setVotesThreshold(40); + + ballard->setCannyLowThresh(30); + ballard->setCannyHighThresh(110); + ballard->setTemplate(templ); + + +// create guil and set options + Ptr guil = createGeneralizedHoughGuil(); + guil->setMinDist(10); + guil->setLevels(360); + guil->setDp(3); + guil->setMaxBufferSize(1000); + + guil->setMinAngle(0); + guil->setMaxAngle(360); + guil->setAngleStep(1); + guil->setAngleThresh(1500); + + guil->setMinScale(0.5); + guil->setMaxScale(2.0); + guil->setScaleStep(0.05); + guil->setScaleThresh(50); + + guil->setPosThresh(10); + + guil->setCannyLowThresh(30); + guil->setCannyHighThresh(110); + + guil->setTemplate(templ); + //! [generalized-hough-transform-setup-parameters] + + + //! [generalized-hough-transform-run] +// execute ballard detection + ballard->detect(grayImage, positionBallard); +// execute guil detection + guil->detect(grayImage, positionGuil); + //! [generalized-hough-transform-run] + + + //! [generalized-hough-transform-draw-results] +// draw ballard + for (vector::iterator iter = positionBallard.begin(); iter != positionBallard.end(); ++iter) { + RotatedRect rRect = RotatedRect(Point2f((*iter)[0], (*iter)[1]), + Size2f(w * (*iter)[2], h * (*iter)[2]), + (*iter)[3]); + Point2f vertices[4]; + rRect.points(vertices); + for (int i = 0; i < 4; i++) + line(image, vertices[i], vertices[(i + 1) % 4], Scalar(255, 0, 0), 6); + } + +// draw guil + for (vector::iterator iter = positionGuil.begin(); iter != positionGuil.end(); ++iter) { + RotatedRect rRect = RotatedRect(Point2f((*iter)[0], (*iter)[1]), + Size2f(w * (*iter)[2], h * (*iter)[2]), + (*iter)[3]); + Point2f vertices[4]; + rRect.points(vertices); + for (int i = 0; i < 4; i++) + line(image, vertices[i], vertices[(i + 1) % 4], Scalar(0, 255, 0), 2); + } + + imshow("result_img", image); + waitKey(); + //! [generalized-hough-transform-draw-results] + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/samples/cpp/tutorial_code/video/meanshift/camshift.cpp b/samples/cpp/tutorial_code/video/meanshift/camshift.cpp index 13965e623f..4f5d796072 100644 --- a/samples/cpp/tutorial_code/video/meanshift/camshift.cpp +++ b/samples/cpp/tutorial_code/video/meanshift/camshift.cpp @@ -58,7 +58,7 @@ int main(int argc, char **argv) calcHist(&hsv_roi, 1, channels, mask, roi_hist, 1, histSize, range); normalize(roi_hist, roi_hist, 0, 255, NORM_MINMAX); - // Setup the termination criteria, either 10 iteration or move by atleast 1 pt + // Setup the termination criteria, either 10 iteration or move by at least 1 pt TermCriteria term_crit(TermCriteria::EPS | TermCriteria::COUNT, 10, 1); while(true){ diff --git a/samples/cpp/tutorial_code/video/meanshift/meanshift.cpp b/samples/cpp/tutorial_code/video/meanshift/meanshift.cpp index 0e16442c6d..e812cb5db4 100644 --- a/samples/cpp/tutorial_code/video/meanshift/meanshift.cpp +++ b/samples/cpp/tutorial_code/video/meanshift/meanshift.cpp @@ -58,7 +58,7 @@ int main(int argc, char **argv) calcHist(&hsv_roi, 1, channels, mask, roi_hist, 1, histSize, range); normalize(roi_hist, roi_hist, 0, 255, NORM_MINMAX); - // Setup the termination criteria, either 10 iteration or move by atleast 1 pt + // Setup the termination criteria, either 10 iteration or move by at least 1 pt TermCriteria term_crit(TermCriteria::EPS | TermCriteria::COUNT, 10, 1); while(true){ diff --git a/samples/data/chessboard.png b/samples/data/chessboard.png index 39bb399e89..1f9112c3a4 100644 Binary files a/samples/data/chessboard.png and b/samples/data/chessboard.png differ diff --git a/samples/data/dnn/object_detection_classes_yolov3.txt b/samples/data/dnn/object_detection_classes_yolov4.txt similarity index 90% rename from samples/data/dnn/object_detection_classes_yolov3.txt rename to samples/data/dnn/object_detection_classes_yolov4.txt index 941cb4e139..ca76c80b5b 100644 --- a/samples/data/dnn/object_detection_classes_yolov3.txt +++ b/samples/data/dnn/object_detection_classes_yolov4.txt @@ -1,8 +1,8 @@ person bicycle car -motorcycle -airplane +motorbike +aeroplane bus train truck @@ -55,12 +55,12 @@ pizza donut cake chair -couch -potted plant +sofa +pottedplant bed -dining table +diningtable toilet -tv +tvmonitor laptop mouse remote diff --git a/samples/directx/CMakeLists.txt b/samples/directx/CMakeLists.txt index 3041e27381..c74c7d4b31 100644 --- a/samples/directx/CMakeLists.txt +++ b/samples/directx/CMakeLists.txt @@ -19,4 +19,16 @@ foreach(sample_filename ${all_samples}) ocv_define_sample(tgt ${sample_filename} directx) ocv_target_link_libraries(${tgt} PRIVATE ${OPENCV_LINKER_LIBS} ${OPENCV_DIRECTX_SAMPLES_REQUIRED_DEPS}) ocv_target_link_libraries(${tgt} PRIVATE "gdi32") + if(sample_filename STREQUAL "d3d9_interop.cpp") + ocv_target_link_libraries(${tgt} PRIVATE d3d9) + endif() + if(sample_filename STREQUAL "d3d9ex_interop.cpp") + ocv_target_link_libraries(${tgt} PRIVATE d3d9) + endif() + if(sample_filename STREQUAL "d3d10_interop.cpp") + ocv_target_link_libraries(${tgt} PRIVATE d3d10) + endif() + if(sample_filename STREQUAL "d3d11_interop.cpp") + ocv_target_link_libraries(${tgt} PRIVATE d3d11) + endif() endforeach() diff --git a/samples/directx/d3d10_interop.cpp b/samples/directx/d3d10_interop.cpp index e8be8fac50..442a3f0c53 100644 --- a/samples/directx/d3d10_interop.cpp +++ b/samples/directx/d3d10_interop.cpp @@ -17,7 +17,6 @@ #include "d3dsample.hpp" -#pragma comment (lib, "d3d10.lib") class D3D10WinApp : public D3DSample { diff --git a/samples/directx/d3d11_interop.cpp b/samples/directx/d3d11_interop.cpp index df40dd3e89..6c55a0c48b 100644 --- a/samples/directx/d3d11_interop.cpp +++ b/samples/directx/d3d11_interop.cpp @@ -17,7 +17,6 @@ #include "d3dsample.hpp" -#pragma comment (lib, "d3d11.lib") class D3D11WinApp : public D3DSample { diff --git a/samples/directx/d3d9_interop.cpp b/samples/directx/d3d9_interop.cpp index c46cf8e9e8..8c308721e0 100644 --- a/samples/directx/d3d9_interop.cpp +++ b/samples/directx/d3d9_interop.cpp @@ -17,7 +17,6 @@ #include "d3dsample.hpp" -#pragma comment (lib, "d3d9.lib") class D3D9WinApp : public D3DSample diff --git a/samples/directx/d3d9ex_interop.cpp b/samples/directx/d3d9ex_interop.cpp index 68b864f0eb..39dda84b66 100644 --- a/samples/directx/d3d9ex_interop.cpp +++ b/samples/directx/d3d9ex_interop.cpp @@ -17,7 +17,6 @@ #include "d3dsample.hpp" -#pragma comment (lib, "d3d9.lib") class D3D9ExWinApp : public D3DSample diff --git a/samples/dnn/models.yml b/samples/dnn/models.yml index 86e86925df..f6e9f0e41f 100644 --- a/samples/dnn/models.yml +++ b/samples/dnn/models.yml @@ -23,16 +23,16 @@ opencv_fd: # Might be used for all YOLOv2, TinyYolov2, YOLOv3, YOLOv4 and TinyYolov4 yolo: load_info: - url: "https://pjreddie.com/media/files/yolov3.weights" - sha1: "520878f12e97cf820529daea502acca380f1cb8e" - model: "yolov3.weights" - config: "yolov3.cfg" + url: "https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights" + sha1: "0143deb6c46fcc7f74dd35bf3c14edc3784e99ee" + model: "yolov4.weights" + config: "yolov4.cfg" mean: [0, 0, 0] scale: 0.00392 width: 416 height: 416 rgb: true - classes: "object_detection_classes_yolov3.txt" + classes: "object_detection_classes_yolov4.txt" sample: "object_detection" tiny-yolo-voc: diff --git a/samples/dnn/nanotrack_tracker.cpp b/samples/dnn/nanotrack_tracker.cpp new file mode 100644 index 0000000000..e98e301f13 --- /dev/null +++ b/samples/dnn/nanotrack_tracker.cpp @@ -0,0 +1,183 @@ +// NanoTrack +// Link to original inference code: https://github.com/HonglinChu/NanoTrack +// Link to original training repo: https://github.com/HonglinChu/SiamTrackers/tree/master/NanoTrack +// backBone model: https://github.com/HonglinChu/SiamTrackers/blob/master/NanoTrack/models/onnx/nanotrack_backbone_sim.onnx +// headNeck model: https://github.com/HonglinChu/SiamTrackers/blob/master/NanoTrack/models/onnx/nanotrack_head_sim.onnx + +#include +#include + +#include +#include +#include +#include + +using namespace cv; +using namespace cv::dnn; + +const char *keys = + "{ help h | | Print help message }" + "{ input i | | Full path to input video folder, the specific camera index. (empty for camera 0) }" + "{ backbone | backbone.onnx | Path to onnx model of backbone.onnx}" + "{ headneck | headneck.onnx | Path to onnx model of headneck.onnx }" + "{ backend | 0 | Choose one of computation backends: " + "0: automatically (by default), " + "1: Halide language (http://halide-lang.org/), " + "2: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), " + "3: OpenCV implementation, " + "4: VKCOM, " + "5: CUDA }," + "{ target | 0 | Choose one of target computation devices: " + "0: CPU target (by default), " + "1: OpenCL, " + "2: OpenCL fp16 (half-float precision), " + "3: VPU, " + "4: Vulkan, " + "6: CUDA, " + "7: CUDA fp16 (half-float preprocess) }" +; + +static +int run(int argc, char** argv) +{ + // Parse command line arguments. + CommandLineParser parser(argc, argv, keys); + + if (parser.has("help")) + { + parser.printMessage(); + return 0; + } + + std::string inputName = parser.get("input"); + std::string backbone = parser.get("backbone"); + std::string headneck = parser.get("headneck"); + int backend = parser.get("backend"); + int target = parser.get("target"); + + Ptr tracker; + try + { + TrackerNano::Params params; + params.backbone = samples::findFile(backbone); + params.neckhead = samples::findFile(headneck); + params.backend = backend; + params.target = target; + tracker = TrackerNano::create(params); + } + catch (const cv::Exception& ee) + { + std::cerr << "Exception: " << ee.what() << std::endl; + std::cout << "Can't load the network by using the following files:" << std::endl; + std::cout << "backbone : " << backbone << std::endl; + std::cout << "headneck : " << headneck << std::endl; + return 2; + } + + const std::string winName = "NanoTrack"; + namedWindow(winName, WINDOW_AUTOSIZE); + + // Open a video file or an image file or a camera stream. + VideoCapture cap; + + if (inputName.empty() || (isdigit(inputName[0]) && inputName.size() == 1)) + { + int c = inputName.empty() ? 0 : inputName[0] - '0'; + std::cout << "Trying to open camera #" << c << " ..." << std::endl; + if (!cap.open(c)) + { + std::cout << "Capture from camera #" << c << " didn't work. Specify -i=