mirror of https://github.com/opencv/opencv.git
commit
cea26341a5
128 changed files with 9934 additions and 930 deletions
@ -0,0 +1,46 @@ |
||||
#============================================================================= |
||||
# Find AVIF library |
||||
#============================================================================= |
||||
# Find the native AVIF headers and libraries. |
||||
# |
||||
# AVIF_INCLUDE_DIRS - where to find avif/avif.h, etc. |
||||
# AVIF_LIBRARIES - List of libraries when using AVIF. |
||||
# AVIF_FOUND - True if AVIF is found. |
||||
#============================================================================= |
||||
|
||||
# Look for the header file. |
||||
|
||||
unset(AVIF_FOUND) |
||||
|
||||
find_package(libavif QUIET) |
||||
|
||||
if(TARGET avif) |
||||
MARK_AS_ADVANCED(AVIF_INCLUDE_DIR) |
||||
MARK_AS_ADVANCED(AVIF_LIBRARY) |
||||
|
||||
SET(AVIF_FOUND TRUE) |
||||
GET_TARGET_PROPERTY(AVIF_LIBRARY avif LOCATION) |
||||
GET_TARGET_PROPERTY(AVIF_INCLUDE_DIR1 avif INCLUDE_DIRECTORIES) |
||||
GET_TARGET_PROPERTY(AVIF_INCLUDE_DIR2 avif INTERFACE_INCLUDE_DIRECTORIES) |
||||
set(AVIF_INCLUDE_DIR) |
||||
if(AVIF_INCLUDE_DIR1) |
||||
LIST(APPEND AVIF_INCLUDE_DIR ${AVIF_INCLUDE_DIR1}) |
||||
endif() |
||||
if(AVIF_INCLUDE_DIR2) |
||||
LIST(APPEND AVIF_INCLUDE_DIR ${AVIF_INCLUDE_DIR2}) |
||||
endif() |
||||
else() |
||||
FIND_PATH(AVIF_INCLUDE_DIR NAMES avif/avif.h) |
||||
|
||||
# Look for the library. |
||||
FIND_LIBRARY(AVIF_LIBRARY NAMES avif) |
||||
MARK_AS_ADVANCED(AVIF_LIBRARY) |
||||
|
||||
# handle the QUIETLY and REQUIRED arguments and set AVIF_FOUND to TRUE if |
||||
# all listed variables are TRUE |
||||
INCLUDE(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) |
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVIF DEFAULT_MSG AVIF_LIBRARY AVIF_INCLUDE_DIR) |
||||
|
||||
SET(AVIF_LIBRARIES ${AVIF_LIBRARY}) |
||||
SET(AVIF_INCLUDE_DIRS ${AVIF_INCLUDE_DIR}) |
||||
endif() |
@ -0,0 +1,76 @@ |
||||
Barcode Recognition {#tutorial_barcode_detect_and_decode} |
||||
=================== |
||||
|
||||
@tableofcontents |
||||
|
||||
@prev_tutorial{tutorial_traincascade} |
||||
@next_tutorial{tutorial_introduction_to_svm} |
||||
|
||||
| | | |
||||
| -: | :- | |
||||
| Compatibility | OpenCV >= 4.8 | |
||||
|
||||
Goal |
||||
---- |
||||
|
||||
In this chapter we will familiarize with the barcode detection and decoding methods available in OpenCV. |
||||
|
||||
Basics |
||||
---- |
||||
|
||||
Barcode is major technique to identify commodity in real life. A common barcode is a pattern of parallel lines arranged by black bars and white bars with vastly different reflectivity. Barcode recognition is to scan the barcode in the horizontal direction to get a string of binary codes composed of bars of different widths and colors, that is, the code information of the barcode. The content of barcode can be decoded by matching with various barcode encoding methods. Currently, we support EAN-8, EAN-13, UPC-A and UPC-E standards. |
||||
|
||||
See https://en.wikipedia.org/wiki/Universal_Product_Code and https://en.wikipedia.org/wiki/International_Article_Number |
||||
|
||||
Related papers: @cite Xiangmin2015research , @cite kass1987analyzing , @cite bazen2002systematic |
||||
|
||||
Code example |
||||
------------ |
||||
|
||||
### Main class |
||||
Several algorithms were introduced for barcode recognition. |
||||
|
||||
While coding, we firstly need to create a cv::barcode::BarcodeDetector object. It has mainly three member functions, which will be introduced in the following. |
||||
|
||||
#### Initialization |
||||
|
||||
Optionally user can construct barcode detector with super resolution model which should be downloaded from https://github.com/WeChatCV/opencv_3rdparty/tree/wechat_qrcode (`sr.caffemodel`, `sr.prototxt`). |
||||
|
||||
@snippet cpp/barcode.cpp initialize |
||||
|
||||
We need to create variables to store the outputs. |
||||
|
||||
@snippet cpp/barcode.cpp output |
||||
|
||||
#### Detecting |
||||
|
||||
cv::barcode::BarcodeDetector::detect method uses an algorithm based on directional coherence. First, we compute the average squared gradients of every pixel, @cite bazen2002systematic . Then we divide an image into square patches and compute the **gradient orientation coherence** and **mean gradient direction** of each patch. Then, we connect all patches that have **high gradient orientation coherence** and **similar gradient direction**. At this stage we use multiscale patches to capture the gradient distribution of multi-size barcodes, and apply non-maximum suppression to filter duplicate proposals. At last, we use cv::minAreaRect to bound the ROI, and output the corners of the rectangles. |
||||
|
||||
Detect codes in the input image, and output the corners of detected rectangles: |
||||
|
||||
@snippet cpp/barcode.cpp detect |
||||
|
||||
#### Decoding |
||||
|
||||
cv::barcode::BarcodeDetector::decode method first super-scales the image (_optionally_) if it is smaller than threshold, sharpens the image and then binaries it by OTSU or local binarization. Then it reads the contents of the barcode by matching the similarity of the specified barcode pattern. |
||||
|
||||
#### Detecting and decoding |
||||
|
||||
cv::barcode::BarcodeDetector::detectAndDecode combines `detect` and `decode` in a single call. A simple example below shows how to use this function: |
||||
|
||||
@snippet cpp/barcode.cpp detectAndDecode |
||||
|
||||
Visualize the results: |
||||
|
||||
@snippet cpp/barcode.cpp visualize |
||||
|
||||
Results |
||||
------- |
||||
|
||||
Original image: |
||||
|
||||
![image](images/barcode_book.jpg) |
||||
|
||||
After detection: |
||||
|
||||
![image](images/barcode_book_res.jpg) |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 31 KiB |
@ -0,0 +1,107 @@ |
||||
// 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 "opencl_kernels_core.hpp" |
||||
#include "stat.hpp" |
||||
|
||||
#include "has_non_zero.simd.hpp" |
||||
#include "has_non_zero.simd_declarations.hpp" // defines CV_CPU_DISPATCH_MODES_ALL=AVX2,...,BASELINE based on CMakeLists.txt content |
||||
|
||||
namespace cv { |
||||
|
||||
static HasNonZeroFunc getHasNonZeroTab(int depth) |
||||
{ |
||||
CV_INSTRUMENT_REGION(); |
||||
CV_CPU_DISPATCH(getHasNonZeroTab, (depth), |
||||
CV_CPU_DISPATCH_MODES_ALL); |
||||
} |
||||
|
||||
#ifdef HAVE_OPENCL |
||||
static bool ocl_hasNonZero( InputArray _src, bool & res ) |
||||
{ |
||||
int type = _src.type(), depth = CV_MAT_DEPTH(type), kercn = ocl::predictOptimalVectorWidth(_src); |
||||
bool doubleSupport = ocl::Device::getDefault().doubleFPConfig() > 0; |
||||
|
||||
if (depth == CV_64F && !doubleSupport) |
||||
return false; |
||||
|
||||
int dbsize = ocl::Device::getDefault().maxComputeUnits(); |
||||
size_t wgs = ocl::Device::getDefault().maxWorkGroupSize(); |
||||
|
||||
int wgs2_aligned = 1; |
||||
while (wgs2_aligned < (int)wgs) |
||||
wgs2_aligned <<= 1; |
||||
wgs2_aligned >>= 1; |
||||
|
||||
ocl::Kernel k("reduce", ocl::core::reduce_oclsrc, |
||||
format("-D srcT=%s -D srcT1=%s -D cn=1 -D OP_COUNT_NON_ZERO" |
||||
" -D WGS=%d -D kercn=%d -D WGS2_ALIGNED=%d%s%s", |
||||
ocl::typeToStr(CV_MAKE_TYPE(depth, kercn)), |
||||
ocl::typeToStr(depth), (int)wgs, kercn, |
||||
wgs2_aligned, doubleSupport ? " -D DOUBLE_SUPPORT" : "", |
||||
_src.isContinuous() ? " -D HAVE_SRC_CONT" : "")); |
||||
if (k.empty()) |
||||
return false; |
||||
|
||||
UMat src = _src.getUMat(), db(1, dbsize, CV_32SC1); |
||||
k.args(ocl::KernelArg::ReadOnlyNoSize(src), src.cols, (int)src.total(), |
||||
dbsize, ocl::KernelArg::PtrWriteOnly(db)); |
||||
|
||||
size_t globalsize = dbsize * wgs; |
||||
if (k.run(1, &globalsize, &wgs, true)) |
||||
return res = (saturate_cast<int>(cv::sum(db.getMat(ACCESS_READ))[0])>0), true; |
||||
return false; |
||||
} |
||||
#endif |
||||
|
||||
bool hasNonZero(InputArray _src) |
||||
{ |
||||
CV_INSTRUMENT_REGION(); |
||||
|
||||
int type = _src.type(), cn = CV_MAT_CN(type); |
||||
CV_Assert( cn == 1 ); |
||||
|
||||
bool res = false; |
||||
|
||||
#ifdef HAVE_OPENCL |
||||
CV_OCL_RUN_(OCL_PERFORMANCE_CHECK(_src.isUMat()) && _src.dims() <= 2, |
||||
ocl_hasNonZero(_src, res), |
||||
res) |
||||
#endif |
||||
|
||||
Mat src = _src.getMat(); |
||||
|
||||
HasNonZeroFunc func = getHasNonZeroTab(src.depth()); |
||||
CV_Assert( func != 0 ); |
||||
|
||||
if (src.dims == 2)//fast path to avoid creating planes of single rows
|
||||
{ |
||||
if (src.isContinuous()) |
||||
res |= func(src.ptr<uchar>(0), src.total()); |
||||
else |
||||
for(int row = 0, rowsCount = src.rows ; !res && (row<rowsCount) ; ++row) |
||||
res |= func(src.ptr<uchar>(row), src.cols); |
||||
} |
||||
else//if (src.dims != 2)
|
||||
{ |
||||
const Mat* arrays[] = {&src, nullptr}; |
||||
Mat planes[1]; |
||||
NAryMatIterator itNAry(arrays, planes, 1); |
||||
for(size_t p = 0 ; !res && (p<itNAry.nplanes) ; ++p, ++itNAry) |
||||
{ |
||||
const Mat& plane = itNAry.planes[0]; |
||||
if (plane.isContinuous()) |
||||
res |= func(plane.ptr<uchar>(0), plane.total()); |
||||
else |
||||
for(int row = 0, rowsCount = plane.rows ; !res && (row<rowsCount) ; ++row) |
||||
res |= func(plane.ptr<uchar>(row), plane.cols); |
||||
} |
||||
} |
||||
|
||||
return res; |
||||
} |
||||
|
||||
} // namespace
|
@ -0,0 +1,327 @@ |
||||
// 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" |
||||
|
||||
namespace cv { |
||||
|
||||
typedef bool (*HasNonZeroFunc)(const uchar*, size_t); |
||||
|
||||
|
||||
CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN |
||||
|
||||
HasNonZeroFunc getHasNonZeroTab(int depth); |
||||
|
||||
|
||||
#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY |
||||
|
||||
template<typename T> |
||||
inline bool hasNonZero_(const T* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
if (len > 0) |
||||
{ |
||||
size_t i=0; |
||||
#if CV_ENABLE_UNROLLED |
||||
for(; !res && (i+4 <= len); i += 4 ) |
||||
res |= ((src[i] | src[i+1] | src[i+2] | src[i+3]) != 0); |
||||
#endif |
||||
for( ; !res && (i < len); i++ ) |
||||
res |= (src[i] != 0); |
||||
} |
||||
return res; |
||||
} |
||||
|
||||
template<> |
||||
inline bool hasNonZero_(const float* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
if (len > 0) |
||||
{ |
||||
size_t i=0; |
||||
if (sizeof(float) == sizeof(unsigned int)) |
||||
{ |
||||
#if CV_ENABLE_UNROLLED |
||||
typedef unsigned int float_as_uint_t; |
||||
const float_as_uint_t* src_as_ui = reinterpret_cast<const float_as_uint_t*>(src); |
||||
for(; !res && (i+4 <= len); i += 4 ) |
||||
{ |
||||
const float_as_uint_t gathered = (src_as_ui[i] | src_as_ui[i+1] | src_as_ui[i+2] | src_as_ui[i+3]); |
||||
res |= ((gathered<<1) != 0);//remove what would be the sign bit
|
||||
} |
||||
#endif |
||||
} |
||||
for( ; !res && (i < len); i++ ) |
||||
res |= (src[i] != 0); |
||||
} |
||||
return res; |
||||
} |
||||
|
||||
template<> |
||||
inline bool hasNonZero_(const double* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
if (len > 0) |
||||
{ |
||||
size_t i=0; |
||||
if (sizeof(double) == sizeof(uint64_t)) |
||||
{ |
||||
#if CV_ENABLE_UNROLLED |
||||
typedef uint64_t double_as_uint_t; |
||||
const double_as_uint_t* src_as_ui = reinterpret_cast<const double_as_uint_t*>(src); |
||||
for(; !res && (i+4 <= len); i += 4 ) |
||||
{ |
||||
const double_as_uint_t gathered = (src_as_ui[i] | src_as_ui[i+1] | src_as_ui[i+2] | src_as_ui[i+3]); |
||||
res |= ((gathered<<1) != 0);//remove what would be the sign bit
|
||||
} |
||||
#endif |
||||
} |
||||
for( ; !res && (i < len); i++ ) |
||||
res |= (src[i] != 0); |
||||
} |
||||
return res; |
||||
} |
||||
|
||||
static bool hasNonZero8u( const uchar* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
const uchar* srcEnd = src+len; |
||||
#if CV_SIMD |
||||
typedef v_uint8 v_type; |
||||
const v_type v_zero = vx_setzero_u8(); |
||||
constexpr const int unrollCount = 2; |
||||
int step = v_type::nlanes * unrollCount; |
||||
int len0 = len & -step; |
||||
const uchar* srcSimdEnd = src+len0; |
||||
|
||||
int countSIMD = static_cast<int>((srcSimdEnd-src)/step); |
||||
while(!res && countSIMD--) |
||||
{ |
||||
v_type v0 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v1 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
res = v_check_any(((v0 | v1) != v_zero)); |
||||
} |
||||
|
||||
v_cleanup(); |
||||
#endif |
||||
return res || hasNonZero_(src, srcEnd-src); |
||||
} |
||||
|
||||
static bool hasNonZero16u( const ushort* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
const ushort* srcEnd = src+len; |
||||
#if CV_SIMD |
||||
typedef v_uint16 v_type; |
||||
const v_type v_zero = vx_setzero_u16(); |
||||
constexpr const int unrollCount = 4; |
||||
int step = v_type::nlanes * unrollCount; |
||||
int len0 = len & -step; |
||||
const ushort* srcSimdEnd = src+len0; |
||||
|
||||
int countSIMD = static_cast<int>((srcSimdEnd-src)/step); |
||||
while(!res && countSIMD--) |
||||
{ |
||||
v_type v0 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v1 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v2 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v3 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v0 |= v1; |
||||
v2 |= v3; |
||||
res = v_check_any(((v0 | v2) != v_zero)); |
||||
} |
||||
|
||||
v_cleanup(); |
||||
#endif |
||||
return res || hasNonZero_(src, srcEnd-src); |
||||
} |
||||
|
||||
static bool hasNonZero32s( const int* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
const int* srcEnd = src+len; |
||||
#if CV_SIMD |
||||
typedef v_int32 v_type; |
||||
const v_type v_zero = vx_setzero_s32(); |
||||
constexpr const int unrollCount = 8; |
||||
int step = v_type::nlanes * unrollCount; |
||||
int len0 = len & -step; |
||||
const int* srcSimdEnd = src+len0; |
||||
|
||||
int countSIMD = static_cast<int>((srcSimdEnd-src)/step); |
||||
while(!res && countSIMD--) |
||||
{ |
||||
v_type v0 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v1 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v2 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v3 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v4 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v5 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v6 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v7 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v0 |= v1; |
||||
v2 |= v3; |
||||
v4 |= v5; |
||||
v6 |= v7; |
||||
|
||||
v0 |= v2; |
||||
v4 |= v6; |
||||
res = v_check_any(((v0 | v4) != v_zero)); |
||||
} |
||||
|
||||
v_cleanup(); |
||||
#endif |
||||
return res || hasNonZero_(src, srcEnd-src); |
||||
} |
||||
|
||||
static bool hasNonZero32f( const float* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
const float* srcEnd = src+len; |
||||
#if CV_SIMD |
||||
typedef v_float32 v_type; |
||||
const v_type v_zero = vx_setzero_f32(); |
||||
constexpr const int unrollCount = 8; |
||||
int step = v_type::nlanes * unrollCount; |
||||
int len0 = len & -step; |
||||
const float* srcSimdEnd = src+len0; |
||||
|
||||
int countSIMD = static_cast<int>((srcSimdEnd-src)/step); |
||||
while(!res && countSIMD--) |
||||
{ |
||||
v_type v0 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v1 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v2 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v3 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v4 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v5 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v6 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v7 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v0 |= v1; |
||||
v2 |= v3; |
||||
v4 |= v5; |
||||
v6 |= v7; |
||||
|
||||
v0 |= v2; |
||||
v4 |= v6; |
||||
//res = v_check_any(((v0 | v4) != v_zero));//beware : (NaN != 0) returns "false" since != is mapped to _CMP_NEQ_OQ and not _CMP_NEQ_UQ
|
||||
res = !v_check_all(((v0 | v4) == v_zero)); |
||||
} |
||||
|
||||
v_cleanup(); |
||||
#endif |
||||
return res || hasNonZero_(src, srcEnd-src); |
||||
} |
||||
|
||||
static bool hasNonZero64f( const double* src, size_t len ) |
||||
{ |
||||
bool res = false; |
||||
const double* srcEnd = src+len; |
||||
#if CV_SIMD_64F |
||||
typedef v_float64 v_type; |
||||
const v_type v_zero = vx_setzero_f64(); |
||||
constexpr const int unrollCount = 16; |
||||
int step = v_type::nlanes * unrollCount; |
||||
int len0 = len & -step; |
||||
const double* srcSimdEnd = src+len0; |
||||
|
||||
int countSIMD = static_cast<int>((srcSimdEnd-src)/step); |
||||
while(!res && countSIMD--) |
||||
{ |
||||
v_type v0 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v1 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v2 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v3 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v4 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v5 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v6 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v7 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v8 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v9 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v10 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v11 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v12 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v13 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v14 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v_type v15 = vx_load(src); |
||||
src += v_type::nlanes; |
||||
v0 |= v1; |
||||
v2 |= v3; |
||||
v4 |= v5; |
||||
v6 |= v7; |
||||
v8 |= v9; |
||||
v10 |= v11; |
||||
v12 |= v13; |
||||
v14 |= v15; |
||||
|
||||
v0 |= v2; |
||||
v4 |= v6; |
||||
v8 |= v10; |
||||
v12 |= v14; |
||||
|
||||
v0 |= v4; |
||||
v8 |= v12; |
||||
//res = v_check_any(((v0 | v8) != v_zero));//beware : (NaN != 0) returns "false" since != is mapped to _CMP_NEQ_OQ and not _CMP_NEQ_UQ
|
||||
res = !v_check_all(((v0 | v8) == v_zero)); |
||||
} |
||||
|
||||
v_cleanup(); |
||||
#endif |
||||
return res || hasNonZero_(src, srcEnd-src); |
||||
} |
||||
|
||||
HasNonZeroFunc getHasNonZeroTab(int depth) |
||||
{ |
||||
static HasNonZeroFunc hasNonZeroTab[] = |
||||
{ |
||||
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero8u), (HasNonZeroFunc)GET_OPTIMIZED(hasNonZero8u), |
||||
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero16u), (HasNonZeroFunc)GET_OPTIMIZED(hasNonZero16u), |
||||
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero32s), (HasNonZeroFunc)GET_OPTIMIZED(hasNonZero32f), |
||||
(HasNonZeroFunc)GET_OPTIMIZED(hasNonZero64f), 0 |
||||
}; |
||||
|
||||
return hasNonZeroTab[depth]; |
||||
} |
||||
|
||||
#endif |
||||
|
||||
CV_CPU_OPTIMIZATION_NAMESPACE_END |
||||
} // namespace
|
@ -0,0 +1,201 @@ |
||||
/*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.
|
||||
// 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*/
|
||||
|
||||
#include "test_precomp.hpp" |
||||
|
||||
namespace opencv_test { namespace { |
||||
|
||||
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroAllZeros; |
||||
|
||||
TEST_P(HasNonZeroAllZeros, hasNonZeroAllZeros) |
||||
{ |
||||
const int type = std::get<0>(GetParam()); |
||||
const Size size = std::get<1>(GetParam()); |
||||
|
||||
Mat m = Mat::zeros(size, type); |
||||
EXPECT_FALSE(hasNonZero(m)); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroAllZeros, |
||||
testing::Combine( |
||||
testing::Values(CV_8UC1, CV_8SC1, CV_16UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_64FC1), |
||||
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113)) |
||||
) |
||||
); |
||||
|
||||
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroNegZeros; |
||||
|
||||
TEST_P(HasNonZeroNegZeros, hasNonZeroNegZeros) |
||||
{ |
||||
const int type = std::get<0>(GetParam()); |
||||
const Size size = std::get<1>(GetParam()); |
||||
|
||||
Mat m = Mat(size, type); |
||||
m.setTo(Scalar::all(-0.)); |
||||
EXPECT_FALSE(hasNonZero(m)); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroNegZeros, |
||||
testing::Combine( |
||||
testing::Values(CV_32FC1, CV_64FC1), |
||||
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113)) |
||||
) |
||||
); |
||||
|
||||
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroLimitValues; |
||||
|
||||
TEST_P(HasNonZeroLimitValues, hasNonZeroLimitValues) |
||||
{ |
||||
const int type = std::get<0>(GetParam()); |
||||
const Size size = std::get<1>(GetParam()); |
||||
|
||||
Mat m = Mat(size, type); |
||||
|
||||
m.setTo(Scalar::all(std::numeric_limits<double>::infinity())); |
||||
EXPECT_TRUE(hasNonZero(m)); |
||||
|
||||
m.setTo(Scalar::all(-std::numeric_limits<double>::infinity())); |
||||
EXPECT_TRUE(hasNonZero(m)); |
||||
|
||||
m.setTo(Scalar::all(std::numeric_limits<double>::quiet_NaN())); |
||||
EXPECT_TRUE(hasNonZero(m)); |
||||
|
||||
m.setTo((CV_MAT_DEPTH(type) == CV_64F) ? Scalar::all(std::numeric_limits<double>::epsilon()) : Scalar::all(std::numeric_limits<float>::epsilon())); |
||||
EXPECT_TRUE(hasNonZero(m)); |
||||
|
||||
m.setTo((CV_MAT_DEPTH(type) == CV_64F) ? Scalar::all(std::numeric_limits<double>::min()) : Scalar::all(std::numeric_limits<float>::min())); |
||||
EXPECT_TRUE(hasNonZero(m)); |
||||
|
||||
m.setTo((CV_MAT_DEPTH(type) == CV_64F) ? Scalar::all(std::numeric_limits<double>::denorm_min()) : Scalar::all(std::numeric_limits<float>::denorm_min())); |
||||
EXPECT_TRUE(hasNonZero(m)); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroLimitValues, |
||||
testing::Combine( |
||||
testing::Values(CV_32FC1, CV_64FC1), |
||||
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113)) |
||||
) |
||||
); |
||||
|
||||
typedef testing::TestWithParam<std::tuple<int, Size> > HasNonZeroRandom; |
||||
|
||||
TEST_P(HasNonZeroRandom, hasNonZeroRandom) |
||||
{ |
||||
const int type = std::get<0>(GetParam()); |
||||
const Size size = std::get<1>(GetParam()); |
||||
|
||||
RNG& rng = theRNG(); |
||||
|
||||
const size_t N = std::min(100, size.area()); |
||||
for(size_t i = 0 ; i<N ; ++i) |
||||
{ |
||||
const int nz_pos_x = rng.uniform(0, size.width); |
||||
const int nz_pos_y = rng.uniform(0, size.height); |
||||
Mat m = Mat::zeros(size, type); |
||||
Mat nzROI = Mat(m, Rect(nz_pos_x, nz_pos_y, 1, 1)); |
||||
nzROI.setTo(Scalar::all(1)); |
||||
EXPECT_TRUE(hasNonZero(m)); |
||||
} |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroRandom, |
||||
testing::Combine( |
||||
testing::Values(CV_8UC1, CV_8SC1, CV_16UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_64FC1), |
||||
testing::Values(Size(1, 1), Size(320, 240), Size(127, 113), Size(1, 113)) |
||||
) |
||||
); |
||||
|
||||
typedef testing::TestWithParam<tuple<int, int, bool> > HasNonZeroNd; |
||||
|
||||
TEST_P(HasNonZeroNd, hasNonZeroNd) |
||||
{ |
||||
const int type = get<0>(GetParam()); |
||||
const int ndims = get<1>(GetParam()); |
||||
const bool continuous = get<2>(GetParam()); |
||||
|
||||
RNG& rng = theRNG(); |
||||
|
||||
const size_t N = 10; |
||||
for(size_t i = 0 ; i<N ; ++i) |
||||
{ |
||||
std::vector<size_t> steps(ndims); |
||||
std::vector<int> sizes(ndims); |
||||
size_t totalBytes = 1; |
||||
for(int dim = 0 ; dim<ndims ; ++dim) |
||||
{ |
||||
const bool isFirstDim = (dim == 0); |
||||
const bool isLastDim = (dim+1 == ndims); |
||||
const int length = rng.uniform(1, 64); |
||||
steps[dim] = (isLastDim ? 1 : static_cast<size_t>(length))*CV_ELEM_SIZE(type); |
||||
sizes[dim] = (isFirstDim || continuous) ? length : rng.uniform(1, length); |
||||
totalBytes *= steps[dim]*static_cast<size_t>(sizes[dim]); |
||||
} |
||||
|
||||
std::vector<unsigned char> buffer(totalBytes); |
||||
void* data = buffer.data(); |
||||
|
||||
Mat m = Mat(ndims, sizes.data(), type, data, steps.data()); |
||||
|
||||
std::vector<Range> nzRange(ndims); |
||||
for(int dim = 0 ; dim<ndims ; ++dim) |
||||
{ |
||||
const int pos = rng.uniform(0, sizes[dim]); |
||||
nzRange[dim] = Range(pos, pos+1); |
||||
} |
||||
|
||||
Mat nzROI = Mat(m, nzRange.data()); |
||||
nzROI.setTo(Scalar::all(1)); |
||||
|
||||
const int nzCount = countNonZero(m); |
||||
EXPECT_EQ((nzCount>0), hasNonZero(m)); |
||||
} |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(Core, HasNonZeroNd, |
||||
testing::Combine( |
||||
testing::Values(CV_8UC1), |
||||
testing::Values(2, 3), |
||||
testing::Values(true, false) |
||||
) |
||||
); |
||||
|
||||
}} // namespace
|
@ -0,0 +1,128 @@ |
||||
// 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) 2023 Intel Corporation
|
||||
|
||||
#ifndef OPENCV_GAPI_INFER_BINDINGS_OV_HPP |
||||
#define OPENCV_GAPI_INFER_BINDINGS_OV_HPP |
||||
|
||||
#include <opencv2/gapi/util/any.hpp> |
||||
#include "opencv2/gapi/own/exports.hpp" // GAPI_EXPORTS |
||||
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage |
||||
#include <opencv2/gapi/infer/ov.hpp> // Params |
||||
|
||||
#include <string> |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace ov { |
||||
|
||||
// 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, |
||||
const std::string &bin_path, |
||||
const std::string &device); |
||||
|
||||
GAPI_WRAP |
||||
PyParams(const std::string &tag, |
||||
const std::string &blob_path, |
||||
const std::string &device); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgPluginConfig( |
||||
const std::map<std::string, std::string> &config); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgInputTensorLayout(std::string tensor_layout); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgInputTensorLayout( |
||||
std::map<std::string, std::string> layout_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgInputModelLayout(std::string tensor_layout); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgInputModelLayout( |
||||
std::map<std::string, std::string> layout_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgOutputTensorLayout(std::string tensor_layout); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgOutputTensorLayout( |
||||
std::map<std::string, std::string> layout_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgOutputModelLayout(std::string tensor_layout); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgOutputModelLayout( |
||||
std::map<std::string, std::string> layout_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgOutputTensorPrecision(int precision); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgOutputTensorPrecision( |
||||
std::map<std::string, int> precision_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgReshape(std::vector<size_t> new_shape); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgReshape( |
||||
std::map<std::string, std::vector<size_t>> new_shape_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgNumRequests(const size_t nireq); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgMean(std::vector<float> mean_values); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgMean( |
||||
std::map<std::string, std::vector<float>> mean_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgScale(std::vector<float> scale_values); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgScale( |
||||
std::map<std::string, std::vector<float>> scale_map); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgResize(int interpolation); |
||||
|
||||
GAPI_WRAP |
||||
PyParams& cfgResize(std::map<std::string, int> interpolation); |
||||
|
||||
GBackend backend() const; |
||||
std::string tag() const; |
||||
cv::util::any params() const; |
||||
|
||||
private: |
||||
std::shared_ptr<Params<cv::gapi::Generic>> m_priv; |
||||
}; |
||||
|
||||
GAPI_EXPORTS_W PyParams params(const std::string &tag, |
||||
const std::string &model_path, |
||||
const std::string &weights, |
||||
const std::string &device); |
||||
|
||||
GAPI_EXPORTS_W PyParams params(const std::string &tag, |
||||
const std::string &bin_path, |
||||
const std::string &device); |
||||
} // namespace ov
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
||||
|
||||
#endif // OPENCV_GAPI_INFER_BINDINGS_OV_HPP
|
@ -0,0 +1,685 @@ |
||||
// 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) 2023 Intel Corporation
|
||||
|
||||
#ifndef OPENCV_GAPI_INFER_OV_HPP |
||||
#define OPENCV_GAPI_INFER_OV_HPP |
||||
|
||||
#include <string> |
||||
|
||||
#include <opencv2/gapi/util/any.hpp> |
||||
#include <opencv2/gapi/own/exports.hpp> // GAPI_EXPORTS |
||||
#include <opencv2/gapi/gkernel.hpp> // GKernelType[M], GBackend |
||||
#include <opencv2/gapi/infer.hpp> // Generic |
||||
|
||||
#include <map> |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
|
||||
/**
|
||||
* @brief This namespace contains G-API OpenVINO 2.0 backend functions, |
||||
* structures, and symbols. |
||||
*/ |
||||
namespace ov { |
||||
|
||||
GAPI_EXPORTS cv::gapi::GBackend backend(); |
||||
|
||||
namespace detail { |
||||
|
||||
template <typename T> |
||||
using AttrMap = std::map<std::string, T>; |
||||
// NB: This type is supposed to be used to hold in/out layers
|
||||
// attributes such as precision, layout, shape etc.
|
||||
//
|
||||
// User can provide attributes either:
|
||||
// 1. cv::util::monostate - No value specified explicitly.
|
||||
// 2. Attr - value specified explicitly that should be broadcasted to all layers.
|
||||
// 3. AttrMap[str->T] - map specifies value for particular layer.
|
||||
template <typename Attr> |
||||
using LayerVariantAttr = cv::util::variant< cv::util::monostate |
||||
, AttrMap<Attr> |
||||
, Attr>; |
||||
|
||||
struct ParamDesc { |
||||
struct Model { |
||||
|
||||
Model(const std::string &model_path_, |
||||
const std::string &bin_path_) |
||||
: model_path(model_path_), bin_path(bin_path_) { |
||||
} |
||||
|
||||
std::string model_path; |
||||
std::string bin_path; |
||||
|
||||
LayerVariantAttr<std::string> input_tensor_layout; |
||||
LayerVariantAttr<std::string> input_model_layout; |
||||
LayerVariantAttr<std::string> output_tensor_layout; |
||||
LayerVariantAttr<std::string> output_model_layout; |
||||
LayerVariantAttr<int> output_tensor_precision; |
||||
|
||||
LayerVariantAttr<std::vector<size_t>> new_shapes; |
||||
|
||||
LayerVariantAttr<std::vector<float>> mean_values; |
||||
LayerVariantAttr<std::vector<float>> scale_values; |
||||
|
||||
LayerVariantAttr<int> interpolation; |
||||
}; |
||||
|
||||
struct CompiledModel { |
||||
std::string blob_path; |
||||
}; |
||||
|
||||
using Kind = cv::util::variant<Model, CompiledModel>; |
||||
|
||||
ParamDesc(Kind &&kind_, |
||||
const std::string &device_, |
||||
const bool is_generic_, |
||||
const size_t num_in_, |
||||
const size_t num_out_) |
||||
: kind(std::move(kind_)), device(device_), |
||||
is_generic(is_generic_), |
||||
num_in(num_in_), num_out(num_out_) { |
||||
} |
||||
|
||||
Kind kind; |
||||
|
||||
std::string device; |
||||
bool is_generic; |
||||
|
||||
std::size_t num_in; |
||||
std::size_t num_out; |
||||
|
||||
std::vector<std::string> input_names; |
||||
std::vector<std::string> output_names; |
||||
|
||||
using PluginConfigT = std::map<std::string, std::string>; |
||||
PluginConfigT config; |
||||
|
||||
size_t nireq = 1; |
||||
}; |
||||
|
||||
// NB: Just helper to avoid code duplication.
|
||||
static detail::ParamDesc::Model& |
||||
getModelToSetAttrOrThrow(detail::ParamDesc::Kind &kind, |
||||
const std::string &attr_name) { |
||||
if (cv::util::holds_alternative<detail::ParamDesc::CompiledModel>(kind)) { |
||||
cv::util::throw_error( |
||||
std::logic_error("Specifying " + attr_name + " isn't" |
||||
" possible for compiled model.")); |
||||
} |
||||
GAPI_Assert(cv::util::holds_alternative<detail::ParamDesc::Model>(kind)); |
||||
return cv::util::get<detail::ParamDesc::Model>(kind); |
||||
} |
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* @brief This structure provides functions |
||||
* that fill inference parameters for "OpenVINO Toolkit" model. |
||||
*/ |
||||
template<typename Net> struct Params { |
||||
public: |
||||
/** @brief Class constructor.
|
||||
|
||||
Constructs Params based on model information and specifies default values for other |
||||
inference description parameters. Model is loaded and compiled using "OpenVINO Toolkit". |
||||
|
||||
@param model_path Path to a model. |
||||
@param bin_path Path to a data file. |
||||
For IR format (*.bin): |
||||
If path is empty, will try to read a bin file with the same name as xml. |
||||
If the bin file with the same name is not found, will load IR without weights. |
||||
For PDPD (*.pdmodel) and ONNX (*.onnx) formats bin_path isn't used. |
||||
@param device target device to use. |
||||
*/ |
||||
Params(const std::string &model_path, |
||||
const std::string &bin_path, |
||||
const std::string &device) |
||||
: m_desc( detail::ParamDesc::Kind{detail::ParamDesc::Model{model_path, bin_path}} |
||||
, device |
||||
, false /* is generic */ |
||||
, std::tuple_size<typename Net::InArgs>::value |
||||
, std::tuple_size<typename Net::OutArgs>::value) { |
||||
} |
||||
|
||||
/** @overload
|
||||
Use this constructor to work with pre-compiled network. |
||||
Model is imported from a pre-compiled blob. |
||||
|
||||
@param blob_path path to the compiled model (*.blob). |
||||
@param device target device to use. |
||||
*/ |
||||
Params(const std::string &blob_path, |
||||
const std::string &device) |
||||
: m_desc( detail::ParamDesc::Kind{detail::ParamDesc::CompiledModel{blob_path}} |
||||
, device |
||||
, false /* is generic */ |
||||
, std::tuple_size<typename Net::InArgs>::value |
||||
, std::tuple_size<typename Net::OutArgs>::value) { |
||||
} |
||||
|
||||
/** @brief Specifies sequence of network input layers names for inference.
|
||||
|
||||
The function is used to associate cv::gapi::infer<> inputs with the model inputs. |
||||
Number of names has to match the number of network inputs as defined in G_API_NET(). |
||||
In case a network has only single input layer, there is no need to specify name manually. |
||||
|
||||
@param layer_names std::array<std::string, N> where N is the number of inputs |
||||
as defined in the @ref G_API_NET. Contains names of input layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgInputLayers(const std::vector<std::string> &layer_names) { |
||||
m_desc.input_names = layer_names; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies sequence of network output layers names for inference.
|
||||
|
||||
The function is used to associate cv::gapi::infer<> outputs with the model outputs. |
||||
Number of names has to match the number of network outputs as defined in G_API_NET(). |
||||
In case a network has only single output layer, there is no need to specify name manually. |
||||
|
||||
@param layer_names std::array<std::string, N> where N is the number of outputs |
||||
as defined in the @ref G_API_NET. Contains names of output layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgOutputLayers(const std::vector<std::string> &layer_names) { |
||||
m_desc.output_names = layer_names; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies OpenVINO plugin configuration.
|
||||
|
||||
The function is used to set configuration for OpenVINO plugin. Some parameters |
||||
can be different for each plugin. Please follow https://docs.openvinotoolkit.org/latest/index.html
|
||||
to check information about specific plugin. |
||||
|
||||
@param config Map of pairs: (config parameter name, config parameter value). |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgPluginConfig(const detail::ParamDesc::PluginConfigT &config) { |
||||
m_desc.config = config; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies tensor layout for an input layer.
|
||||
|
||||
The function is used to set tensor layout for an input layer. |
||||
|
||||
@param layout Tensor layout ("NCHW", "NWHC", etc) |
||||
will be applied to all input layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgInputTensorLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") |
||||
.input_tensor_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
@param layout_map Map of pairs: name of corresponding input layer |
||||
and its tensor layout represented in std::string ("NCHW", "NHWC", etc) |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& |
||||
cfgInputTensorLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") |
||||
.input_tensor_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies model layout for an input layer.
|
||||
|
||||
The function is used to set model layout for an input layer. |
||||
|
||||
@param layout Model layout ("NCHW", "NHWC", etc) |
||||
will be applied to all input layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgInputModelLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") |
||||
.input_model_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
@param layout_map Map of pairs: name of corresponding input layer |
||||
and its model layout ("NCHW", "NHWC", etc) |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& |
||||
cfgInputModelLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") |
||||
.input_model_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies tensor layout for an output layer.
|
||||
|
||||
The function is used to set tensor layout for an output layer. |
||||
|
||||
@param layout Tensor layout ("NCHW", "NWHC", etc) |
||||
will be applied to all output layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgOutputTensorLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") |
||||
.output_tensor_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
@param layout_map Map of pairs: name of corresponding output layer |
||||
and its tensor layout represented in std::string ("NCHW", "NHWC", etc) |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& |
||||
cfgOutputTensorLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") |
||||
.output_tensor_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies model layout for an output layer.
|
||||
|
||||
The function is used to set model layout for an output layer. |
||||
|
||||
@param layout Model layout ("NCHW", "NHWC", etc) |
||||
will be applied to all output layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgOutputModelLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") |
||||
.output_model_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
@param layout_map Map of pairs: name of corresponding output layer |
||||
and its model layout ("NCHW", "NHWC", etc) |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& |
||||
cfgOutputModelLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") |
||||
.output_model_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies tensor precision for an output layer.
|
||||
|
||||
The function is used to set tensor precision for an output layer.. |
||||
|
||||
@param precision Precision in OpenCV format (CV_8U, CV_32F, ...) |
||||
will be applied to all output layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgOutputTensorPrecision(int precision) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") |
||||
.output_tensor_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<Net>& |
||||
cfgOutputTensorPrecision(detail::AttrMap<int> precision_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") |
||||
.output_tensor_precision = std::move(precision_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies the new shape for input layers.
|
||||
|
||||
The function is used to set new shape for input layers. |
||||
|
||||
@param new_shape New shape will be applied to all input layers. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& |
||||
cfgReshape(std::vector<size_t> new_shape) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") |
||||
.new_shapes = std::move(new_shape); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
|
||||
@param new_shape_map Map of pairs: name of corresponding output layer |
||||
and its new shape. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& |
||||
cfgReshape(detail::AttrMap<std::vector<size_t>> new_shape_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") |
||||
.new_shapes = std::move(new_shape_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies number of asynchronous inference requests.
|
||||
|
||||
@param nireq Number of inference asynchronous requests. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgNumRequests(const size_t nireq) { |
||||
if (nireq == 0) { |
||||
cv::util::throw_error( |
||||
std::logic_error("Number of inference requests" |
||||
" must be greater than zero.")); |
||||
} |
||||
m_desc.nireq = nireq; |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies mean values for preprocessing.
|
||||
* |
||||
The function is used to set mean values for input layer preprocessing. |
||||
|
||||
@param mean_values Float vector contains mean values |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgMean(std::vector<float> mean_values) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") |
||||
.mean_values = std::move(mean_values); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
|
||||
@param mean_map Map of pairs: name of corresponding input layer |
||||
and its mean values. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgMean(detail::AttrMap<std::vector<float>> mean_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") |
||||
.mean_values = std::move(mean_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies scale values for preprocessing.
|
||||
* |
||||
The function is used to set scale values for input layer preprocessing. |
||||
|
||||
@param scale_values Float vector contains scale values |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgScale(std::vector<float> scale_values) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") |
||||
.scale_values = std::move(scale_values); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
|
||||
@param scale_map Map of pairs: name of corresponding input layer |
||||
and its mean values. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgScale(detail::AttrMap<std::vector<float>> scale_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") |
||||
.scale_values = std::move(scale_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @brief Specifies resize interpolation algorithm.
|
||||
* |
||||
The function is used to configure resize preprocessing for input layer. |
||||
|
||||
@param interpolation Resize interpolation algorithm. |
||||
Supported algorithms: #INTER_NEAREST, #INTER_LINEAR, #INTER_CUBIC. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgResize(int interpolation) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") |
||||
.interpolation = std::move(interpolation); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload
|
||||
|
||||
@param interpolation Map of pairs: name of corresponding input layer |
||||
and its resize algorithm. |
||||
@return reference to this parameter structure. |
||||
*/ |
||||
Params<Net>& cfgResize(detail::AttrMap<int> interpolation) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") |
||||
.interpolation = std::move(interpolation); |
||||
return *this; |
||||
} |
||||
|
||||
// BEGIN(G-API's network parametrization API)
|
||||
GBackend backend() const { return cv::gapi::ov::backend(); } |
||||
std::string tag() const { return Net::tag(); } |
||||
cv::util::any params() const { return { m_desc }; } |
||||
// END(G-API's network parametrization API)
|
||||
|
||||
protected: |
||||
detail::ParamDesc m_desc; |
||||
}; |
||||
|
||||
/*
|
||||
* @brief This structure provides functions for generic network type that |
||||
* fill inference parameters. |
||||
* @see struct Generic |
||||
*/ |
||||
template<> |
||||
class Params<cv::gapi::Generic> { |
||||
public: |
||||
/** @brief Class constructor.
|
||||
|
||||
Constructs Params based on model information and specifies default values for other |
||||
inference description parameters. Model is loaded and compiled using "OpenVINO Toolkit". |
||||
|
||||
@param tag string tag of the network for which these parameters are intended. |
||||
@param model_path Path to a model. |
||||
@param bin_path Path to a data file. |
||||
For IR format (*.bin): |
||||
If path is empty, will try to read a bin file with the same name as xml. |
||||
If the bin file with the same name is not found, will load IR without weights. |
||||
For PDPD (*.pdmodel) and ONNX (*.onnx) formats bin_path isn't used. |
||||
@param device target device to use. |
||||
*/ |
||||
Params(const std::string &tag, |
||||
const std::string &model_path, |
||||
const std::string &bin_path, |
||||
const std::string &device) |
||||
: m_tag(tag), |
||||
m_desc( detail::ParamDesc::Kind{detail::ParamDesc::Model{model_path, bin_path}} |
||||
, device |
||||
, true /* is generic */ |
||||
, 0u |
||||
, 0u) { |
||||
} |
||||
|
||||
/** @overload
|
||||
|
||||
This constructor for pre-compiled networks. Model is imported from pre-compiled |
||||
blob. |
||||
|
||||
@param tag string tag of the network for which these parameters are intended. |
||||
@param blob_path path to the compiled model (*.blob). |
||||
@param device target device to use. |
||||
*/ |
||||
Params(const std::string &tag, |
||||
const std::string &blob_path, |
||||
const std::string &device) |
||||
: m_tag(tag), |
||||
m_desc( detail::ParamDesc::Kind{detail::ParamDesc::CompiledModel{blob_path}} |
||||
, device |
||||
, true /* is generic */ |
||||
, 0u |
||||
, 0u) { |
||||
} |
||||
|
||||
/** @see ov::Params::cfgPluginConfig. */ |
||||
Params& cfgPluginConfig(const detail::ParamDesc::PluginConfigT &config) { |
||||
m_desc.config = config; |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgInputTensorLayout. */ |
||||
Params& cfgInputTensorLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") |
||||
.input_tensor_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& |
||||
cfgInputTensorLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input tensor layout") |
||||
.input_tensor_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgInputModelLayout. */ |
||||
Params& cfgInputModelLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") |
||||
.input_model_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& |
||||
cfgInputModelLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "input model layout") |
||||
.input_model_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgOutputTensorLayout. */ |
||||
Params& cfgOutputTensorLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") |
||||
.output_tensor_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& |
||||
cfgOutputTensorLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor layout") |
||||
.output_tensor_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgOutputModelLayout. */ |
||||
Params& cfgOutputModelLayout(std::string layout) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") |
||||
.output_model_layout = std::move(layout); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& |
||||
cfgOutputModelLayout(detail::AttrMap<std::string> layout_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output model layout") |
||||
.output_model_layout = std::move(layout_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgOutputTensorPrecision. */ |
||||
Params& cfgOutputTensorPrecision(int precision) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") |
||||
.output_tensor_precision = precision; |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& |
||||
cfgOutputTensorPrecision(detail::AttrMap<int> precision_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "output tensor precision") |
||||
.output_tensor_precision = std::move(precision_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgReshape. */ |
||||
Params& cfgReshape(std::vector<size_t> new_shape) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") |
||||
.new_shapes = std::move(new_shape); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& |
||||
cfgReshape(detail::AttrMap<std::vector<size_t>> new_shape_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "reshape") |
||||
.new_shapes = std::move(new_shape_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgNumRequests. */ |
||||
Params& cfgNumRequests(const size_t nireq) { |
||||
if (nireq == 0) { |
||||
cv::util::throw_error( |
||||
std::logic_error("Number of inference requests" |
||||
" must be greater than zero.")); |
||||
} |
||||
m_desc.nireq = nireq; |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgMean. */ |
||||
Params& cfgMean(std::vector<float> mean_values) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") |
||||
.mean_values = std::move(mean_values); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& cfgMean(detail::AttrMap<std::vector<float>> mean_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "mean values") |
||||
.mean_values = std::move(mean_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgScale. */ |
||||
Params& cfgScale(std::vector<float> scale_values) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") |
||||
.scale_values = std::move(scale_values); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& cfgScale(detail::AttrMap<std::vector<float>> scale_map) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "scale values") |
||||
.scale_values = std::move(scale_map); |
||||
return *this; |
||||
} |
||||
|
||||
/** @see ov::Params::cfgResize. */ |
||||
Params& cfgResize(int interpolation) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") |
||||
.interpolation = std::move(interpolation); |
||||
return *this; |
||||
} |
||||
|
||||
/** @overload */ |
||||
Params& cfgResize(detail::AttrMap<int> interpolation) { |
||||
detail::getModelToSetAttrOrThrow(m_desc.kind, "resize preprocessing") |
||||
.interpolation = std::move(interpolation); |
||||
return *this; |
||||
} |
||||
|
||||
// BEGIN(G-API's network parametrization API)
|
||||
GBackend backend() const { return cv::gapi::ov::backend(); } |
||||
std::string tag() const { return m_tag; } |
||||
cv::util::any params() const { return { m_desc }; } |
||||
// END(G-API's network parametrization API)
|
||||
|
||||
protected: |
||||
std::string m_tag; |
||||
detail::ParamDesc m_desc; |
||||
}; |
||||
|
||||
} // namespace ov
|
||||
} // namespace gapi
|
||||
} // namespace cv
|
||||
|
||||
#endif // OPENCV_GAPI_INFER_OV_HPP
|
@ -0,0 +1,238 @@ |
||||
#!/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') |
||||
|
||||
|
||||
openvino_is_available = True |
||||
try: |
||||
from openvino.runtime import Core, Type, Layout, PartialShape |
||||
from openvino.preprocess import ResizeAlgorithm, PrePostProcessor |
||||
except ImportError: |
||||
openvino_is_available = False |
||||
|
||||
|
||||
def skip_if_openvino_not_available(): |
||||
if not openvino_is_available: |
||||
raise unittest.SkipTest("OpenVINO isn't available from python.") |
||||
|
||||
|
||||
class AgeGenderOV: |
||||
def __init__(self, model_path, bin_path, device): |
||||
self.device = device |
||||
self.core = Core() |
||||
self.model = self.core.read_model(model_path, bin_path) |
||||
|
||||
|
||||
def reshape(self, new_shape): |
||||
self.model.reshape(new_shape) |
||||
|
||||
|
||||
def cfgPrePostProcessing(self, pp_callback): |
||||
ppp = PrePostProcessor(self.model) |
||||
pp_callback(ppp) |
||||
self.model = ppp.build() |
||||
|
||||
|
||||
def apply(self, in_data): |
||||
compiled_model = self.core.compile_model(self.model, self.device) |
||||
infer_request = compiled_model.create_infer_request() |
||||
results = infer_request.infer(in_data) |
||||
ov_age = results['age_conv3'].squeeze() |
||||
ov_gender = results['prob'].squeeze() |
||||
return ov_age, ov_gender |
||||
|
||||
|
||||
class AgeGenderGAPI: |
||||
tag = 'age-gender-net' |
||||
|
||||
def __init__(self, model_path, bin_path, device): |
||||
g_in = cv.GMat() |
||||
inputs = cv.GInferInputs() |
||||
inputs.setInput('data', g_in) |
||||
# TODO: It'd be nice to pass dict instead. |
||||
# E.g cv.gapi.infer("net", {'data': g_in}) |
||||
outputs = cv.gapi.infer(AgeGenderGAPI.tag, inputs) |
||||
age_g = outputs.at("age_conv3") |
||||
gender_g = outputs.at("prob") |
||||
|
||||
self.comp = cv.GComputation(cv.GIn(g_in), cv.GOut(age_g, gender_g)) |
||||
self.pp = cv.gapi.ov.params(AgeGenderGAPI.tag, \ |
||||
model_path, bin_path, device) |
||||
|
||||
|
||||
def apply(self, in_data): |
||||
compile_args = cv.gapi.compile_args(cv.gapi.networks(self.pp)) |
||||
gapi_age, gapi_gender = self.comp.apply(cv.gin(in_data), compile_args) |
||||
gapi_gender = gapi_gender.squeeze() |
||||
gapi_age = gapi_age.squeeze() |
||||
return gapi_age, gapi_gender |
||||
|
||||
|
||||
class test_gapi_infer_ov(NewOpenCVTests): |
||||
|
||||
def test_age_gender_infer_image(self): |
||||
skip_if_openvino_not_available() |
||||
|
||||
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' |
||||
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
device_id = 'CPU' |
||||
|
||||
img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) |
||||
img = cv.imread(img_path) |
||||
|
||||
# OpenVINO |
||||
def preproc(ppp): |
||||
ppp.input().model().set_layout(Layout("NCHW")) |
||||
ppp.input().tensor().set_element_type(Type.u8) \ |
||||
.set_spatial_static_shape(img.shape[0], img.shape[1]) \ |
||||
.set_layout(Layout("NHWC")) |
||||
ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR) |
||||
|
||||
|
||||
ref = AgeGenderOV(model_path, bin_path, device_id) |
||||
ref.cfgPrePostProcessing(preproc) |
||||
ov_age, ov_gender = ref.apply(np.expand_dims(img, 0)) |
||||
|
||||
# OpenCV G-API (No preproc required) |
||||
comp = AgeGenderGAPI(model_path, bin_path, device_id) |
||||
gapi_age, gapi_gender = comp.apply(img) |
||||
|
||||
# Check |
||||
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) |
||||
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) |
||||
|
||||
|
||||
def test_age_gender_infer_tensor(self): |
||||
skip_if_openvino_not_available() |
||||
|
||||
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' |
||||
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
device_id = 'CPU' |
||||
|
||||
img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) |
||||
img = cv.imread(img_path) |
||||
|
||||
# Prepare data manually |
||||
tensor = cv.resize(img, (62, 62)).astype(np.float32) |
||||
tensor = np.transpose(tensor, (2, 0, 1)) |
||||
tensor = np.expand_dims(tensor, 0) |
||||
|
||||
# OpenVINO (No preproce required) |
||||
ref = AgeGenderOV(model_path, bin_path, device_id) |
||||
ov_age, ov_gender = ref.apply(tensor) |
||||
|
||||
# OpenCV G-API (No preproc required) |
||||
comp = AgeGenderGAPI(model_path, bin_path, device_id) |
||||
gapi_age, gapi_gender = comp.apply(tensor) |
||||
|
||||
# Check |
||||
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) |
||||
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) |
||||
|
||||
|
||||
def test_age_gender_infer_batch(self): |
||||
skip_if_openvino_not_available() |
||||
|
||||
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' |
||||
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
device_id = 'CPU' |
||||
|
||||
img_path1 = self.find_file('cv/face/david1.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) |
||||
img_path2 = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) |
||||
img1 = cv.imread(img_path1) |
||||
img2 = cv.imread(img_path2) |
||||
# img1 and img2 have the same size |
||||
batch_img = np.array([img1, img2]) |
||||
|
||||
# OpenVINO |
||||
def preproc(ppp): |
||||
ppp.input().model().set_layout(Layout("NCHW")) |
||||
ppp.input().tensor().set_element_type(Type.u8) \ |
||||
.set_spatial_static_shape(img1.shape[0], img2.shape[1]) \ |
||||
.set_layout(Layout("NHWC")) |
||||
ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR) |
||||
|
||||
|
||||
ref = AgeGenderOV(model_path, bin_path, device_id) |
||||
ref.reshape(PartialShape([2, 3, 62, 62])) |
||||
ref.cfgPrePostProcessing(preproc) |
||||
ov_age, ov_gender = ref.apply(batch_img) |
||||
|
||||
# OpenCV G-API |
||||
comp = AgeGenderGAPI(model_path, bin_path, device_id) |
||||
comp.pp.cfgReshape([2, 3, 62, 62]) \ |
||||
.cfgInputModelLayout("NCHW") \ |
||||
.cfgInputTensorLayout("NHWC") \ |
||||
.cfgResize(cv.INTER_LINEAR) |
||||
gapi_age, gapi_gender = comp.apply(batch_img) |
||||
|
||||
# Check |
||||
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) |
||||
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) |
||||
|
||||
|
||||
def test_age_gender_infer_planar(self): |
||||
skip_if_openvino_not_available() |
||||
|
||||
root_path = '/omz_intel_models/intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013' |
||||
model_path = self.find_file(root_path + '.xml', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
bin_path = self.find_file(root_path + '.bin', [os.environ.get('OPENCV_DNN_TEST_DATA_PATH')]) |
||||
device_id = 'CPU' |
||||
|
||||
img_path = self.find_file('cv/face/david2.jpg', [os.environ.get('OPENCV_TEST_DATA_PATH')]) |
||||
img = cv.imread(img_path) |
||||
planar_img = np.transpose(img, (2, 0, 1)) |
||||
planar_img = np.expand_dims(planar_img, 0) |
||||
|
||||
# OpenVINO |
||||
def preproc(ppp): |
||||
ppp.input().tensor().set_element_type(Type.u8) \ |
||||
.set_spatial_static_shape(img.shape[0], img.shape[1]) |
||||
ppp.input().preprocess().resize(ResizeAlgorithm.RESIZE_LINEAR) |
||||
|
||||
|
||||
ref = AgeGenderOV(model_path, bin_path, device_id) |
||||
ref.cfgPrePostProcessing(preproc) |
||||
ov_age, ov_gender = ref.apply(planar_img) |
||||
|
||||
# OpenCV G-API |
||||
comp = AgeGenderGAPI(model_path, bin_path, device_id) |
||||
comp.pp.cfgResize(cv.INTER_LINEAR) |
||||
gapi_age, gapi_gender = comp.apply(planar_img) |
||||
|
||||
# Check |
||||
self.assertEqual(0.0, cv.norm(ov_gender, gapi_gender, cv.NORM_INF)) |
||||
self.assertEqual(0.0, cv.norm(ov_age, gapi_age, cv.NORM_INF)) |
||||
|
||||
|
||||
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() |
@ -0,0 +1,168 @@ |
||||
#include <opencv2/gapi/infer/bindings_ov.hpp> |
||||
|
||||
cv::gapi::ov::PyParams::PyParams(const std::string &tag, |
||||
const std::string &model_path, |
||||
const std::string &bin_path, |
||||
const std::string &device) |
||||
: m_priv(std::make_shared<Params<cv::gapi::Generic>>(tag, model_path, bin_path, device)) { |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams::PyParams(const std::string &tag, |
||||
const std::string &blob_path, |
||||
const std::string &device) |
||||
: m_priv(std::make_shared<Params<cv::gapi::Generic>>(tag, blob_path, device)) { |
||||
} |
||||
|
||||
cv::gapi::GBackend cv::gapi::ov::PyParams::backend() const { |
||||
return m_priv->backend(); |
||||
} |
||||
|
||||
std::string cv::gapi::ov::PyParams::tag() const { |
||||
return m_priv->tag(); |
||||
} |
||||
|
||||
cv::util::any cv::gapi::ov::PyParams::params() const { |
||||
return m_priv->params(); |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgPluginConfig( |
||||
const std::map<std::string, std::string> &config) { |
||||
m_priv->cfgPluginConfig(config); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgInputTensorLayout(std::string tensor_layout) { |
||||
m_priv->cfgInputTensorLayout(std::move(tensor_layout)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgInputTensorLayout( |
||||
std::map<std::string, std::string> layout_map) { |
||||
m_priv->cfgInputTensorLayout(std::move(layout_map)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgInputModelLayout(std::string tensor_layout) { |
||||
m_priv->cfgInputModelLayout(std::move(tensor_layout)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgInputModelLayout( |
||||
std::map<std::string, std::string> layout_map) { |
||||
m_priv->cfgInputModelLayout(std::move(layout_map)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgOutputTensorLayout(std::string tensor_layout) { |
||||
m_priv->cfgOutputTensorLayout(std::move(tensor_layout)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgOutputTensorLayout( |
||||
std::map<std::string, std::string> layout_map) { |
||||
m_priv->cfgOutputTensorLayout(std::move(layout_map)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgOutputModelLayout(std::string tensor_layout) { |
||||
m_priv->cfgOutputModelLayout(std::move(tensor_layout)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgOutputModelLayout( |
||||
std::map<std::string, std::string> layout_map) { |
||||
m_priv->cfgOutputModelLayout(std::move(layout_map)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgOutputTensorPrecision(int precision) { |
||||
m_priv->cfgOutputTensorPrecision(precision); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgOutputTensorPrecision( |
||||
std::map<std::string, int> precision_map) { |
||||
m_priv->cfgOutputTensorPrecision(precision_map); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgReshape(std::vector<size_t> new_shape) { |
||||
m_priv->cfgReshape(std::move(new_shape)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgReshape( |
||||
std::map<std::string, std::vector<size_t>> new_shape_map) { |
||||
m_priv->cfgReshape(std::move(new_shape_map)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgNumRequests(const size_t nireq) { |
||||
m_priv->cfgNumRequests(nireq); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgMean(std::vector<float> mean_values) { |
||||
m_priv->cfgMean(std::move(mean_values)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgMean( |
||||
std::map<std::string, std::vector<float>> mean_map) { |
||||
m_priv->cfgMean(std::move(mean_map)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgScale(std::vector<float> scale_values) { |
||||
m_priv->cfgScale(std::move(scale_values)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgScale( |
||||
std::map<std::string, std::vector<float>> scale_map) { |
||||
m_priv->cfgScale(std::move(scale_map)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgResize(int interpolation) { |
||||
m_priv->cfgResize(interpolation); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams& |
||||
cv::gapi::ov::PyParams::cfgResize(std::map<std::string, int> interpolation) { |
||||
m_priv->cfgResize(std::move(interpolation)); |
||||
return *this; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams cv::gapi::ov::params(const std::string &tag, |
||||
const std::string &model_path, |
||||
const std::string &weights, |
||||
const std::string &device) { |
||||
return {tag, model_path, weights, device}; |
||||
} |
||||
|
||||
cv::gapi::ov::PyParams cv::gapi::ov::params(const std::string &tag, |
||||
const std::string &blob_path, |
||||
const std::string &device) { |
||||
return {tag, blob_path, device}; |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,66 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
//
|
||||
// Copyright (C) 2023 Intel Corporation
|
||||
|
||||
#ifndef OPENCV_GAPI_GOVBACKEND_HPP |
||||
#define OPENCV_GAPI_GOVBACKEND_HPP |
||||
|
||||
// Include anyway - cv::gapi::ov::backend() still needs to be defined
|
||||
#include "opencv2/gapi/infer/ov.hpp" |
||||
|
||||
#if defined HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000 |
||||
|
||||
#include <openvino/openvino.hpp> |
||||
|
||||
#include "backends/common/gbackend.hpp" |
||||
|
||||
namespace cv { |
||||
namespace gimpl { |
||||
namespace ov { |
||||
|
||||
struct OVCompiled { |
||||
::ov::CompiledModel compiled_model; |
||||
}; |
||||
|
||||
class RequestPool; |
||||
|
||||
class GOVExecutable final: public GIslandExecutable |
||||
{ |
||||
const ade::Graph &m_g; |
||||
GModel::ConstGraph m_gm; |
||||
|
||||
// The only executable stuff in this graph
|
||||
// (assuming it is always single-op)
|
||||
ade::NodeHandle this_nh; |
||||
OVCompiled compiled; |
||||
|
||||
// List of all resources in graph (both internal and external)
|
||||
std::vector<ade::NodeHandle> m_dataNodes; |
||||
|
||||
// To manage multiple async requests
|
||||
std::unique_ptr<RequestPool> m_reqPool; |
||||
|
||||
public: |
||||
GOVExecutable(const ade::Graph &graph, |
||||
const std::vector<ade::NodeHandle> &nodes); |
||||
|
||||
virtual inline bool canReshape() const override { return false; } |
||||
virtual inline void reshape(ade::Graph&, const GCompileArgs&) override { |
||||
GAPI_Error("InternalError"); // Not implemented yet
|
||||
} |
||||
|
||||
virtual void run(std::vector<InObj> &&, |
||||
std::vector<OutObj> &&) override { |
||||
GAPI_Error("Not implemented"); |
||||
} |
||||
|
||||
virtual void run(GIslandExecutable::IInput &in, |
||||
GIslandExecutable::IOutput &out) override; |
||||
}; |
||||
|
||||
}}} |
||||
|
||||
#endif // HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
|
||||
#endif // OPENCV_GAPI_GOVBACKEND_HPP
|
@ -0,0 +1,35 @@ |
||||
// 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) 2023 Intel Corporation
|
||||
|
||||
#ifndef OPENCV_GAPI_INFER_OV_UTIL_HPP |
||||
#define OPENCV_GAPI_INFER_OV_UTIL_HPP |
||||
|
||||
#if defined HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000 |
||||
|
||||
// NOTE: This file is not included by default in infer/ov.hpp
|
||||
// and won't be. infer/ov.hpp doesn't depend on OV headers itself.
|
||||
// This file does -- so needs to be included separately by those who care.
|
||||
|
||||
#include <openvino/openvino.hpp> |
||||
|
||||
#include <opencv2/core/cvdef.h> // GAPI_EXPORTS |
||||
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage |
||||
|
||||
namespace cv { |
||||
namespace gapi { |
||||
namespace ov { |
||||
namespace util { |
||||
|
||||
// NB: These functions are EXPORTed to make them accessible by the
|
||||
// test suite only.
|
||||
GAPI_EXPORTS std::vector<int> to_ocv(const ::ov::Shape &shape); |
||||
GAPI_EXPORTS int to_ocv(const ::ov::element::Type &type); |
||||
|
||||
}}}} |
||||
|
||||
#endif // HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
|
||||
|
||||
#endif // OPENCV_GAPI_INFER_OV_UTIL_HPP
|
@ -0,0 +1,540 @@ |
||||
// 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) 2023 Intel Corporation
|
||||
|
||||
#if defined HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000 |
||||
|
||||
#include "../test_precomp.hpp" |
||||
|
||||
#include "backends/ov/util.hpp" |
||||
|
||||
#include <opencv2/gapi/infer/ov.hpp> |
||||
|
||||
#include <openvino/openvino.hpp> |
||||
|
||||
namespace opencv_test |
||||
{ |
||||
|
||||
namespace { |
||||
// FIXME: taken from DNN module
|
||||
void initDLDTDataPath() |
||||
{ |
||||
#ifndef WINRT |
||||
static bool initialized = false; |
||||
if (!initialized) |
||||
{ |
||||
const char* omzDataPath = getenv("OPENCV_OPEN_MODEL_ZOO_DATA_PATH"); |
||||
if (omzDataPath) |
||||
cvtest::addDataSearchPath(omzDataPath); |
||||
const char* dnnDataPath = getenv("OPENCV_DNN_TEST_DATA_PATH"); |
||||
if (dnnDataPath) { |
||||
// Add the dnnDataPath itself - G-API is using some images there directly
|
||||
cvtest::addDataSearchPath(dnnDataPath); |
||||
cvtest::addDataSearchPath(dnnDataPath + std::string("/omz_intel_models")); |
||||
} |
||||
initialized = true; |
||||
} |
||||
#endif // WINRT
|
||||
} |
||||
|
||||
static const std::string SUBDIR = "intel/age-gender-recognition-retail-0013/FP32/"; |
||||
|
||||
void copyFromOV(ov::Tensor &tensor, cv::Mat &mat) { |
||||
GAPI_Assert(tensor.get_byte_size() == mat.total() * mat.elemSize()); |
||||
std::copy_n(reinterpret_cast<uint8_t*>(tensor.data()), |
||||
tensor.get_byte_size(), |
||||
mat.ptr<uint8_t>()); |
||||
} |
||||
|
||||
void copyToOV(const cv::Mat &mat, ov::Tensor &tensor) { |
||||
GAPI_Assert(tensor.get_byte_size() == mat.total() * mat.elemSize()); |
||||
std::copy_n(mat.ptr<uint8_t>(), |
||||
tensor.get_byte_size(), |
||||
reinterpret_cast<uint8_t*>(tensor.data())); |
||||
} |
||||
|
||||
// FIXME: taken from the DNN module
|
||||
void normAssert(cv::InputArray ref, cv::InputArray test, |
||||
const char *comment /*= ""*/, |
||||
double l1 = 0.00001, double lInf = 0.0001) { |
||||
double normL1 = cvtest::norm(ref, test, cv::NORM_L1) / ref.getMat().total(); |
||||
EXPECT_LE(normL1, l1) << comment; |
||||
|
||||
double normInf = cvtest::norm(ref, test, cv::NORM_INF); |
||||
EXPECT_LE(normInf, lInf) << comment; |
||||
} |
||||
|
||||
ov::Core getCore() { |
||||
static ov::Core core; |
||||
return core; |
||||
} |
||||
|
||||
// TODO: AGNetGenComp, AGNetTypedComp, AGNetOVComp, AGNetOVCompiled
|
||||
// can be generalized to work with any model and used as parameters for tests.
|
||||
|
||||
struct AGNetGenComp { |
||||
static constexpr const char* tag = "age-gender-generic"; |
||||
using Params = cv::gapi::ov::Params<cv::gapi::Generic>; |
||||
|
||||
static Params params(const std::string &xml, |
||||
const std::string &bin, |
||||
const std::string &device) { |
||||
return {tag, xml, bin, device}; |
||||
} |
||||
|
||||
static Params params(const std::string &blob_path, |
||||
const std::string &device) { |
||||
return {tag, blob_path, device}; |
||||
} |
||||
|
||||
static cv::GComputation create() { |
||||
cv::GMat in; |
||||
GInferInputs inputs; |
||||
inputs["data"] = in; |
||||
auto outputs = cv::gapi::infer<cv::gapi::Generic>(tag, inputs); |
||||
auto age = outputs.at("age_conv3"); |
||||
auto gender = outputs.at("prob"); |
||||
return cv::GComputation{cv::GIn(in), cv::GOut(age, gender)}; |
||||
} |
||||
}; |
||||
|
||||
struct AGNetTypedComp { |
||||
using AGInfo = std::tuple<cv::GMat, cv::GMat>; |
||||
G_API_NET(AgeGender, <AGInfo(cv::GMat)>, "typed-age-gender"); |
||||
using Params = cv::gapi::ov::Params<AgeGender>; |
||||
|
||||
static Params params(const std::string &xml_path, |
||||
const std::string &bin_path, |
||||
const std::string &device) { |
||||
return Params { |
||||
xml_path, bin_path, device |
||||
}.cfgOutputLayers({ "age_conv3", "prob" }); |
||||
} |
||||
|
||||
static cv::GComputation create() { |
||||
cv::GMat in; |
||||
cv::GMat age, gender; |
||||
std::tie(age, gender) = cv::gapi::infer<AgeGender>(in); |
||||
return cv::GComputation{cv::GIn(in), cv::GOut(age, gender)}; |
||||
} |
||||
}; |
||||
|
||||
class AGNetOVCompiled { |
||||
public: |
||||
AGNetOVCompiled(ov::CompiledModel &&compiled_model) |
||||
: m_compiled_model(std::move(compiled_model)) { |
||||
} |
||||
|
||||
void operator()(const cv::Mat &in_mat, |
||||
cv::Mat &age_mat, |
||||
cv::Mat &gender_mat) { |
||||
auto infer_request = m_compiled_model.create_infer_request(); |
||||
auto input_tensor = infer_request.get_input_tensor(); |
||||
copyToOV(in_mat, input_tensor); |
||||
|
||||
infer_request.infer(); |
||||
|
||||
auto age_tensor = infer_request.get_tensor("age_conv3"); |
||||
age_mat.create(cv::gapi::ov::util::to_ocv(age_tensor.get_shape()), |
||||
cv::gapi::ov::util::to_ocv(age_tensor.get_element_type())); |
||||
copyFromOV(age_tensor, age_mat); |
||||
|
||||
auto gender_tensor = infer_request.get_tensor("prob"); |
||||
gender_mat.create(cv::gapi::ov::util::to_ocv(gender_tensor.get_shape()), |
||||
cv::gapi::ov::util::to_ocv(gender_tensor.get_element_type())); |
||||
copyFromOV(gender_tensor, gender_mat); |
||||
} |
||||
|
||||
void export_model(const std::string &outpath) { |
||||
std::ofstream file{outpath, std::ios::out | std::ios::binary}; |
||||
GAPI_Assert(file.is_open()); |
||||
m_compiled_model.export_model(file); |
||||
} |
||||
|
||||
private: |
||||
ov::CompiledModel m_compiled_model; |
||||
}; |
||||
|
||||
struct ImageInputPreproc { |
||||
void operator()(ov::preprocess::PrePostProcessor &ppp) { |
||||
ppp.input().tensor().set_layout(ov::Layout("NHWC")) |
||||
.set_element_type(ov::element::u8) |
||||
.set_shape({1, size.height, size.width, 3}); |
||||
ppp.input().model().set_layout(ov::Layout("NCHW")); |
||||
ppp.input().preprocess().resize(::ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR); |
||||
} |
||||
|
||||
cv::Size size; |
||||
}; |
||||
|
||||
class AGNetOVComp { |
||||
public: |
||||
AGNetOVComp(const std::string &xml_path, |
||||
const std::string &bin_path, |
||||
const std::string &device) |
||||
: m_device(device) { |
||||
m_model = getCore().read_model(xml_path, bin_path); |
||||
} |
||||
|
||||
using PrePostProcessF = std::function<void(ov::preprocess::PrePostProcessor&)>; |
||||
|
||||
void cfgPrePostProcessing(PrePostProcessF f) { |
||||
ov::preprocess::PrePostProcessor ppp(m_model); |
||||
f(ppp); |
||||
m_model = ppp.build(); |
||||
} |
||||
|
||||
AGNetOVCompiled compile() { |
||||
auto compiled_model = getCore().compile_model(m_model, m_device); |
||||
return {std::move(compiled_model)}; |
||||
} |
||||
|
||||
void apply(const cv::Mat &in_mat, |
||||
cv::Mat &age_mat, |
||||
cv::Mat &gender_mat) { |
||||
compile()(in_mat, age_mat, gender_mat); |
||||
} |
||||
|
||||
private: |
||||
std::string m_device; |
||||
std::shared_ptr<ov::Model> m_model; |
||||
}; |
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// TODO: Make all of tests below parmetrized to avoid code duplication
|
||||
TEST(TestAgeGenderOV, InferTypedTensor) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat({1, 3, 62, 62}, CV_32F); |
||||
cv::randu(in_mat, -1, 1); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.apply(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetTypedComp::create(); |
||||
auto pp = AGNetTypedComp::params(xml_path, bin_path, device); |
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferTypedImage) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat(300, 300, CV_8UC3); |
||||
cv::randu(in_mat, 0, 255); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()}); |
||||
ref.apply(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetTypedComp::create(); |
||||
auto pp = AGNetTypedComp::params(xml_path, bin_path, device); |
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferGenericTensor) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat({1, 3, 62, 62}, CV_32F); |
||||
cv::randu(in_mat, -1, 1); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.apply(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(xml_path, bin_path, device); |
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferGenericImage) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat(300, 300, CV_8UC3); |
||||
cv::randu(in_mat, 0, 255); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()}); |
||||
ref.apply(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(xml_path, bin_path, device); |
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferGenericImageBlob) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string blob_path = "age-gender-recognition-retail-0013.blob"; |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat(300, 300, CV_8UC3); |
||||
cv::randu(in_mat, 0, 255); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.cfgPrePostProcessing(ImageInputPreproc{in_mat.size()}); |
||||
auto cc_ref = ref.compile(); |
||||
// NB: Output blob will contain preprocessing inside.
|
||||
cc_ref.export_model(blob_path); |
||||
cc_ref(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(blob_path, device); |
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferGenericTensorBlob) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string blob_path = "age-gender-recognition-retail-0013.blob"; |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat({1, 3, 62, 62}, CV_32F); |
||||
cv::randu(in_mat, -1, 1); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
auto cc_ref = ref.compile(); |
||||
cc_ref.export_model(blob_path); |
||||
cc_ref(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(blob_path, device); |
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferBothOutputsFP16) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat({1, 3, 62, 62}, CV_32F); |
||||
cv::randu(in_mat, -1, 1); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp){ |
||||
ppp.output(0).tensor().set_element_type(ov::element::f16); |
||||
ppp.output(1).tensor().set_element_type(ov::element::f16); |
||||
}); |
||||
ref.apply(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(xml_path, bin_path, device); |
||||
pp.cfgOutputTensorPrecision(CV_16F); |
||||
|
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferOneOutputFP16) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat({1, 3, 62, 62}, CV_32F); |
||||
cv::randu(in_mat, -1, 1); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
const std::string fp16_output_name = "prob"; |
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.cfgPrePostProcessing([&](ov::preprocess::PrePostProcessor &ppp){ |
||||
ppp.output(fp16_output_name).tensor().set_element_type(ov::element::f16); |
||||
}); |
||||
ref.apply(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(xml_path, bin_path, device); |
||||
pp.cfgOutputTensorPrecision({{fp16_output_name, CV_16F}}); |
||||
|
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, ThrowCfgOutputPrecForBlob) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string blob_path = "age-gender-recognition-retail-0013.blob"; |
||||
const std::string device = "CPU"; |
||||
|
||||
// OpenVINO (Just for blob compilation)
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
auto cc_ref = ref.compile(); |
||||
cc_ref.export_model(blob_path); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(blob_path, device); |
||||
|
||||
EXPECT_ANY_THROW(pp.cfgOutputTensorPrecision(CV_16F)); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, ThrowInvalidConfigIR) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(xml_path, bin_path, device); |
||||
pp.cfgPluginConfig({{"some_key", "some_value"}}); |
||||
|
||||
EXPECT_ANY_THROW(comp.compile(cv::GMatDesc{CV_8U,3,cv::Size{320, 240}}, |
||||
cv::compile_args(cv::gapi::networks(pp)))); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, ThrowInvalidConfigBlob) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string blob_path = "age-gender-recognition-retail-0013.blob"; |
||||
const std::string device = "CPU"; |
||||
|
||||
// OpenVINO (Just for blob compilation)
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
auto cc_ref = ref.compile(); |
||||
cc_ref.export_model(blob_path); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetGenComp::create(); |
||||
auto pp = AGNetGenComp::params(blob_path, device); |
||||
pp.cfgPluginConfig({{"some_key", "some_value"}}); |
||||
|
||||
EXPECT_ANY_THROW(comp.compile(cv::GMatDesc{CV_8U,3,cv::Size{320, 240}}, |
||||
cv::compile_args(cv::gapi::networks(pp)))); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, ThrowInvalidImageLayout) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
// NB: This mat may only have "NHWC" layout.
|
||||
cv::Mat in_mat(300, 300, CV_8UC3); |
||||
cv::randu(in_mat, 0, 255); |
||||
cv::Mat gender, gapi_age, gapi_gender; |
||||
auto comp = AGNetTypedComp::create(); |
||||
auto pp = AGNetTypedComp::params(xml_path, bin_path, device); |
||||
|
||||
pp.cfgInputTensorLayout("NCHW"); |
||||
|
||||
EXPECT_ANY_THROW(comp.compile(cv::descr_of(in_mat), |
||||
cv::compile_args(cv::gapi::networks(pp)))); |
||||
} |
||||
|
||||
TEST(TestAgeGenderOV, InferTensorWithPreproc) { |
||||
initDLDTDataPath(); |
||||
const std::string xml_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.xml"); |
||||
const std::string bin_path = findDataFile(SUBDIR + "age-gender-recognition-retail-0013.bin"); |
||||
const std::string device = "CPU"; |
||||
|
||||
cv::Mat in_mat({1, 240, 320, 3}, CV_32F); |
||||
cv::randu(in_mat, -1, 1); |
||||
cv::Mat ov_age, ov_gender, gapi_age, gapi_gender; |
||||
|
||||
// OpenVINO
|
||||
AGNetOVComp ref(xml_path, bin_path, device); |
||||
ref.cfgPrePostProcessing([](ov::preprocess::PrePostProcessor &ppp) { |
||||
auto& input = ppp.input(); |
||||
input.tensor().set_spatial_static_shape(240, 320) |
||||
.set_layout("NHWC"); |
||||
input.preprocess().resize(ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR); |
||||
}); |
||||
ref.apply(in_mat, ov_age, ov_gender); |
||||
|
||||
// G-API
|
||||
auto comp = AGNetTypedComp::create(); |
||||
auto pp = AGNetTypedComp::params(xml_path, bin_path, device); |
||||
pp.cfgResize(cv::INTER_LINEAR) |
||||
.cfgInputTensorLayout("NHWC"); |
||||
|
||||
comp.apply(cv::gin(in_mat), cv::gout(gapi_age, gapi_gender), |
||||
cv::compile_args(cv::gapi::networks(pp))); |
||||
|
||||
// Assert
|
||||
normAssert(ov_age, gapi_age, "Test age output" ); |
||||
normAssert(ov_gender, gapi_gender, "Test gender output"); |
||||
} |
||||
|
||||
} // namespace opencv_test
|
||||
|
||||
#endif // HAVE_INF_ENGINE && INF_ENGINE_RELEASE >= 2022010000
|
@ -0,0 +1,369 @@ |
||||
// 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_AVIF |
||||
|
||||
#include <avif/avif.h> |
||||
#include <fstream> |
||||
|
||||
#include <opencv2/core/utils/configuration.private.hpp> |
||||
#include "opencv2/imgproc.hpp" |
||||
#include "grfmt_avif.hpp" |
||||
|
||||
#define CV_AVIF_USE_QUALITY \ |
||||
(AVIF_VERSION > ((0 * 1000000) + (11 * 10000) + (1 * 100))) |
||||
|
||||
#if !CV_AVIF_USE_QUALITY |
||||
#define AVIF_QUALITY_LOSSLESS 100 |
||||
#define AVIF_QUALITY_WORST 0 |
||||
#define AVIF_QUALITY_BEST 100 |
||||
|
||||
#endif |
||||
|
||||
namespace cv { |
||||
namespace { |
||||
|
||||
struct AvifImageDeleter { |
||||
void operator()(avifImage *image) { avifImageDestroy(image); } |
||||
}; |
||||
|
||||
using AvifImageUniquePtr = std::unique_ptr<avifImage, AvifImageDeleter>; |
||||
|
||||
avifResult CopyToMat(const avifImage *image, int channels, Mat *mat) { |
||||
CV_Assert((int)image->height == mat->rows); |
||||
CV_Assert((int)image->width == mat->cols); |
||||
if (channels == 1) { |
||||
const cv::Mat image_wrap = |
||||
cv::Mat(image->height, image->width, |
||||
CV_MAKE_TYPE((image->depth == 8) ? CV_8U : CV_16U, 1), |
||||
image->yuvPlanes[0], image->yuvRowBytes[0]); |
||||
if ((image->depth == 8 && mat->depth() == CV_8U) || |
||||
(image->depth > 8 && mat->depth() == CV_16U)) { |
||||
image_wrap.copyTo(*mat); |
||||
} else { |
||||
CV_Assert(image->depth > 8 && mat->depth() == CV_8U); |
||||
image_wrap.convertTo(*mat, CV_8U, 1. / (1 << (image->depth - 8))); |
||||
} |
||||
return AVIF_RESULT_OK; |
||||
} |
||||
avifRGBImage rgba; |
||||
avifRGBImageSetDefaults(&rgba, image); |
||||
if (channels == 3) { |
||||
rgba.format = AVIF_RGB_FORMAT_BGR; |
||||
} else { |
||||
CV_Assert(channels == 4); |
||||
rgba.format = AVIF_RGB_FORMAT_BGRA; |
||||
} |
||||
rgba.rowBytes = mat->step[0]; |
||||
rgba.depth = (mat->depth() == CV_16U) ? image->depth : 8; |
||||
rgba.pixels = reinterpret_cast<uint8_t *>(mat->data); |
||||
return avifImageYUVToRGB(image, &rgba); |
||||
} |
||||
|
||||
AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, |
||||
int bit_depth) { |
||||
CV_Assert(img.depth() == CV_8U || img.depth() == CV_16U); |
||||
|
||||
const int width = img.cols; |
||||
const int height = img.rows; |
||||
|
||||
avifImage *result; |
||||
|
||||
if (img.channels() == 1) { |
||||
result = avifImageCreateEmpty(); |
||||
if (result == nullptr) return nullptr; |
||||
result->width = width; |
||||
result->height = height; |
||||
result->depth = bit_depth; |
||||
result->yuvFormat = AVIF_PIXEL_FORMAT_YUV400; |
||||
result->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED; |
||||
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; |
||||
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; |
||||
result->yuvRange = AVIF_RANGE_FULL; |
||||
result->yuvPlanes[0] = img.data; |
||||
result->yuvRowBytes[0] = img.step[0]; |
||||
result->imageOwnsYUVPlanes = AVIF_FALSE; |
||||
return AvifImageUniquePtr(result); |
||||
} |
||||
|
||||
if (lossless) { |
||||
result = |
||||
avifImageCreate(width, height, bit_depth, AVIF_PIXEL_FORMAT_YUV444); |
||||
if (result == nullptr) return nullptr; |
||||
result->colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED; |
||||
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED; |
||||
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; |
||||
result->yuvRange = AVIF_RANGE_FULL; |
||||
} else { |
||||
result = |
||||
avifImageCreate(width, height, bit_depth, AVIF_PIXEL_FORMAT_YUV420); |
||||
if (result == nullptr) return nullptr; |
||||
result->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709; |
||||
result->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB; |
||||
result->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601; |
||||
result->yuvRange = AVIF_RANGE_FULL; |
||||
} |
||||
|
||||
avifRGBImage rgba; |
||||
avifRGBImageSetDefaults(&rgba, result); |
||||
if (img.channels() == 3) { |
||||
rgba.format = AVIF_RGB_FORMAT_BGR; |
||||
} else { |
||||
CV_Assert(img.channels() == 4); |
||||
rgba.format = AVIF_RGB_FORMAT_BGRA; |
||||
} |
||||
rgba.rowBytes = img.step[0]; |
||||
rgba.depth = bit_depth; |
||||
rgba.pixels = |
||||
const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(img.data)); |
||||
|
||||
if (avifImageRGBToYUV(result, &rgba) != AVIF_RESULT_OK) { |
||||
avifImageDestroy(result); |
||||
return nullptr; |
||||
} |
||||
return AvifImageUniquePtr(result); |
||||
} |
||||
|
||||
} // namespace
|
||||
|
||||
// 64Mb limit to avoid memory saturation.
|
||||
static const size_t kParamMaxFileSize = utils::getConfigurationParameterSizeT( |
||||
"OPENCV_IMGCODECS_AVIF_MAX_FILE_SIZE", 64 * 1024 * 1024); |
||||
|
||||
static constexpr size_t kAvifSignatureSize = 500; |
||||
|
||||
AvifDecoder::AvifDecoder() { |
||||
m_buf_supported = true; |
||||
channels_ = 0; |
||||
decoder_ = avifDecoderCreate(); |
||||
} |
||||
|
||||
AvifDecoder::~AvifDecoder() { |
||||
if (decoder_ != nullptr) avifDecoderDestroy(decoder_); |
||||
} |
||||
|
||||
size_t AvifDecoder::signatureLength() const { return kAvifSignatureSize; } |
||||
|
||||
bool AvifDecoder::checkSignature(const String &signature) const { |
||||
avifDecoderSetIOMemory(decoder_, |
||||
reinterpret_cast<const uint8_t *>(signature.c_str()), |
||||
signature.size()); |
||||
decoder_->io->sizeHint = 1e9; |
||||
const avifResult status = avifDecoderParse(decoder_); |
||||
return (status == AVIF_RESULT_OK || status == AVIF_RESULT_TRUNCATED_DATA); |
||||
} |
||||
|
||||
#define OPENCV_AVIF_CHECK_STATUS(X, ENCDEC) \ |
||||
{ \
|
||||
const avifResult status = (X); \
|
||||
if (status != AVIF_RESULT_OK) { \
|
||||
const std::string error(ENCDEC->diag.error); \
|
||||
CV_Error(Error::StsParseError, \
|
||||
error + " " + avifResultToString(status)); \
|
||||
return false; \
|
||||
} \
|
||||
} |
||||
|
||||
ImageDecoder AvifDecoder::newDecoder() const { return makePtr<AvifDecoder>(); } |
||||
|
||||
bool AvifDecoder::readHeader() { |
||||
if (!m_buf.empty()) { |
||||
CV_Assert(m_buf.type() == CV_8UC1); |
||||
CV_Assert(m_buf.rows == 1); |
||||
} |
||||
|
||||
OPENCV_AVIF_CHECK_STATUS( |
||||
m_buf.empty() |
||||
? avifDecoderSetIOFile(decoder_, m_filename.c_str()) |
||||
: avifDecoderSetIOMemory( |
||||
decoder_, reinterpret_cast<const uint8_t *>(m_buf.data), |
||||
m_buf.total()), |
||||
decoder_); |
||||
OPENCV_AVIF_CHECK_STATUS(avifDecoderParse(decoder_), decoder_); |
||||
|
||||
m_width = decoder_->image->width; |
||||
m_height = decoder_->image->height; |
||||
channels_ = (decoder_->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3; |
||||
if (decoder_->alphaPresent) ++channels_; |
||||
bit_depth_ = decoder_->image->depth; |
||||
CV_Assert(bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12); |
||||
m_type = CV_MAKETYPE(bit_depth_ == 8 ? CV_8U : CV_16U, channels_); |
||||
is_first_image_ = true; |
||||
return true; |
||||
} |
||||
|
||||
bool AvifDecoder::readData(Mat &img) { |
||||
CV_CheckGE(m_width, 0, ""); |
||||
CV_CheckGE(m_height, 0, ""); |
||||
|
||||
CV_CheckEQ(img.cols, m_width, ""); |
||||
CV_CheckEQ(img.rows, m_height, ""); |
||||
CV_CheckType( |
||||
img.type(), |
||||
(img.channels() == 1 || img.channels() == 3 || img.channels() == 4) && |
||||
(img.depth() == CV_8U || img.depth() == CV_16U), |
||||
"AVIF only supports 1, 3, 4 channels and CV_8U and CV_16U"); |
||||
|
||||
Mat read_img; |
||||
if (img.channels() == channels_) { |
||||
read_img = img; |
||||
} else { |
||||
// Use the asked depth but keep the number of channels. OpenCV and not
|
||||
// libavif will do the color conversion.
|
||||
read_img.create(m_height, m_width, CV_MAKE_TYPE(img.depth(), channels_)); |
||||
} |
||||
|
||||
if (is_first_image_) { |
||||
if (!nextPage()) return false; |
||||
is_first_image_ = false; |
||||
} |
||||
|
||||
if (CopyToMat(decoder_->image, channels_, &read_img) != AVIF_RESULT_OK) { |
||||
CV_Error(Error::StsInternal, "Cannot convert from AVIF to Mat"); |
||||
return false; |
||||
} |
||||
|
||||
if (decoder_->image->exif.size > 0) { |
||||
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size); |
||||
} |
||||
|
||||
if (img.channels() == channels_) { |
||||
// We already wrote to the right buffer.
|
||||
} else { |
||||
if (channels_ == 1 && img.channels() == 3) { |
||||
cvtColor(read_img, img, COLOR_GRAY2BGR); |
||||
} else if (channels_ == 1 && img.channels() == 4) { |
||||
cvtColor(read_img, img, COLOR_GRAY2BGRA); |
||||
} else if (channels_ == 3 && img.channels() == 1) { |
||||
cvtColor(read_img, img, COLOR_BGR2GRAY); |
||||
} else if (channels_ == 3 && img.channels() == 4) { |
||||
cvtColor(read_img, img, COLOR_BGR2BGRA); |
||||
} else if (channels_ == 4 && img.channels() == 1) { |
||||
cvtColor(read_img, img, COLOR_BGRA2GRAY); |
||||
} else if (channels_ == 4 && img.channels() == 3) { |
||||
cvtColor(read_img, img, COLOR_BGRA2BGR); |
||||
} else { |
||||
CV_Error(Error::StsInternal, ""); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
bool AvifDecoder::nextPage() { |
||||
const avifResult status = avifDecoderNextImage(decoder_); |
||||
if (status == AVIF_RESULT_NO_IMAGES_REMAINING) return false; |
||||
if (status != AVIF_RESULT_OK) { |
||||
const std::string error(decoder_->diag.error); |
||||
CV_Error(Error::StsParseError, error + " " + avifResultToString(status)); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
AvifEncoder::AvifEncoder() { |
||||
m_description = "AVIF files (*.avif)"; |
||||
m_buf_supported = true; |
||||
encoder_ = avifEncoderCreate(); |
||||
} |
||||
|
||||
AvifEncoder::~AvifEncoder() { |
||||
if (encoder_) avifEncoderDestroy(encoder_); |
||||
} |
||||
|
||||
bool AvifEncoder::isFormatSupported(int depth) const { |
||||
return (depth == CV_8U || depth == CV_16U); |
||||
} |
||||
|
||||
bool AvifEncoder::write(const Mat &img, const std::vector<int> ¶ms) { |
||||
std::vector<Mat> img_vec(1, img); |
||||
return writeToOutput(img_vec, params); |
||||
} |
||||
|
||||
bool AvifEncoder::writemulti(const std::vector<Mat> &img_vec, |
||||
const std::vector<int> ¶ms) { |
||||
return writeToOutput(img_vec, params); |
||||
} |
||||
|
||||
bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec, |
||||
const std::vector<int> ¶ms) { |
||||
int bit_depth = 8; |
||||
int speed = AVIF_SPEED_FASTEST; |
||||
for (size_t i = 0; i < params.size(); i += 2) { |
||||
if (params[i] == IMWRITE_AVIF_QUALITY) { |
||||
const int quality = std::min(std::max(params[i + 1], AVIF_QUALITY_WORST), |
||||
AVIF_QUALITY_BEST); |
||||
#if CV_AVIF_USE_QUALITY |
||||
encoder_->quality = quality; |
||||
#else |
||||
encoder_->minQuantizer = encoder_->maxQuantizer = |
||||
(AVIF_QUANTIZER_BEST_QUALITY - AVIF_QUANTIZER_WORST_QUALITY) * |
||||
quality / (AVIF_QUALITY_BEST - AVIF_QUALITY_WORST) + |
||||
AVIF_QUANTIZER_WORST_QUALITY; |
||||
#endif |
||||
} else if (params[i] == IMWRITE_AVIF_DEPTH) { |
||||
bit_depth = params[i + 1]; |
||||
} else if (params[i] == IMWRITE_AVIF_SPEED) { |
||||
speed = params[i + 1]; |
||||
} |
||||
} |
||||
|
||||
avifRWData output_ori = AVIF_DATA_EMPTY; |
||||
std::unique_ptr<avifRWData, decltype(&avifRWDataFree)> output(&output_ori, |
||||
avifRWDataFree); |
||||
#if CV_AVIF_USE_QUALITY |
||||
const bool do_lossless = (encoder_->quality == AVIF_QUALITY_LOSSLESS); |
||||
#else |
||||
const bool do_lossless = |
||||
(encoder_->minQuantizer == AVIF_QUANTIZER_BEST_QUALITY && |
||||
encoder_->maxQuantizer == AVIF_QUANTIZER_BEST_QUALITY); |
||||
#endif |
||||
encoder_->speed = speed; |
||||
|
||||
const avifAddImageFlags flag = (img_vec.size() == 1) |
||||
? AVIF_ADD_IMAGE_FLAG_SINGLE |
||||
: AVIF_ADD_IMAGE_FLAG_NONE; |
||||
std::vector<AvifImageUniquePtr> images; |
||||
std::vector<cv::Mat> imgs_scaled; |
||||
for (const cv::Mat &img : img_vec) { |
||||
CV_CheckType( |
||||
img.type(), |
||||
(bit_depth == 8 && img.depth() == CV_8U) || |
||||
((bit_depth == 10 || bit_depth == 12) && img.depth() == CV_16U), |
||||
"AVIF only supports bit depth of 8 with CV_8U input or " |
||||
"bit depth of 10 or 12 with CV_16U input"); |
||||
CV_Check(img.channels(), |
||||
img.channels() == 1 || img.channels() == 3 || img.channels() == 4, |
||||
"AVIF only supports 1, 3, 4 channels"); |
||||
|
||||
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth)); |
||||
} |
||||
for (const AvifImageUniquePtr &image : images) { |
||||
OPENCV_AVIF_CHECK_STATUS( |
||||
avifEncoderAddImage(encoder_, image.get(), /*durationInTimescale=*/1, |
||||
flag), |
||||
encoder_); |
||||
} |
||||
|
||||
OPENCV_AVIF_CHECK_STATUS(avifEncoderFinish(encoder_, output.get()), encoder_); |
||||
|
||||
if (m_buf) { |
||||
m_buf->resize(output->size); |
||||
std::memcpy(m_buf->data(), output->data, output->size); |
||||
} else { |
||||
std::ofstream(m_filename, std::ofstream::binary) |
||||
.write(reinterpret_cast<char *>(output->data), output->size); |
||||
} |
||||
|
||||
return (output->size > 0); |
||||
} |
||||
|
||||
ImageEncoder AvifEncoder::newEncoder() const { return makePtr<AvifEncoder>(); } |
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif |
@ -0,0 +1,62 @@ |
||||
// 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_AVIF_H_ |
||||
#define _GRFMT_AVIF_H_ |
||||
|
||||
#include "grfmt_base.hpp" |
||||
|
||||
#ifdef HAVE_AVIF |
||||
|
||||
struct avifDecoder; |
||||
struct avifEncoder; |
||||
struct avifRWData; |
||||
|
||||
namespace cv { |
||||
|
||||
class AvifDecoder CV_FINAL : public BaseImageDecoder { |
||||
public: |
||||
AvifDecoder(); |
||||
~AvifDecoder(); |
||||
|
||||
bool readHeader() CV_OVERRIDE; |
||||
bool readData(Mat& img) CV_OVERRIDE; |
||||
bool nextPage() CV_OVERRIDE; |
||||
|
||||
size_t signatureLength() const CV_OVERRIDE; |
||||
bool checkSignature(const String& signature) const CV_OVERRIDE; |
||||
ImageDecoder newDecoder() const CV_OVERRIDE; |
||||
|
||||
protected: |
||||
int channels_; |
||||
int bit_depth_; |
||||
avifDecoder* decoder_; |
||||
bool is_first_image_; |
||||
}; |
||||
|
||||
class AvifEncoder CV_FINAL : public BaseImageEncoder { |
||||
public: |
||||
AvifEncoder(); |
||||
~AvifEncoder() CV_OVERRIDE; |
||||
|
||||
bool isFormatSupported(int depth) const CV_OVERRIDE; |
||||
|
||||
bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE; |
||||
|
||||
bool writemulti(const std::vector<Mat>& img_vec, |
||||
const std::vector<int>& params) CV_OVERRIDE; |
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE; |
||||
|
||||
private: |
||||
bool writeToOutput(const std::vector<Mat>& img_vec, |
||||
const std::vector<int>& params); |
||||
avifEncoder* encoder_; |
||||
}; |
||||
|
||||
} // namespace cv
|
||||
|
||||
#endif |
||||
|
||||
#endif /*_GRFMT_AVIF_H_*/ |
@ -0,0 +1,355 @@ |
||||
// 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 <cstdint> |
||||
#include <fstream> |
||||
|
||||
#include "test_precomp.hpp" |
||||
|
||||
#ifdef HAVE_AVIF |
||||
|
||||
namespace opencv_test { |
||||
namespace { |
||||
|
||||
class Imgcodecs_Avif_RoundTripSuite |
||||
: public testing::TestWithParam<std::tuple<int, int, int, ImreadModes>> { |
||||
protected: |
||||
static cv::Mat modifyImage(const cv::Mat& img_original, int channels, |
||||
int bit_depth) { |
||||
cv::Mat img; |
||||
if (channels == 1) { |
||||
cv::cvtColor(img_original, img, cv::COLOR_BGR2GRAY); |
||||
} else if (channels == 4) { |
||||
std::vector<cv::Mat> imgs; |
||||
cv::split(img_original, imgs); |
||||
imgs.push_back(cv::Mat(imgs[0])); |
||||
imgs[imgs.size() - 1] = cv::Scalar::all(128); |
||||
cv::merge(imgs, img); |
||||
} else { |
||||
img = img_original.clone(); |
||||
} |
||||
|
||||
cv::Mat img_final = img; |
||||
// Convert image to CV_16U for some bit depths.
|
||||
if (bit_depth > 8) img.convertTo(img_final, CV_16U, 1 << (bit_depth - 8)); |
||||
|
||||
return img_final; |
||||
} |
||||
|
||||
void SetUp() { |
||||
bit_depth_ = std::get<0>(GetParam()); |
||||
channels_ = std::get<1>(GetParam()); |
||||
quality_ = std::get<2>(GetParam()); |
||||
imread_mode_ = std::get<3>(GetParam()); |
||||
encoding_params_ = {cv::IMWRITE_AVIF_QUALITY, quality_, |
||||
cv::IMWRITE_AVIF_DEPTH, bit_depth_}; |
||||
} |
||||
|
||||
bool IsBitDepthValid() const { |
||||
return (bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12); |
||||
} |
||||
|
||||
// Makes sure images are close enough after encode/decode roundtrip.
|
||||
void ValidateRead(const cv::Mat& img_original, const cv::Mat& img) const { |
||||
EXPECT_EQ(img_original.size(), img.size()); |
||||
if (imread_mode_ == IMREAD_UNCHANGED) { |
||||
ASSERT_EQ(img_original.type(), img.type()); |
||||
// Lossless.
|
||||
if (quality_ == 100) { |
||||
EXPECT_EQ(0, cvtest::norm(img, img_original, NORM_INF)); |
||||
} else { |
||||
const float norm = cvtest::norm(img, img_original, NORM_L2) / |
||||
img.channels() / img.cols / img.rows / |
||||
(1 << (bit_depth_ - 8)); |
||||
if (quality_ == 50) { |
||||
EXPECT_LE(norm, 10); |
||||
} else if (quality_ == 0) { |
||||
EXPECT_LE(norm, 13); |
||||
} else { |
||||
EXPECT_FALSE(true); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public: |
||||
int bit_depth_; |
||||
int channels_; |
||||
int quality_; |
||||
int imread_mode_; |
||||
std::vector<int> encoding_params_; |
||||
}; |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Imgcodecs_Avif_Image_RoundTripSuite |
||||
: public Imgcodecs_Avif_RoundTripSuite { |
||||
public: |
||||
const cv::Mat& get_img_original() { |
||||
const Key key = {channels_, (bit_depth_ < 8) ? 8 : bit_depth_}; |
||||
return imgs_[key]; |
||||
} |
||||
|
||||
// Prepare the original image modified for different number of channels and
|
||||
// bit depth.
|
||||
static void SetUpTestCase() { |
||||
const string root = cvtest::TS::ptr()->get_data_path(); |
||||
const string filename = root + "../cv/shared/lena.png"; |
||||
const cv::Mat img_original = cv::imread(filename); |
||||
cv::Mat img_resized; |
||||
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0); |
||||
for (int channels : {1, 3, 4}) { |
||||
for (int bit_depth : {8, 10, 12}) { |
||||
const Key key{channels, bit_depth}; |
||||
imgs_[key] = modifyImage(img_resized, channels, bit_depth); |
||||
} |
||||
} |
||||
} |
||||
|
||||
static const int kWidth; |
||||
static const int kHeight; |
||||
|
||||
private: |
||||
typedef std::tuple<int, int> Key; |
||||
static std::map<Key, cv::Mat> imgs_; |
||||
}; |
||||
std::map<std::tuple<int, int>, cv::Mat> |
||||
Imgcodecs_Avif_Image_RoundTripSuite::imgs_; |
||||
const int Imgcodecs_Avif_Image_RoundTripSuite::kWidth = 51; |
||||
const int Imgcodecs_Avif_Image_RoundTripSuite::kHeight = 31; |
||||
|
||||
class Imgcodecs_Avif_Image_WriteReadSuite |
||||
: public Imgcodecs_Avif_Image_RoundTripSuite {}; |
||||
|
||||
TEST_P(Imgcodecs_Avif_Image_WriteReadSuite, imwrite_imread) { |
||||
const cv::Mat& img_original = get_img_original(); |
||||
ASSERT_FALSE(img_original.empty()); |
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif"); |
||||
if (!IsBitDepthValid()) { |
||||
EXPECT_NO_FATAL_FAILURE( |
||||
cv::imwrite(output, img_original, encoding_params_)); |
||||
EXPECT_NE(0, remove(output.c_str())); |
||||
return; |
||||
} |
||||
EXPECT_NO_THROW(cv::imwrite(output, img_original, encoding_params_)); |
||||
|
||||
// Read from file.
|
||||
const cv::Mat img = cv::imread(output, imread_mode_); |
||||
|
||||
ValidateRead(img_original, img); |
||||
|
||||
EXPECT_EQ(0, remove(output.c_str())); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P( |
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Image_WriteReadSuite, |
||||
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}), |
||||
::testing::ValuesIn({1, 3, 4}), |
||||
::testing::ValuesIn({0, 50, 100}), |
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, |
||||
IMREAD_COLOR}))); |
||||
|
||||
class Imgcodecs_Avif_Image_EncodeDecodeSuite |
||||
: public Imgcodecs_Avif_Image_RoundTripSuite {}; |
||||
|
||||
TEST_P(Imgcodecs_Avif_Image_EncodeDecodeSuite, imencode_imdecode) { |
||||
const cv::Mat& img_original = get_img_original(); |
||||
ASSERT_FALSE(img_original.empty()); |
||||
|
||||
// Encode.
|
||||
std::vector<unsigned char> buf; |
||||
if (!IsBitDepthValid()) { |
||||
EXPECT_THROW(cv::imencode(".avif", img_original, buf, encoding_params_), |
||||
cv::Exception); |
||||
return; |
||||
} |
||||
bool result; |
||||
EXPECT_NO_THROW( |
||||
result = cv::imencode(".avif", img_original, buf, encoding_params_);); |
||||
EXPECT_TRUE(result); |
||||
|
||||
// Read back.
|
||||
const cv::Mat img = cv::imdecode(buf, imread_mode_); |
||||
|
||||
ValidateRead(img_original, img); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P( |
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Image_EncodeDecodeSuite, |
||||
::testing::Combine(::testing::ValuesIn({6, 8, 10, 12}), |
||||
::testing::ValuesIn({1, 3, 4}), |
||||
::testing::ValuesIn({0, 50, 100}), |
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, |
||||
IMREAD_COLOR}))); |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_AVIF_Exif; |
||||
|
||||
TEST_P(Imgcodecs_AVIF_Exif, exif_orientation) { |
||||
const string root = cvtest::TS::ptr()->get_data_path(); |
||||
const string filename = root + GetParam(); |
||||
const int colorThresholdHigh = 250; |
||||
const int colorThresholdLow = 5; |
||||
|
||||
Mat m_img = imread(filename); |
||||
ASSERT_FALSE(m_img.empty()); |
||||
Vec3b vec; |
||||
|
||||
// Checking the first quadrant (with supposed red)
|
||||
vec = m_img.at<Vec3b>(2, 2); // some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow); |
||||
EXPECT_LE(vec.val[1], colorThresholdLow); |
||||
EXPECT_GE(vec.val[2], colorThresholdHigh); |
||||
|
||||
// Checking the second quadrant (with supposed green)
|
||||
vec = m_img.at<Vec3b>(2, 7); // some point inside the square
|
||||
EXPECT_LE(vec.val[0], colorThresholdLow); |
||||
EXPECT_GE(vec.val[1], colorThresholdHigh); |
||||
EXPECT_LE(vec.val[2], colorThresholdLow); |
||||
|
||||
// Checking the third quadrant (with supposed blue)
|
||||
vec = m_img.at<Vec3b>(7, 2); // some point inside the square
|
||||
EXPECT_GE(vec.val[0], colorThresholdHigh); |
||||
EXPECT_LE(vec.val[1], colorThresholdLow); |
||||
EXPECT_LE(vec.val[2], colorThresholdLow); |
||||
} |
||||
|
||||
const string exif_files[] = {"readwrite/testExifOrientation_1.avif", |
||||
"readwrite/testExifOrientation_2.avif", |
||||
"readwrite/testExifOrientation_3.avif", |
||||
"readwrite/testExifOrientation_4.avif", |
||||
"readwrite/testExifOrientation_5.avif", |
||||
"readwrite/testExifOrientation_6.avif", |
||||
"readwrite/testExifOrientation_7.avif", |
||||
"readwrite/testExifOrientation_8.avif"}; |
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExifFiles, Imgcodecs_AVIF_Exif, |
||||
testing::ValuesIn(exif_files)); |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Imgcodecs_Avif_Animation_RoundTripSuite |
||||
: public Imgcodecs_Avif_RoundTripSuite { |
||||
public: |
||||
const std::vector<cv::Mat>& get_anim_original() { |
||||
const Key key = {channels_, bit_depth_}; |
||||
return anims_[key]; |
||||
} |
||||
|
||||
// Prepare the original image modified for different number of channels and
|
||||
// bit depth.
|
||||
static void SetUpTestCase() { |
||||
const string root = cvtest::TS::ptr()->get_data_path(); |
||||
const string filename = root + "../cv/shared/lena.png"; |
||||
const cv::Mat img_original = cv::imread(filename); |
||||
cv::Mat img_resized; |
||||
cv::resize(img_original, img_resized, cv::Size(kWidth, kHeight), 0, 0); |
||||
for (int channels : {1, 3, 4}) { |
||||
for (int bit_depth : {8, 10, 12}) { |
||||
const Key key{channels, bit_depth}; |
||||
const cv::Mat img = modifyImage(img_resized, channels, bit_depth); |
||||
cv::Mat img2, img3; |
||||
cv::flip(img, img2, 0); |
||||
cv::flip(img, img3, -1); |
||||
anims_[key] = {img, img2, img3}; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ValidateRead(const std::vector<cv::Mat>& anim_original, |
||||
const std::vector<cv::Mat>& anim) const { |
||||
ASSERT_EQ(anim_original.size(), anim.size()); |
||||
for (size_t i = 0; i < anim.size(); ++i) { |
||||
Imgcodecs_Avif_RoundTripSuite::ValidateRead(anim_original[i], anim[i]); |
||||
} |
||||
} |
||||
|
||||
static const int kWidth; |
||||
static const int kHeight; |
||||
|
||||
private: |
||||
typedef std::tuple<int, int> Key; |
||||
static std::map<Key, std::vector<cv::Mat>> anims_; |
||||
}; |
||||
std::map<std::tuple<int, int>, std::vector<cv::Mat>> |
||||
Imgcodecs_Avif_Animation_RoundTripSuite::anims_; |
||||
const int Imgcodecs_Avif_Animation_RoundTripSuite::kWidth = 5; |
||||
const int Imgcodecs_Avif_Animation_RoundTripSuite::kHeight = 5; |
||||
|
||||
class Imgcodecs_Avif_Animation_WriteReadSuite |
||||
: public Imgcodecs_Avif_Animation_RoundTripSuite {}; |
||||
|
||||
TEST_P(Imgcodecs_Avif_Animation_WriteReadSuite, encode_decode) { |
||||
const std::vector<cv::Mat>& anim_original = get_anim_original(); |
||||
ASSERT_FALSE(anim_original.empty()); |
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif"); |
||||
if (!IsBitDepthValid()) { |
||||
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_), |
||||
cv::Exception); |
||||
EXPECT_NE(0, remove(output.c_str())); |
||||
return; |
||||
} |
||||
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_)); |
||||
|
||||
// Read from file.
|
||||
std::vector<cv::Mat> anim; |
||||
ASSERT_TRUE(cv::imreadmulti(output, anim, imread_mode_)); |
||||
|
||||
ValidateRead(anim_original, anim); |
||||
|
||||
EXPECT_EQ(0, remove(output.c_str())); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P( |
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteReadSuite, |
||||
::testing::Combine(::testing::ValuesIn({8, 10, 12}), |
||||
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}), |
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, |
||||
IMREAD_COLOR}))); |
||||
class Imgcodecs_Avif_Animation_WriteDecodeSuite |
||||
: public Imgcodecs_Avif_Animation_RoundTripSuite {}; |
||||
|
||||
TEST_P(Imgcodecs_Avif_Animation_WriteDecodeSuite, encode_decode) { |
||||
const std::vector<cv::Mat>& anim_original = get_anim_original(); |
||||
ASSERT_FALSE(anim_original.empty()); |
||||
|
||||
// Encode.
|
||||
const string output = cv::tempfile(".avif"); |
||||
if (!IsBitDepthValid()) { |
||||
EXPECT_THROW(cv::imwritemulti(output, anim_original, encoding_params_), |
||||
cv::Exception); |
||||
EXPECT_NE(0, remove(output.c_str())); |
||||
return; |
||||
} |
||||
EXPECT_NO_THROW(cv::imwritemulti(output, anim_original, encoding_params_)); |
||||
|
||||
// Put file into buffer and read from buffer.
|
||||
std::ifstream file(output, std::ios::binary | std::ios::ate); |
||||
std::streamsize size = file.tellg(); |
||||
file.seekg(0, std::ios::beg); |
||||
std::vector<unsigned char> buf(size); |
||||
EXPECT_TRUE(file.read(reinterpret_cast<char*>(buf.data()), size)); |
||||
EXPECT_EQ(0, remove(output.c_str())); |
||||
std::vector<cv::Mat> anim; |
||||
ASSERT_TRUE(cv::imdecodemulti(buf, imread_mode_, anim)); |
||||
|
||||
ValidateRead(anim_original, anim); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P( |
||||
Imgcodecs_AVIF, Imgcodecs_Avif_Animation_WriteDecodeSuite, |
||||
::testing::Combine(::testing::ValuesIn({8, 10, 12}), |
||||
::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}), |
||||
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, |
||||
IMREAD_COLOR}))); |
||||
|
||||
} // namespace
|
||||
} // namespace opencv_test
|
||||
|
||||
#endif // HAVE_AVIF
|
@ -0,0 +1,65 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#ifndef OPENCV_OBJDETECT_BARCODE_HPP |
||||
#define OPENCV_OBJDETECT_BARCODE_HPP |
||||
|
||||
#include <opencv2/core.hpp> |
||||
#include <opencv2/objdetect/graphical_code_detector.hpp> |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
//! @addtogroup objdetect_barcode
|
||||
//! @{
|
||||
|
||||
class CV_EXPORTS_W_SIMPLE BarcodeDetector : public cv::GraphicalCodeDetector |
||||
{ |
||||
public: |
||||
/** @brief Initialize the BarcodeDetector.
|
||||
*/ |
||||
CV_WRAP BarcodeDetector(); |
||||
/** @brief Initialize the BarcodeDetector.
|
||||
* |
||||
* Parameters allow to load _optional_ Super Resolution DNN model for better quality. |
||||
* @param prototxt_path prototxt file path for the super resolution model |
||||
* @param model_path model file path for the super resolution model |
||||
*/ |
||||
CV_WRAP BarcodeDetector(const std::string &prototxt_path, const std::string &model_path); |
||||
~BarcodeDetector(); |
||||
|
||||
/** @brief Decodes barcode in image once it's found by the detect() method.
|
||||
* |
||||
* @param img grayscale or color (BGR) image containing bar code. |
||||
* @param points vector of rotated rectangle vertices found by detect() method (or some other algorithm). |
||||
* For N detected barcodes, the dimensions of this array should be [N][4]. |
||||
* Order of four points in vector<Point2f> is bottomLeft, topLeft, topRight, bottomRight. |
||||
* @param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded. |
||||
* @param decoded_type vector strings, specifies the type of these barcodes |
||||
* @return true if at least one valid barcode have been found |
||||
*/ |
||||
CV_WRAP bool decodeWithType(InputArray img, |
||||
InputArray points, |
||||
CV_OUT std::vector<std::string> &decoded_info, |
||||
CV_OUT std::vector<std::string> &decoded_type) const; |
||||
|
||||
/** @brief Both detects and decodes barcode
|
||||
|
||||
* @param img grayscale or color (BGR) image containing barcode. |
||||
* @param decoded_info UTF8-encoded output vector of string(s) or empty vector of string if the codes cannot be decoded. |
||||
* @param decoded_type vector of strings, specifies the type of these barcodes |
||||
* @param points optional output vector of vertices of the found barcode rectangle. Will be empty if not found. |
||||
* @return true if at least one valid barcode have been found |
||||
*/ |
||||
CV_WRAP bool detectAndDecodeWithType(InputArray img, |
||||
CV_OUT std::vector<std::string> &decoded_info, |
||||
CV_OUT std::vector<std::string> &decoded_type, |
||||
OutputArray points = noArray()) const; |
||||
}; |
||||
//! @}
|
||||
|
||||
}} // cv::barcode::
|
||||
|
||||
#endif // OPENCV_OBJDETECT_BARCODE_HPP
|
@ -0,0 +1,81 @@ |
||||
// 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_GRAPHICAL_CODE_DETECTOR_HPP |
||||
#define OPENCV_OBJDETECT_GRAPHICAL_CODE_DETECTOR_HPP |
||||
|
||||
#include <opencv2/core.hpp> |
||||
|
||||
namespace cv { |
||||
|
||||
//! @addtogroup objdetect_common
|
||||
//! @{
|
||||
|
||||
class CV_EXPORTS_W_SIMPLE GraphicalCodeDetector { |
||||
public: |
||||
CV_DEPRECATED_EXTERNAL // avoid using in C++ code, will be moved to "protected" (need to fix bindings first)
|
||||
GraphicalCodeDetector(); |
||||
|
||||
GraphicalCodeDetector(const GraphicalCodeDetector&) = default; |
||||
GraphicalCodeDetector(GraphicalCodeDetector&&) = default; |
||||
GraphicalCodeDetector& operator=(const GraphicalCodeDetector&) = default; |
||||
GraphicalCodeDetector& operator=(GraphicalCodeDetector&&) = default; |
||||
|
||||
/** @brief Detects graphical code in image and returns the quadrangle containing the code.
|
||||
@param img grayscale or color (BGR) image containing (or not) graphical code. |
||||
@param points Output vector of vertices of the minimum-area quadrangle containing the code. |
||||
*/ |
||||
CV_WRAP bool detect(InputArray img, OutputArray points) const; |
||||
|
||||
/** @brief Decodes graphical code in image once it's found by the detect() method.
|
||||
|
||||
Returns UTF8-encoded output string or empty string if the code cannot be decoded. |
||||
@param img grayscale or color (BGR) image containing graphical code. |
||||
@param points Quadrangle vertices found by detect() method (or some other algorithm). |
||||
@param straight_code The optional output image containing binarized code, will be empty if not found. |
||||
*/ |
||||
CV_WRAP std::string decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const; |
||||
|
||||
/** @brief Both detects and decodes graphical code
|
||||
|
||||
@param img grayscale or color (BGR) image containing graphical code. |
||||
@param points optional output array of vertices of the found graphical code quadrangle, will be empty if not found. |
||||
@param straight_code The optional output image containing binarized code |
||||
*/ |
||||
CV_WRAP std::string detectAndDecode(InputArray img, OutputArray points = noArray(), |
||||
OutputArray straight_code = noArray()) const; |
||||
|
||||
|
||||
/** @brief Detects graphical codes in image and returns the vector of the quadrangles containing the codes.
|
||||
@param img grayscale or color (BGR) image containing (or not) graphical codes. |
||||
@param points Output vector of vector of vertices of the minimum-area quadrangle containing the codes. |
||||
*/ |
||||
CV_WRAP bool detectMulti(InputArray img, OutputArray points) const; |
||||
|
||||
/** @brief Decodes graphical codes in image once it's found by the detect() method.
|
||||
@param img grayscale or color (BGR) image containing graphical codes. |
||||
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded. |
||||
@param points vector of Quadrangle vertices found by detect() method (or some other algorithm). |
||||
@param straight_code The optional output vector of images containing binarized codes |
||||
*/ |
||||
CV_WRAP bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector<std::string>& decoded_info, |
||||
OutputArrayOfArrays straight_code = noArray()) const; |
||||
|
||||
/** @brief Both detects and decodes graphical codes
|
||||
@param img grayscale or color (BGR) image containing graphical codes. |
||||
@param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded. |
||||
@param points optional output vector of vertices of the found graphical code quadrangles. Will be empty if not found. |
||||
@param straight_code The optional vector of images containing binarized codes |
||||
*/ |
||||
CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector<std::string>& decoded_info, OutputArray points = noArray(), |
||||
OutputArrayOfArrays straight_code = noArray()) const; |
||||
struct Impl; |
||||
protected: |
||||
Ptr<Impl> p; |
||||
}; |
||||
|
||||
//! @}
|
||||
|
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,50 @@ |
||||
package org.opencv.test.barcode; |
||||
|
||||
import java.util.List; |
||||
import org.opencv.core.Mat; |
||||
import org.opencv.objdetect.BarcodeDetector; |
||||
import org.opencv.imgcodecs.Imgcodecs; |
||||
import org.opencv.test.OpenCVTestCase; |
||||
import java.util.ArrayList; |
||||
|
||||
public class BarcodeDetectorTest extends OpenCVTestCase { |
||||
|
||||
private final static String ENV_OPENCV_TEST_DATA_PATH = "OPENCV_TEST_DATA_PATH"; |
||||
private String testDataPath; |
||||
|
||||
@Override |
||||
protected void setUp() throws Exception { |
||||
super.setUp(); |
||||
|
||||
testDataPath = System.getenv(ENV_OPENCV_TEST_DATA_PATH); |
||||
if (testDataPath == null) |
||||
throw new Exception(ENV_OPENCV_TEST_DATA_PATH + " has to be defined!"); |
||||
} |
||||
|
||||
public void testDetectAndDecode() { |
||||
Mat img = Imgcodecs.imread(testDataPath + "/cv/barcode/multiple/4_barcodes.jpg"); |
||||
assertFalse(img.empty()); |
||||
BarcodeDetector detector = new BarcodeDetector(); |
||||
assertNotNull(detector); |
||||
List < String > infos = new ArrayList< String >(); |
||||
List < String > types = new ArrayList< String >(); |
||||
|
||||
boolean result = detector.detectAndDecodeWithType(img, infos, types); |
||||
assertTrue(result); |
||||
assertEquals(infos.size(), 4); |
||||
assertEquals(types.size(), 4); |
||||
final String[] correctResults = {"9787122276124", "9787118081473", "9787564350840", "9783319200064"}; |
||||
for (int i = 0; i < 4; i++) { |
||||
assertEquals(types.get(i), "EAN_13"); |
||||
result = false; |
||||
for (int j = 0; j < 4; j++) { |
||||
if (correctResults[j].equals(infos.get(i))) { |
||||
result = true; |
||||
break; |
||||
} |
||||
} |
||||
assertTrue(result); |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"ManualFuncs" : { |
||||
"QRCodeDetectorAruco": { |
||||
"getDetectorParameters": { "declaration" : [""], "implementation" : [""] } |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
#!/usr/bin/env python |
||||
''' |
||||
=============================================================================== |
||||
Barcode detect and decode pipeline. |
||||
=============================================================================== |
||||
''' |
||||
import os |
||||
import numpy as np |
||||
import cv2 as cv |
||||
|
||||
from tests_common import NewOpenCVTests |
||||
|
||||
class barcode_detector_test(NewOpenCVTests): |
||||
|
||||
def test_detect(self): |
||||
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/multiple/4_barcodes.jpg')) |
||||
self.assertFalse(img is None) |
||||
detector = cv.barcode_BarcodeDetector() |
||||
retval, corners = detector.detect(img) |
||||
self.assertTrue(retval) |
||||
self.assertEqual(corners.shape, (4, 4, 2)) |
||||
|
||||
def test_detect_and_decode(self): |
||||
img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/barcode/single/book.jpg')) |
||||
self.assertFalse(img is None) |
||||
detector = cv.barcode_BarcodeDetector() |
||||
retval, decoded_info, decoded_type, corners = detector.detectAndDecodeWithType(img) |
||||
self.assertTrue(retval) |
||||
self.assertTrue(len(decoded_info) > 0) |
||||
self.assertTrue(len(decoded_type) > 0) |
||||
self.assertEqual(decoded_info[0], "9787115279460") |
||||
self.assertEqual(decoded_type[0], "EAN_13") |
||||
self.assertEqual(corners.shape, (1, 4, 2)) |
@ -0,0 +1,114 @@ |
||||
// 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/objdetect/barcode.hpp" |
||||
|
||||
namespace opencv_test{namespace{ |
||||
|
||||
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_multi; |
||||
typedef ::perf::TestBaseWithParam< tuple<string, cv::Size> > Perf_Barcode_single; |
||||
|
||||
PERF_TEST_P_(Perf_Barcode_multi, detect) |
||||
{ |
||||
const string root = "cv/barcode/multiple/"; |
||||
const string name_current_image = get<0>(GetParam()); |
||||
const cv::Size sz = get<1>(GetParam()); |
||||
const string image_path = findDataFile(root + name_current_image); |
||||
|
||||
Mat src = imread(image_path); |
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; |
||||
cv::resize(src, src, sz); |
||||
|
||||
vector< Point > corners; |
||||
auto bardet = barcode::BarcodeDetector(); |
||||
bool res = false; |
||||
TEST_CYCLE() |
||||
{ |
||||
res = bardet.detectMulti(src, corners); |
||||
} |
||||
SANITY_CHECK_NOTHING(); |
||||
ASSERT_TRUE(res); |
||||
} |
||||
|
||||
PERF_TEST_P_(Perf_Barcode_multi, detect_decode) |
||||
{ |
||||
const string root = "cv/barcode/multiple/"; |
||||
const string name_current_image = get<0>(GetParam()); |
||||
const cv::Size sz = get<1>(GetParam()); |
||||
const string image_path = findDataFile(root + name_current_image); |
||||
|
||||
Mat src = imread(image_path); |
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; |
||||
cv::resize(src, src, sz); |
||||
|
||||
vector<std::string> decoded_info; |
||||
vector<std::string> decoded_type; |
||||
vector< Point > corners; |
||||
auto bardet = barcode::BarcodeDetector(); |
||||
bool res = false; |
||||
TEST_CYCLE() |
||||
{ |
||||
res = bardet.detectAndDecodeWithType(src, decoded_info, decoded_type, corners); |
||||
} |
||||
SANITY_CHECK_NOTHING(); |
||||
ASSERT_TRUE(res); |
||||
} |
||||
|
||||
PERF_TEST_P_(Perf_Barcode_single, detect) |
||||
{ |
||||
const string root = "cv/barcode/single/"; |
||||
const string name_current_image = get<0>(GetParam()); |
||||
const cv::Size sz = get<1>(GetParam()); |
||||
const string image_path = findDataFile(root + name_current_image); |
||||
|
||||
Mat src = imread(image_path); |
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; |
||||
cv::resize(src, src, sz); |
||||
|
||||
vector< Point > corners; |
||||
auto bardet = barcode::BarcodeDetector(); |
||||
bool res = false; |
||||
TEST_CYCLE() |
||||
{ |
||||
res = bardet.detectMulti(src, corners); |
||||
} |
||||
SANITY_CHECK_NOTHING(); |
||||
ASSERT_TRUE(res); |
||||
} |
||||
|
||||
PERF_TEST_P_(Perf_Barcode_single, detect_decode) |
||||
{ |
||||
const string root = "cv/barcode/single/"; |
||||
const string name_current_image = get<0>(GetParam()); |
||||
const cv::Size sz = get<1>(GetParam()); |
||||
const string image_path = findDataFile(root + name_current_image); |
||||
|
||||
Mat src = imread(image_path); |
||||
ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; |
||||
cv::resize(src, src, sz); |
||||
|
||||
vector<std::string> decoded_info; |
||||
vector<std::string> decoded_type; |
||||
vector< Point > corners; |
||||
auto bardet = barcode::BarcodeDetector(); |
||||
bool res = false; |
||||
TEST_CYCLE() |
||||
{ |
||||
res = bardet.detectAndDecodeWithType(src, decoded_info, decoded_type, corners); |
||||
} |
||||
SANITY_CHECK_NOTHING(); |
||||
ASSERT_TRUE(res); |
||||
} |
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_multi, |
||||
testing::Combine( |
||||
testing::Values("4_barcodes.jpg"), |
||||
testing::Values(cv::Size(2041, 2722), cv::Size(1361, 1815), cv::Size(680, 907)))); |
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Barcode_single, |
||||
testing::Combine( |
||||
testing::Values("book.jpg", "bottle_1.jpg", "bottle_2.jpg"), |
||||
testing::Values(cv::Size(480, 360), cv::Size(640, 480), cv::Size(800, 600)))); |
||||
|
||||
}} //namespace
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,374 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#include "precomp.hpp" |
||||
#include <opencv2/objdetect/barcode.hpp> |
||||
#include <opencv2/core/utils/filesystem.hpp> |
||||
#include "barcode_decoder/ean13_decoder.hpp" |
||||
#include "barcode_decoder/ean8_decoder.hpp" |
||||
#include "barcode_detector/bardetect.hpp" |
||||
#include "barcode_decoder/common/super_scale.hpp" |
||||
#include "barcode_decoder/common/utils.hpp" |
||||
#include "graphical_code_detector_impl.hpp" |
||||
|
||||
using std::string; |
||||
using std::vector; |
||||
using std::make_shared; |
||||
using std::array; |
||||
using std::shared_ptr; |
||||
using std::dynamic_pointer_cast; |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
//==================================================================================================
|
||||
|
||||
static bool checkBarInputImage(InputArray img, Mat &gray) |
||||
{ |
||||
CV_Assert(!img.empty()); |
||||
CV_CheckDepthEQ(img.depth(), CV_8U, ""); |
||||
if (img.cols() <= 40 || img.rows() <= 40) |
||||
{ |
||||
return false; // image data is not enough for providing reliable results
|
||||
} |
||||
int incn = img.channels(); |
||||
CV_Check(incn, incn == 1 || incn == 3 || incn == 4, ""); |
||||
if (incn == 3 || incn == 4) |
||||
{ |
||||
cvtColor(img, gray, COLOR_BGR2GRAY); |
||||
} |
||||
else |
||||
{ |
||||
gray = img.getMat(); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static void updatePointsResult(OutputArray points_, const vector<Point2f> &points) |
||||
{ |
||||
if (points_.needed()) |
||||
{ |
||||
int N = int(points.size() / 4); |
||||
if (N > 0) |
||||
{ |
||||
Mat m_p(N, 4, CV_32FC2, (void *) &points[0]); |
||||
int points_type = points_.fixedType() ? points_.type() : CV_32FC2; |
||||
m_p.reshape(2, points_.rows()).convertTo(points_, points_type); // Mat layout: N x 4 x 2cn
|
||||
} |
||||
else |
||||
{ |
||||
points_.release(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
inline const array<shared_ptr<AbsDecoder>, 2> &getDecoders() |
||||
{ |
||||
//indicate Decoder
|
||||
static const array<shared_ptr<AbsDecoder>, 2> decoders{ |
||||
shared_ptr<AbsDecoder>(new Ean13Decoder()), shared_ptr<AbsDecoder>(new Ean8Decoder())}; |
||||
return decoders; |
||||
} |
||||
|
||||
//==================================================================================================
|
||||
|
||||
class BarDecode |
||||
{ |
||||
public: |
||||
void init(const vector<Mat> &bar_imgs_); |
||||
|
||||
const vector<Result> &getDecodeInformation() |
||||
{ return result_info; } |
||||
|
||||
bool decodeMultiplyProcess(); |
||||
|
||||
private: |
||||
vector<Mat> bar_imgs; |
||||
vector<Result> result_info; |
||||
}; |
||||
|
||||
void BarDecode::init(const vector<Mat> &bar_imgs_) |
||||
{ |
||||
bar_imgs = bar_imgs_; |
||||
} |
||||
|
||||
bool BarDecode::decodeMultiplyProcess() |
||||
{ |
||||
static float constexpr THRESHOLD_CONF = 0.6f; |
||||
result_info.clear(); |
||||
result_info.resize(bar_imgs.size()); |
||||
parallel_for_(Range(0, int(bar_imgs.size())), [&](const Range &range) { |
||||
for (int i = range.start; i < range.end; i++) |
||||
{ |
||||
Mat bin_bar; |
||||
Result max_res; |
||||
float max_conf = -1.f; |
||||
bool decoded = false; |
||||
for (const auto &decoder:getDecoders()) |
||||
{ |
||||
if (decoded) |
||||
{ break; } |
||||
for (const auto binary_type : binary_types) |
||||
{ |
||||
binarize(bar_imgs[i], bin_bar, binary_type); |
||||
auto cur_res = decoder->decodeROI(bin_bar); |
||||
if (cur_res.second > max_conf) |
||||
{ |
||||
max_res = cur_res.first; |
||||
max_conf = cur_res.second; |
||||
if (max_conf > THRESHOLD_CONF) |
||||
{ |
||||
// code decoded
|
||||
decoded = true; |
||||
break; |
||||
} |
||||
} |
||||
} //binary types
|
||||
} //decoder types
|
||||
|
||||
result_info[i] = max_res; |
||||
} |
||||
}); |
||||
return !result_info.empty(); |
||||
} |
||||
|
||||
//==================================================================================================
|
||||
// Private class definition and implementation (pimpl)
|
||||
|
||||
struct BarcodeImpl : public GraphicalCodeDetector::Impl |
||||
{ |
||||
public: |
||||
shared_ptr<SuperScale> sr; |
||||
bool use_nn_sr = false; |
||||
|
||||
public: |
||||
//=================
|
||||
// own methods
|
||||
BarcodeImpl() = default; |
||||
vector<Mat> initDecode(const Mat &src, const vector<vector<Point2f>> &points) const; |
||||
bool decodeWithType(InputArray img, |
||||
InputArray points, |
||||
vector<string> &decoded_info, |
||||
vector<string> &decoded_type) const; |
||||
bool detectAndDecodeWithType(InputArray img, |
||||
vector<string> &decoded_info, |
||||
vector<string> &decoded_type, |
||||
OutputArray points_) const; |
||||
|
||||
//=================
|
||||
// implement interface
|
||||
~BarcodeImpl() CV_OVERRIDE {} |
||||
bool detect(InputArray img, OutputArray points) const CV_OVERRIDE; |
||||
string decode(InputArray img, InputArray points, OutputArray straight_code) const CV_OVERRIDE; |
||||
string detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const CV_OVERRIDE; |
||||
bool detectMulti(InputArray img, OutputArray points) const CV_OVERRIDE; |
||||
bool decodeMulti(InputArray img, InputArray points, vector<string>& decoded_info, OutputArrayOfArrays straight_code) const CV_OVERRIDE; |
||||
bool detectAndDecodeMulti(InputArray img, vector<string>& decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const CV_OVERRIDE; |
||||
}; |
||||
|
||||
// return cropped and scaled bar img
|
||||
vector<Mat> BarcodeImpl::initDecode(const Mat &src, const vector<vector<Point2f>> &points) const |
||||
{ |
||||
vector<Mat> bar_imgs; |
||||
for (auto &corners : points) |
||||
{ |
||||
Mat bar_img; |
||||
cropROI(src, bar_img, corners); |
||||
// sharpen(bar_img, bar_img);
|
||||
// empirical settings
|
||||
if (bar_img.cols < 320 || bar_img.cols > 640) |
||||
{ |
||||
float scale = 560.0f / static_cast<float>(bar_img.cols); |
||||
sr->processImageScale(bar_img, bar_img, scale, use_nn_sr); |
||||
} |
||||
bar_imgs.emplace_back(bar_img); |
||||
} |
||||
return bar_imgs; |
||||
} |
||||
|
||||
bool BarcodeImpl::decodeWithType(InputArray img, |
||||
InputArray points, |
||||
vector<string> &decoded_info, |
||||
vector<string> &decoded_type) const |
||||
{ |
||||
Mat inarr; |
||||
if (!checkBarInputImage(img, inarr)) |
||||
{ |
||||
return false; |
||||
} |
||||
CV_Assert(points.size().width > 0); |
||||
CV_Assert((points.size().width % 4) == 0); |
||||
vector<vector<Point2f>> src_points; |
||||
Mat bar_points = points.getMat(); |
||||
bar_points = bar_points.reshape(2, 1); |
||||
for (int i = 0; i < bar_points.size().width; i += 4) |
||||
{ |
||||
vector<Point2f> tempMat = bar_points.colRange(i, i + 4); |
||||
if (contourArea(tempMat) > 0.0) |
||||
{ |
||||
src_points.push_back(tempMat); |
||||
} |
||||
} |
||||
CV_Assert(!src_points.empty()); |
||||
vector<Mat> bar_imgs = initDecode(inarr, src_points); |
||||
BarDecode bardec; |
||||
bardec.init(bar_imgs); |
||||
bardec.decodeMultiplyProcess(); |
||||
const vector<Result> info = bardec.getDecodeInformation(); |
||||
decoded_info.clear(); |
||||
decoded_type.clear(); |
||||
bool ok = false; |
||||
for (const auto &res : info) |
||||
{ |
||||
if (res.isValid()) |
||||
{ |
||||
ok = true; |
||||
} |
||||
|
||||
decoded_info.emplace_back(res.result); |
||||
decoded_type.emplace_back(res.typeString()); |
||||
} |
||||
return ok; |
||||
} |
||||
|
||||
bool BarcodeImpl::detectAndDecodeWithType(InputArray img, |
||||
vector<string> &decoded_info, |
||||
vector<string> &decoded_type, |
||||
OutputArray points_) const |
||||
{ |
||||
Mat inarr; |
||||
if (!checkBarInputImage(img, inarr)) |
||||
{ |
||||
points_.release(); |
||||
return false; |
||||
} |
||||
vector<Point2f> points; |
||||
bool ok = this->detect(inarr, points); |
||||
if (!ok) |
||||
{ |
||||
points_.release(); |
||||
return false; |
||||
} |
||||
updatePointsResult(points_, points); |
||||
decoded_info.clear(); |
||||
decoded_type.clear(); |
||||
ok = decodeWithType(inarr, points, decoded_info, decoded_type); |
||||
return ok; |
||||
} |
||||
|
||||
bool BarcodeImpl::detect(InputArray img, OutputArray points) const |
||||
{ |
||||
Mat inarr; |
||||
if (!checkBarInputImage(img, inarr)) |
||||
{ |
||||
points.release(); |
||||
return false; |
||||
} |
||||
|
||||
Detect bardet; |
||||
bardet.init(inarr); |
||||
bardet.localization(); |
||||
if (!bardet.computeTransformationPoints()) |
||||
{ return false; } |
||||
vector<vector<Point2f>> pnts2f = bardet.getTransformationPoints(); |
||||
vector<Point2f> trans_points; |
||||
for (auto &i : pnts2f) |
||||
{ |
||||
for (const auto &j : i) |
||||
{ |
||||
trans_points.push_back(j); |
||||
} |
||||
} |
||||
updatePointsResult(points, trans_points); |
||||
return true; |
||||
} |
||||
|
||||
string BarcodeImpl::decode(InputArray img, InputArray points, OutputArray straight_code) const |
||||
{ |
||||
CV_UNUSED(straight_code); |
||||
vector<string> decoded_info; |
||||
vector<string> decoded_type; |
||||
if (!decodeWithType(img, points, decoded_info, decoded_type)) |
||||
return string(); |
||||
if (decoded_info.size() < 1) |
||||
return string(); |
||||
return decoded_info[0]; |
||||
} |
||||
|
||||
string BarcodeImpl::detectAndDecode(InputArray img, OutputArray points, OutputArray straight_code) const |
||||
{ |
||||
CV_UNUSED(straight_code); |
||||
vector<string> decoded_info; |
||||
vector<string> decoded_type; |
||||
vector<Point> points_; |
||||
if (!detectAndDecodeWithType(img, decoded_info, decoded_type, points_)) |
||||
return string(); |
||||
if (points_.size() < 4 || decoded_info.size() < 1) |
||||
return string(); |
||||
points_.resize(4); |
||||
points.setTo(points_); |
||||
return decoded_info[0]; |
||||
} |
||||
|
||||
bool BarcodeImpl::detectMulti(InputArray img, OutputArray points) const |
||||
{ |
||||
return detect(img, points); |
||||
} |
||||
|
||||
bool BarcodeImpl::decodeMulti(InputArray img, InputArray points, vector<string> &decoded_info, OutputArrayOfArrays straight_code) const |
||||
{ |
||||
CV_UNUSED(straight_code); |
||||
vector<string> decoded_type; |
||||
return decodeWithType(img, points, decoded_info, decoded_type); |
||||
} |
||||
|
||||
bool BarcodeImpl::detectAndDecodeMulti(InputArray img, vector<string> &decoded_info, OutputArray points, OutputArrayOfArrays straight_code) const |
||||
{ |
||||
CV_UNUSED(straight_code); |
||||
vector<string> decoded_type; |
||||
return detectAndDecodeWithType(img, decoded_info, decoded_type, points); |
||||
} |
||||
|
||||
//==================================================================================================
|
||||
// Public class implementation
|
||||
|
||||
BarcodeDetector::BarcodeDetector() |
||||
: BarcodeDetector(string(), string()) |
||||
{ |
||||
} |
||||
|
||||
BarcodeDetector::BarcodeDetector(const string &prototxt_path, const string &model_path) |
||||
{ |
||||
Ptr<BarcodeImpl> p_ = new BarcodeImpl(); |
||||
p = p_; |
||||
if (!prototxt_path.empty() && !model_path.empty()) |
||||
{ |
||||
CV_Assert(utils::fs::exists(prototxt_path)); |
||||
CV_Assert(utils::fs::exists(model_path)); |
||||
p_->sr = make_shared<SuperScale>(); |
||||
int res = p_->sr->init(prototxt_path, model_path); |
||||
CV_Assert(res == 0); |
||||
p_->use_nn_sr = true; |
||||
} |
||||
} |
||||
|
||||
BarcodeDetector::~BarcodeDetector() = default; |
||||
|
||||
bool BarcodeDetector::decodeWithType(InputArray img, InputArray points, vector<string> &decoded_info, vector<string> &decoded_type) const |
||||
{ |
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p); |
||||
CV_Assert(p_); |
||||
return p_->decodeWithType(img, points, decoded_info, decoded_type); |
||||
} |
||||
|
||||
bool BarcodeDetector::detectAndDecodeWithType(InputArray img, vector<string> &decoded_info, vector<string> &decoded_type, OutputArray points_) const |
||||
{ |
||||
Ptr<BarcodeImpl> p_ = dynamic_pointer_cast<BarcodeImpl>(p); |
||||
CV_Assert(p_); |
||||
return p_->detectAndDecodeWithType(img, decoded_info, decoded_type, points_); |
||||
} |
||||
|
||||
}// namespace barcode
|
||||
} // namespace cv
|
@ -0,0 +1,118 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#include "../precomp.hpp" |
||||
#include "abs_decoder.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
void cropROI(const Mat &src, Mat &dst, const std::vector<Point2f> &rects) |
||||
{ |
||||
std::vector<Point2f> vertices = rects; |
||||
int height = cvRound(norm(vertices[0] - vertices[1])); |
||||
int width = cvRound(norm(vertices[1] - vertices[2])); |
||||
if (height > width) |
||||
{ |
||||
std::swap(height, width); |
||||
Point2f v0 = vertices[0]; |
||||
vertices.erase(vertices.begin()); |
||||
vertices.push_back(v0); |
||||
} |
||||
std::vector<Point2f> dst_vertices{ |
||||
Point2f(0, (float) (height - 1)), Point2f(0, 0), Point2f((float) (width - 1), 0), |
||||
Point2f((float) (width - 1), (float) (height - 1))}; |
||||
dst.create(Size(width, height), CV_8UC1); |
||||
Mat M = getPerspectiveTransform(vertices, dst_vertices); |
||||
warpPerspective(src, dst, M, dst.size(), cv::INTER_LINEAR, BORDER_CONSTANT, Scalar(255)); |
||||
} |
||||
|
||||
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter) |
||||
{ |
||||
size_t counter_length = counter.pattern.size(); |
||||
std::fill(counter.pattern.begin(), counter.pattern.end(), 0); |
||||
counter.sum = 0; |
||||
size_t end = row.size(); |
||||
uchar color = row[start]; |
||||
uint counterPosition = 0; |
||||
while (start < end) |
||||
{ |
||||
if (row[start] == color) |
||||
{ // that is, exactly one is true
|
||||
counter.pattern[counterPosition]++; |
||||
counter.sum++; |
||||
} |
||||
else |
||||
{ |
||||
counterPosition++; |
||||
if (counterPosition == counter_length) |
||||
{ |
||||
break; |
||||
} |
||||
else |
||||
{ |
||||
counter.pattern[counterPosition] = 1; |
||||
counter.sum++; |
||||
color = 255 - color; |
||||
} |
||||
} |
||||
++start; |
||||
} |
||||
} |
||||
|
||||
static inline uint |
||||
patternMatchVariance(const Counter &counter, const std::vector<int> &pattern, uint maxIndividualVariance) |
||||
{ |
||||
size_t numCounters = counter.pattern.size(); |
||||
int total = static_cast<int>(counter.sum); |
||||
int patternLength = std::accumulate(pattern.cbegin(), pattern.cend(), 0); |
||||
if (total < patternLength) |
||||
{ |
||||
// If we don't even have one pixel per unit of bar width, assume this is too small
|
||||
// to reliably match, so fail:
|
||||
// and use constexpr functions
|
||||
return WHITE;// max
|
||||
} |
||||
// We're going to fake floating-point math in integers. We just need to use more bits.
|
||||
// Scale up patternLength so that intermediate values below like scaledCounter will have
|
||||
// more "significant digits"
|
||||
|
||||
int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength; |
||||
maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT; |
||||
uint totalVariance = 0; |
||||
for (uint x = 0; x < numCounters; x++) |
||||
{ |
||||
int cnt = counter.pattern[x] << INTEGER_MATH_SHIFT; |
||||
int scaledPattern = pattern[x] * unitBarWidth; |
||||
uint variance = std::abs(cnt - scaledPattern); |
||||
if (variance > maxIndividualVariance) |
||||
{ |
||||
return WHITE; |
||||
} |
||||
totalVariance += variance; |
||||
} |
||||
return totalVariance / total; |
||||
} |
||||
|
||||
/**
|
||||
* Determines how closely a set of observed counts of runs of black/white values matches a given |
||||
* target pattern. This is reported as the ratio of the total variance from the expected pattern |
||||
* proportions across all pattern elements, to the length of the pattern. |
||||
* |
||||
* @param counters observed counters |
||||
* @param pattern expected pattern |
||||
* @param maxIndividualVariance The most any counter can differ before we give up |
||||
* @return ratio of total variance between counters and pattern compared to total pattern size, |
||||
* where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means |
||||
* the total variance between counters and patterns equals the pattern length, higher values mean |
||||
* even more variance |
||||
*/ |
||||
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual) |
||||
{ |
||||
CV_Assert(counters.pattern.size() == pattern.size()); |
||||
return patternMatchVariance(counters, pattern, maxIndividual); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,99 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#ifndef OPENCV_BARCODE_ABS_DECODER_HPP |
||||
#define OPENCV_BARCODE_ABS_DECODER_HPP |
||||
|
||||
#include "opencv2/objdetect/barcode.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
using std::string; |
||||
using std::vector; |
||||
constexpr static uchar BLACK = std::numeric_limits<uchar>::min(); |
||||
// WHITE elemental area is 0xff
|
||||
constexpr static uchar WHITE = std::numeric_limits<uchar>::max(); |
||||
|
||||
|
||||
struct Result |
||||
{ |
||||
enum BarcodeType |
||||
{ |
||||
BARCODE_NONE, |
||||
BARCODE_EAN_8, |
||||
BARCODE_EAN_13, |
||||
BARCODE_UPC_A, |
||||
BARCODE_UPC_E, |
||||
BARCODE_UPC_EAN_EXTENSION |
||||
}; |
||||
|
||||
std::string result; |
||||
BarcodeType format = Result::BARCODE_NONE; |
||||
|
||||
Result() = default; |
||||
|
||||
Result(const std::string &_result, BarcodeType _format) |
||||
{ |
||||
result = _result; |
||||
format = _format; |
||||
} |
||||
string typeString() const |
||||
{ |
||||
switch (format) |
||||
{ |
||||
case Result::BARCODE_EAN_8: return "EAN_8"; |
||||
case Result::BARCODE_EAN_13: return "EAN_13"; |
||||
case Result::BARCODE_UPC_E: return "UPC_E"; |
||||
case Result::BARCODE_UPC_A: return "UPC_A"; |
||||
case Result::BARCODE_UPC_EAN_EXTENSION: return "UPC_EAN_EXTENSION"; |
||||
default: return string(); |
||||
} |
||||
} |
||||
bool isValid() const |
||||
{ |
||||
return format != BARCODE_NONE; |
||||
} |
||||
}; |
||||
|
||||
struct Counter |
||||
{ |
||||
std::vector<int> pattern; |
||||
uint sum; |
||||
|
||||
explicit Counter(const vector<int> &_pattern) |
||||
{ |
||||
pattern = _pattern; |
||||
sum = 0; |
||||
} |
||||
}; |
||||
|
||||
class AbsDecoder |
||||
{ |
||||
public: |
||||
virtual std::pair<Result, float> decodeROI(const Mat &bar_img) const = 0; |
||||
|
||||
virtual ~AbsDecoder() = default; |
||||
|
||||
protected: |
||||
virtual Result decode(const vector<uchar> &data) const = 0; |
||||
|
||||
virtual bool isValid(const string &result) const = 0; |
||||
|
||||
size_t bits_num{}; |
||||
size_t digit_number{}; |
||||
}; |
||||
|
||||
void cropROI(const Mat &_src, Mat &_dst, const std::vector<Point2f> &rect); |
||||
|
||||
void fillCounter(const std::vector<uchar> &row, uint start, Counter &counter); |
||||
|
||||
constexpr static uint INTEGER_MATH_SHIFT = 8; |
||||
constexpr static uint PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT; |
||||
|
||||
uint patternMatch(const Counter &counters, const std::vector<int> &pattern, uint maxIndividual); |
||||
} |
||||
} // namespace cv
|
||||
|
||||
#endif // OPENCV_BARCODE_ABS_DECODER_HPP
|
@ -0,0 +1,195 @@ |
||||
// 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.
|
||||
// Modified from ZXing. Copyright ZXing authors.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
|
||||
#include "../../precomp.hpp" |
||||
#include "hybrid_binarizer.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
|
||||
#define CLAMP(x, x1, x2) x < (x1) ? (x1) : ((x) > (x2) ? (x2) : (x)) |
||||
|
||||
// This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
|
||||
// So this is the smallest dimension in each axis we can accept.
|
||||
constexpr static int BLOCK_SIZE_POWER = 3; |
||||
constexpr static int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
|
||||
constexpr static int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11
|
||||
constexpr static int MINIMUM_DIMENSION = BLOCK_SIZE * 5; |
||||
constexpr static int MIN_DYNAMIC_RANGE = 24; |
||||
|
||||
void |
||||
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height, |
||||
const Mat &black_points, Mat &dst) |
||||
{ |
||||
int maxYOffset = height - BLOCK_SIZE; |
||||
int maxXOffset = width - BLOCK_SIZE; |
||||
for (int y = 0; y < sub_height; y++) |
||||
{ |
||||
int yoffset = y << BLOCK_SIZE_POWER; |
||||
if (yoffset > maxYOffset) |
||||
{ |
||||
yoffset = maxYOffset; |
||||
} |
||||
int top = CLAMP(y, 2, sub_height - 3); |
||||
for (int x = 0; x < sub_width; x++) |
||||
{ |
||||
int xoffset = x << BLOCK_SIZE_POWER; |
||||
if (xoffset > maxXOffset) |
||||
{ |
||||
xoffset = maxXOffset; |
||||
} |
||||
int left = CLAMP(x, 2, sub_width - 3); |
||||
int sum = 0; |
||||
const auto *black_row = black_points.ptr<uchar>(top - 2); |
||||
for (int z = 0; z <= 4; z++) |
||||
{ |
||||
sum += black_row[left - 2] + black_row[left - 1] + black_row[left] + black_row[left + 1] + |
||||
black_row[left + 2]; |
||||
black_row += black_points.cols; |
||||
} |
||||
int average = sum / 25; |
||||
int temp_y = 0; |
||||
|
||||
auto *ptr = dst.ptr<uchar>(yoffset, xoffset); |
||||
for (int offset = yoffset * width + xoffset; temp_y < 8; offset += width) |
||||
{ |
||||
for (int temp_x = 0; temp_x < 8; ++temp_x) |
||||
{ |
||||
*(ptr + temp_x) = (luminances[offset + temp_x] & 255) <= average ? 0 : 255; |
||||
} |
||||
++temp_y; |
||||
ptr += width; |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height) |
||||
{ |
||||
int maxYOffset = height - BLOCK_SIZE; |
||||
int maxXOffset = width - BLOCK_SIZE; |
||||
Mat black_points(Size(sub_width, sub_height), CV_8UC1); |
||||
for (int y = 0; y < sub_height; y++) |
||||
{ |
||||
int yoffset = y << BLOCK_SIZE_POWER; |
||||
if (yoffset > maxYOffset) |
||||
{ |
||||
yoffset = maxYOffset; |
||||
} |
||||
for (int x = 0; x < sub_width; x++) |
||||
{ |
||||
int xoffset = x << BLOCK_SIZE_POWER; |
||||
if (xoffset > maxXOffset) |
||||
{ |
||||
xoffset = maxXOffset; |
||||
} |
||||
int sum = 0; |
||||
int min = 0xFF; |
||||
int max = 0; |
||||
for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) |
||||
{ |
||||
for (int xx = 0; xx < BLOCK_SIZE; xx++) |
||||
{ |
||||
int pixel = luminances[offset + xx] & 0xFF; |
||||
sum += pixel; |
||||
// still looking for good contrast
|
||||
if (pixel < min) |
||||
{ |
||||
min = pixel; |
||||
} |
||||
if (pixel > max) |
||||
{ |
||||
max = pixel; |
||||
} |
||||
} |
||||
// short-circuit min/max tests once dynamic range is met
|
||||
if (max - min > MIN_DYNAMIC_RANGE) |
||||
{ |
||||
// finish the rest of the rows quickly
|
||||
for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) |
||||
{ |
||||
for (int xx = 0; xx < BLOCK_SIZE; xx++) |
||||
{ |
||||
sum += luminances[offset + xx] & 0xFF; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// The default estimate is the average of the values in the block.
|
||||
int average = sum >> (BLOCK_SIZE_POWER * 2); |
||||
if (max - min <= MIN_DYNAMIC_RANGE) |
||||
{ |
||||
// If variation within the block is low, assume this is a block with only light or only
|
||||
// dark pixels. In that case we do not want to use the average, as it would divide this
|
||||
// low contrast area into black and white pixels, essentially creating data out of noise.
|
||||
//
|
||||
// The default assumption is that the block is light/background. Since no estimate for
|
||||
// the level of dark pixels exists locally, use half the min for the block.
|
||||
average = min / 2; |
||||
|
||||
if (y > 0 && x > 0) |
||||
{ |
||||
// Correct the "white background" assumption for blocks that have neighbors by comparing
|
||||
// the pixels in this block to the previously calculated black points. This is based on
|
||||
// the fact that dark barcode symbology is always surrounded by some amount of light
|
||||
// background for which reasonable black point estimates were made. The bp estimated at
|
||||
// the boundaries is used for the interior.
|
||||
|
||||
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
|
||||
int averageNeighborBlackPoint = |
||||
(black_points.at<uchar>(y - 1, x) + (2 * black_points.at<uchar>(y, x - 1)) + |
||||
black_points.at<uchar>(y - 1, x - 1)) / 4; |
||||
if (min < averageNeighborBlackPoint) |
||||
{ |
||||
average = averageNeighborBlackPoint; |
||||
} |
||||
} |
||||
} |
||||
black_points.at<uchar>(y, x) = (uchar) average; |
||||
} |
||||
} |
||||
return black_points; |
||||
|
||||
} |
||||
|
||||
|
||||
void hybridBinarization(const Mat &src, Mat &dst) |
||||
{ |
||||
int width = src.cols; |
||||
int height = src.rows; |
||||
|
||||
if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) |
||||
{ |
||||
std::vector<uchar> luminances(src.begin<uchar>(), src.end<uchar>()); |
||||
|
||||
int sub_width = width >> BLOCK_SIZE_POWER; |
||||
if ((width & BLOCK_SIZE_MASK) != 0) |
||||
{ |
||||
sub_width++; |
||||
} |
||||
|
||||
int sub_height = height >> BLOCK_SIZE_POWER; |
||||
if ((height & BLOCK_SIZE_MASK) != 0) |
||||
{ |
||||
sub_height++; |
||||
} |
||||
|
||||
Mat black_points = calculateBlackPoints(luminances, sub_width, sub_height, width, height); |
||||
|
||||
dst.create(src.size(), src.type()); |
||||
calculateThresholdForBlock(luminances, sub_width, sub_height, width, height, black_points, dst); |
||||
} |
||||
else |
||||
{ |
||||
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY); |
||||
} |
||||
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
// 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.
|
||||
// Modified from ZXing. Copyright ZXing authors.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License").
|
||||
|
||||
#ifndef OPENCV_BARCODE_HYBRID_BINARIZER_HPP |
||||
#define OPENCV_BARCODE_HYBRID_BINARIZER_HPP |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
void hybridBinarization(const Mat &src, Mat &dst); |
||||
|
||||
void |
||||
calculateThresholdForBlock(const std::vector<uchar> &luminances, int sub_width, int sub_height, int width, int height, |
||||
const Mat &black_points, Mat &dst); |
||||
|
||||
Mat calculateBlackPoints(std::vector<uchar> luminances, int sub_width, int sub_height, int width, int height); |
||||
} |
||||
} |
||||
#endif // OPENCV_BARCODE_HYBRID_BINARIZER_HPP
|
@ -0,0 +1,77 @@ |
||||
// 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.
|
||||
//
|
||||
// Tencent is pleased to support the open source community by making WeChat QRCode available.
|
||||
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
// Modified by darkliang wangberlinT
|
||||
|
||||
#include "../../precomp.hpp" |
||||
#include "super_scale.hpp" |
||||
|
||||
#ifdef HAVE_OPENCV_DNN |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
constexpr static float MAX_SCALE = 4.0f; |
||||
|
||||
int SuperScale::init(const std::string &proto_path, const std::string &model_path) |
||||
{ |
||||
srnet_ = dnn::readNetFromCaffe(proto_path, model_path); |
||||
net_loaded_ = true; |
||||
return 0; |
||||
} |
||||
|
||||
void SuperScale::processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size) |
||||
{ |
||||
scale = min(scale, MAX_SCALE); |
||||
if (scale > .0 && scale < 1.0) |
||||
{ // down sample
|
||||
resize(src, dst, Size(), scale, scale, INTER_AREA); |
||||
} |
||||
else if (scale > 1.5 && scale < 2.0) |
||||
{ |
||||
resize(src, dst, Size(), scale, scale, INTER_CUBIC); |
||||
} |
||||
else if (scale >= 2.0) |
||||
{ |
||||
int width = src.cols; |
||||
int height = src.rows; |
||||
if (use_sr && (int) sqrt(width * height * 1.0) < sr_max_size && net_loaded_) |
||||
{ |
||||
superResolutionScale(src, dst); |
||||
if (scale > 2.0) |
||||
{ |
||||
processImageScale(dst, dst, scale / 2.0f, use_sr); |
||||
} |
||||
} |
||||
else |
||||
{ resize(src, dst, Size(), scale, scale, INTER_CUBIC); } |
||||
} |
||||
} |
||||
|
||||
int SuperScale::superResolutionScale(const Mat &src, Mat &dst) |
||||
{ |
||||
Mat blob; |
||||
dnn::blobFromImage(src, blob, 1.0 / 255, Size(src.cols, src.rows), {0.0f}, false, false); |
||||
|
||||
srnet_.setInput(blob); |
||||
auto prob = srnet_.forward(); |
||||
|
||||
dst = Mat(prob.size[2], prob.size[3], CV_8UC1); |
||||
|
||||
for (int row = 0; row < prob.size[2]; row++) |
||||
{ |
||||
const float *prob_score = prob.ptr<float>(0, 0, row); |
||||
auto *dst_row = dst.ptr<uchar>(row); |
||||
for (int col = 0; col < prob.size[3]; col++) |
||||
{ |
||||
dst_row[col] = saturate_cast<uchar>(prob_score[col] * 255.0f); |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
} // namespace barcode
|
||||
} // namespace cv
|
||||
|
||||
#endif // HAVE_OPENCV_DNN
|
@ -0,0 +1,69 @@ |
||||
/// 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.
|
||||
//
|
||||
// Tencent is pleased to support the open source community by making WeChat QRCode available.
|
||||
// Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
|
||||
|
||||
#ifndef OPENCV_BARCODE_SUPER_SCALE_HPP |
||||
#define OPENCV_BARCODE_SUPER_SCALE_HPP |
||||
|
||||
#ifdef HAVE_OPENCV_DNN |
||||
|
||||
#include "opencv2/dnn.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
class SuperScale |
||||
{ |
||||
public: |
||||
SuperScale() = default; |
||||
|
||||
~SuperScale() = default; |
||||
|
||||
int init(const std::string &proto_path, const std::string &model_path); |
||||
|
||||
void processImageScale(const Mat &src, Mat &dst, float scale, const bool &use_sr, int sr_max_size = 160); |
||||
|
||||
private: |
||||
dnn::Net srnet_; |
||||
bool net_loaded_ = false; |
||||
|
||||
int superResolutionScale(const cv::Mat &src, cv::Mat &dst); |
||||
}; |
||||
|
||||
} // namespace barcode
|
||||
} // namespace cv
|
||||
|
||||
#else // HAVE_OPENCV_DNN
|
||||
|
||||
#include "opencv2/core.hpp" |
||||
#include "opencv2/core/utils/logger.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
class SuperScale |
||||
{ |
||||
public: |
||||
int init(const std::string &, const std::string &) |
||||
{ |
||||
return 0; |
||||
} |
||||
void processImageScale(const Mat &src, Mat &dst, float scale, const bool & isEnabled, int) |
||||
{ |
||||
if (isEnabled) |
||||
{ |
||||
CV_LOG_WARNING(NULL, "objdetect/barcode: SuperScaling disabled - OpenCV has been built without DNN support"); |
||||
} |
||||
resize(src, dst, Size(), scale, scale, INTER_CUBIC); |
||||
} |
||||
}; |
||||
|
||||
} // namespace barcode
|
||||
} // namespace cv
|
||||
|
||||
#endif // !HAVE_OPENCV_DNN
|
||||
|
||||
#endif // OPENCV_BARCODE_SUPER_SCALE_HPP
|
@ -0,0 +1,36 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#include "../../precomp.hpp" |
||||
#include "utils.hpp" |
||||
#include "hybrid_binarizer.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
|
||||
void sharpen(const Mat &src, const Mat &dst) |
||||
{ |
||||
Mat blur; |
||||
GaussianBlur(src, blur, Size(0, 0), 25); |
||||
addWeighted(src, 2, blur, -1, -20, dst); |
||||
} |
||||
|
||||
void binarize(const Mat &src, Mat &dst, BinaryType mode) |
||||
{ |
||||
switch (mode) |
||||
{ |
||||
case OTSU: |
||||
threshold(src, dst, 155, 255, THRESH_OTSU + THRESH_BINARY); |
||||
break; |
||||
case HYBRID: |
||||
hybridBinarization(src, dst); |
||||
break; |
||||
default: |
||||
CV_Error(Error::StsNotImplemented, "This binary type is not yet implemented"); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,26 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#ifndef OPENCV_BARCODE_UTILS_HPP |
||||
#define OPENCV_BARCODE_UTILS_HPP |
||||
|
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
enum BinaryType |
||||
{ |
||||
OTSU = 0, HYBRID = 1 |
||||
}; |
||||
static constexpr BinaryType binary_types[] = {OTSU, HYBRID}; |
||||
|
||||
void sharpen(const Mat &src, const Mat &dst); |
||||
|
||||
void binarize(const Mat &src, Mat &dst, BinaryType mode); |
||||
|
||||
} |
||||
} |
||||
|
||||
#endif // OPENCV_BARCODE_UTILS_HPP
|
@ -0,0 +1,92 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#include "../precomp.hpp" |
||||
#include "ean13_decoder.hpp" |
||||
|
||||
// three digit decode method from https://baike.baidu.com/item/EAN-13
|
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
static constexpr size_t EAN13BITS_NUM = 95; |
||||
static constexpr size_t EAN13DIGIT_NUM = 13; |
||||
// default thought that mat is a matrix after binary-transfer.
|
||||
/**
|
||||
* decode EAN-13 |
||||
* @prama: data: the input array, |
||||
* @prama: start: the index of start order, begin at 0, max-value is data.size()-1 |
||||
* it scan begin at the data[start] |
||||
*/ |
||||
Result Ean13Decoder::decode(const vector<uchar> &data) const |
||||
{ |
||||
string result; |
||||
char decode_result[EAN13DIGIT_NUM + 1]{'\0'}; |
||||
if (data.size() < EAN13BITS_NUM) |
||||
{ |
||||
return Result("Wrong Size", Result::BARCODE_NONE); |
||||
} |
||||
pair<uint, uint> pattern; |
||||
if (!findStartGuardPatterns(data, pattern)) |
||||
{ |
||||
return Result("Begin Pattern Not Found", Result::BARCODE_NONE); |
||||
} |
||||
uint start = pattern.second; |
||||
Counter counter(vector<int>{0, 0, 0, 0}); |
||||
size_t end = data.size(); |
||||
int first_char_bit = 0; |
||||
// [1,6] are left part of EAN, [7,12] are right part, index 0 is calculated by left part
|
||||
for (int i = 1; i < 7 && start < end; ++i) |
||||
{ |
||||
int bestMatch = decodeDigit(data, counter, start, get_AB_Patterns()); |
||||
if (bestMatch == -1) |
||||
{ |
||||
return Result("Decode Error", Result::BARCODE_NONE); |
||||
} |
||||
decode_result[i] = static_cast<char>('0' + bestMatch % 10); |
||||
start = counter.sum + start; |
||||
first_char_bit += (bestMatch >= 10) << i; |
||||
} |
||||
decode_result[0] = static_cast<char>(FIRST_CHAR_ARRAY()[first_char_bit >> 2] + '0'); |
||||
// why there need >> 2?
|
||||
// first, the i in for-cycle is begin in 1
|
||||
// second, the first i = 1 is always
|
||||
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size())); |
||||
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern)) |
||||
{ |
||||
return Result("Middle Pattern Not Found", Result::BARCODE_NONE); |
||||
|
||||
} |
||||
start = pattern.second; |
||||
for (int i = 0; i < 6 && start < end; ++i) |
||||
{ |
||||
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns()); |
||||
if (bestMatch == -1) |
||||
{ |
||||
return Result("Decode Error", Result::BARCODE_NONE); |
||||
} |
||||
decode_result[i + 7] = static_cast<char>('0' + bestMatch); |
||||
start = counter.sum + start; |
||||
} |
||||
Counter end_counter(vector<int>(BEGIN_PATTERN().size())); |
||||
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern)) |
||||
{ |
||||
return Result("End Pattern Not Found", Result::BARCODE_NONE); |
||||
} |
||||
result = string(decode_result); |
||||
if (!isValid(result)) |
||||
{ |
||||
return Result("Wrong: " + result.append(string(EAN13DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE); |
||||
} |
||||
return Result(result, Result::BARCODE_EAN_13); |
||||
} |
||||
|
||||
Ean13Decoder::Ean13Decoder() |
||||
{ |
||||
this->bits_num = EAN13BITS_NUM; |
||||
this->digit_number = EAN13DIGIT_NUM; |
||||
} |
||||
} |
||||
} |
@ -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.
|
||||
// Copyright (c) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#ifndef OPENCV_BARCODE_EAN13_DECODER_HPP |
||||
#define OPENCV_BARCODE_EAN13_DECODER_HPP |
||||
|
||||
#include "upcean_decoder.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
//extern struct EncodePair;
|
||||
using std::string; |
||||
using std::vector; |
||||
using std::pair; |
||||
|
||||
|
||||
class Ean13Decoder : public UPCEANDecoder |
||||
{ |
||||
public: |
||||
Ean13Decoder(); |
||||
|
||||
~Ean13Decoder() override = default; |
||||
|
||||
protected: |
||||
Result decode(const vector<uchar> &data) const override; |
||||
}; |
||||
} |
||||
} // namespace cv
|
||||
#endif // OPENCV_BARCODE_EAN13_DECODER_HPP
|
@ -0,0 +1,79 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#include "../precomp.hpp" |
||||
#include "ean8_decoder.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
static constexpr size_t EAN8BITS_NUM = 70; |
||||
static constexpr size_t EAN8DIGIT_NUM = 8; |
||||
|
||||
Result Ean8Decoder::decode(const vector<uchar> &data) const |
||||
{ |
||||
std::string result; |
||||
char decode_result[EAN8DIGIT_NUM + 1]{'\0'}; |
||||
if (data.size() < EAN8BITS_NUM) |
||||
{ |
||||
return Result("Wrong Size", Result::BARCODE_NONE); |
||||
} |
||||
pair<uint, uint> pattern; |
||||
if (!findStartGuardPatterns(data, pattern)) |
||||
{ |
||||
return Result("Begin Pattern Not Found", Result::BARCODE_NONE); |
||||
} |
||||
uint start = pattern.second; |
||||
Counter counter(vector<int>{0, 0, 0, 0}); |
||||
size_t end = data.size(); |
||||
for (int i = 0; i < 4 && start < end; ++i) |
||||
{ |
||||
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns()); |
||||
if (bestMatch == -1) |
||||
{ |
||||
return Result("Decode Error", Result::BARCODE_NONE); |
||||
} |
||||
decode_result[i] = static_cast<char>('0' + bestMatch % 10); |
||||
start = counter.sum + start; |
||||
} |
||||
|
||||
Counter middle_counter(vector<int>(MIDDLE_PATTERN().size())); |
||||
|
||||
if (!findGuardPatterns(data, start, true, MIDDLE_PATTERN(), middle_counter, pattern)) |
||||
{ |
||||
return Result("Middle Pattern Not Found", Result::BARCODE_NONE); |
||||
} |
||||
|
||||
start = pattern.second; |
||||
for (int i = 0; i < 4 && start < end; ++i) |
||||
{ |
||||
int bestMatch = decodeDigit(data, counter, start, get_A_or_C_Patterns()); |
||||
if (bestMatch == -1) |
||||
{ |
||||
return Result("Decode Error", Result::BARCODE_NONE); |
||||
} |
||||
decode_result[i + 4] = static_cast<char>('0' + bestMatch); |
||||
start = counter.sum + start; |
||||
} |
||||
Counter end_counter(vector<int>(BEGIN_PATTERN().size())); |
||||
if (!findGuardPatterns(data, start, false, BEGIN_PATTERN(), end_counter, pattern)) |
||||
{ |
||||
return Result("End Pattern Not Found", Result::BARCODE_NONE); |
||||
} |
||||
result = string(decode_result); |
||||
if (!isValid(result)) |
||||
{ |
||||
return Result("Wrong: " + result.append(string(EAN8DIGIT_NUM - result.size(), ' ')), Result::BARCODE_NONE); |
||||
} |
||||
return Result(result, Result::BARCODE_EAN_8); |
||||
} |
||||
|
||||
Ean8Decoder::Ean8Decoder() |
||||
{ |
||||
this->digit_number = EAN8DIGIT_NUM; |
||||
this->bits_num = EAN8BITS_NUM; |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,32 @@ |
||||
// 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) 2020-2021 darkliang wangberlinT Certseeds
|
||||
|
||||
#ifndef OPENCV_BARCODE_EAN8_DECODER_HPP |
||||
#define OPENCV_BARCODE_EAN8_DECODER_HPP |
||||
|
||||
#include "upcean_decoder.hpp" |
||||
|
||||
namespace cv { |
||||
namespace barcode { |
||||
|
||||
using std::string; |
||||
using std::vector; |
||||
using std::pair; |
||||
|
||||
class Ean8Decoder : public UPCEANDecoder |
||||
{ |
||||
|
||||
public: |
||||
Ean8Decoder(); |
||||
|
||||
~Ean8Decoder() override = default; |
||||
|
||||
protected: |
||||
Result decode(const vector<uchar> &data) const override; |
||||
}; |
||||
} |
||||
} |
||||
|
||||
#endif // OPENCV_BARCODE_EAN8_DECODER_HPP
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue