mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
f968fb969f
* experimental new arithmetics; work-in-progress * continue working on new-gen arithmetic expressions * * improved performance of the new add on small arrays * added sub * extended tests * fixed potential bug when adding multi-channel array and a single-channel scalar * improved const handling * * added copyMask * added mul/dev (without scale so far) * * accelerated mul * addedd scale to mul and div * * done substantial refactoring; however a few more rounds of refactoring are ahead. * added min, max, absdiff, addweighted. * improved performance of the new arithmetic functions, but some of them are still slow, e.g. operations with mask have some bugs (that affect speed, not accuracy). * * further (significantly) accelerated several functions, especially on small arrays: mul, binary ops with mask * further polished the new arithmetic engine * started integration of the new element-wise arithmetic engine into core * big step forward. We now use the new engine inside cv::add, subtract, multiply, divide, absdiff, min and max. * big progress: * added bitwise operations * fixed and accelerated compare * ported regression tests to test new broadcasting behaviour of arithmetic functions * lot's of improvements in compare, divide, addWeighted! * lot's of small and big performance improvements in the new arithmetics * * some more optimizations; parsing texpr-expressions is now faster as well * port new_arithm to Linux/x86: dispatch guards, scalar-Mat compat fallback, dnn shape-contract fixes Core: - arithm.simd.hpp: CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY guards (the file is included once per dispatched mode on x86), vx_load_expand instead of the 128-bit v_load_expand, VTraits::vlanes() instead of ::nlanes - arithm.cpp/precomp.hpp: compat fallback for scalar-like Mat operands (1x1, 1xcn/cnx1, 4x1 CV_64F - java/python tuples, operator-(Mat, Matx)): treated as a per-channel scalar ONLY when the shapes are not broadcast-compatible, so every valid numpy-style broadcast keeps its meaning and calls that would otherwise throw get the 4.x semantics DNN (fallout of the stricter shape semantics, found by the new engine): - dict.hpp: DictValue relied on fresh AutoBuffer having size()==fixed_size; allocate explicitly - batch_norm: weights_/bias_ are 1-D [n] now; 0/1-D forward runs on exact-shape 1-D views - net_impl2: extend the post-forward sanity check to non-temp outputs - a layer that reallocates its preallocated output tensor now fails loudly instead of silently detaching the result from the graph - LSTM/LSTM2 batchwise (layout=1): getMemoryShapes now matches what forward() writes (ONNX: Y=(batch,seq,dirs,hid), Yh/Yc=(batch,dirs,hid)); forward assembles seq-major results in a local buffer and transposes INTO the preallocated outputs in place Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * u8/s8 multiply: exact integer SIMD path on non-FP16 builds (3-10x vs 5.x) The unit-scale branch of vecBinaryKernel already supported a separate work-vector type Wvec1 (used by the ARM f16 build and by u16/s16 everywhere), but on x86 the u8/s8 same-type multiply still went through the f32 hub. Route it through v_uint16/v_int16: products of 8-bit values fit exactly (255^2 < 2^16), the saturating pack on store gives bit-exact results at half the vector traffic. Also fix a latent kernel bug this exposed: the unit-scale branch stepped by Wvec's lane count while loading/storing Wvec1 vectors. All previous Wvec1 instantiations had equal lane counts, but u8's v_uint16 has 2x the lanes of v_float32 - the pairs overlapped (50% redundant work) and the tail backoff could write VECSZ bytes past the row end. The branch now derives its step, offsets and tail condition from Wvec1 itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * vecBinaryKernel: constexpr Op::useScalar instead of a runtime-only scale check Every binary op functor now declares whether it consumes the scale scalar (params[0]): true only for mul and the two div variants. Ops that ignore it (add/sub/min/max/absdiff) take the fast 2-arg branch unconditionally - the 'scalar == 1' check used to fail for them (their params[0] is 0), sending them through the preproc branch, and the condition now folds at compile time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * restore cv::hal::mul8u as a wrapper over the element-wise engine The symbol is still declared in core/hal/hal.hpp and called directly by external code (the G-API fluid backend in opencv_contrib), but its implementation went away with the old arithm kernels. Forward it to getMulFunc(CV_8U, CV_8U) - with scale==1 it lands on the new exact integer SIMD path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * silence every new-arithm warning reported by CI (ARM64/Mac) and gcc 15 - arithm.cpp: bitwise_op_ocl and the actualScalarDepth/coerceTypes helpers are consumed only by the OpenCL paths - guard them with HAVE_OPENCL; haveScalar in cv::compare is read only inside CV_OCL_RUN - CV_UNUSED for OpenCL-less builds - arithm_expr.hpp: declare getBitwiseFunc/getNotFunc/getAddWeightedFunc next to the other per-op entry points (-Wmissing-prototypes in arithm.dispatch.cpp) - arithm.simd.hpp: define CV_SIMD_16F to 0 when FP16 SIMD is absent (-Wundef); {}-init the expandScalar staging buffers (-Wmaybe-uninitialized: they are fully written before use, but the compiler cannot prove it with runtime vector widths); rename the compare kernel's lambda parameter (-Wshadow) - arithm_expr.cpp: rename the exec tile-lambda's hot-field locals that shadowed TExpr members and outer locals (-Wshadow) - test_new_arithm_extensive.cpp: rename the name-generator lambdas' parameter shadowing the INSTANTIATE macro's own (-Wshadow), drop an unused variable Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * restore 4.x scalar semantics for the bindings' 4x1 CV_64F Scalar columns The python/java bindings materialize numbers and tuples as a 2-D 4x1 CV_64F Mat - or UMat, when the call carries UMat arguments. Three CI-reported python failures came from those pseudo-scalars reaching the engine as arrays: - absdiff(int_arr, 0): the (4,1) column is broadcast-COMPATIBLE with a 1-D array, so numpy semantics silently won - an outer-product f64 result instead of the int per-channel-scalar one; - subtract(u8 4x8x4, (40,)): same, by the rows==4 coincidence; - multiply(UMat, 2., dst=UMat): the scalar arrives as a UMAT, which the scalar detection did not recognize at all. isScalarArg now treats the exact bindings shape - 2-D 4x1 CV_64F single-channel Mat/UMat against a <=4-channel array - as a scalar UNCONDITIONALLY (a 1-D [4] array has dims==1 and still broadcasts). One exception, decided in arithm_op: when the partner is itself a tiny scalar-shaped array, both are honest data and ride the broadcast (compare(Mat 4x1, Mat 1x1) - issue #8999 - stays elementwise). Small-array discipline, this all runs per engine call: the probes read Mat/UMat fields directly (rows == 4 alone rejects almost everything, no _InputArray getter dispatch), and a UMAT scalar's 32 bytes are copied into a caller-stack buffer - no heap, no getMat mapping. Measured: no latency change on 4x4/16x16 element-wise calls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * cv::texpr: std::string_view -> const std::string& in the public API string_view in an exported signature breaks some CUDA toolchain builds, and for short expression strings the difference is immaterial (SSO, parsed once). The parser internals keep string_view - the argument converts implicitly. Also drop the now-unused <string_view> include from cvstd.hpp, so the header does not reach every nvcc TU. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * compare boundary-rewrite: fixed 4-slot kind/bound arrays instead of AutoBuffers A CONST operand is capped at 4 channels (addConst), so the per-channel kind/bound staging needs no dynamic buffers - plain int[4]/double[4], with a CV_Assert on the contract. This is also what gcc's -Wmaybe-uninitialized was flagging (it could not see the AutoBuffer's inline storage get filled). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * element-wise engine: unary math kernels (sqrt/exp/log/sin/cos/tanh/erf/relu) + select New dispatched pair math.simd.hpp / math.dispatch.cpp - the unary/ternary sibling of arithm.simd.hpp: - vecUnaryKernel: T -> T over f16/bf16/f32/f64 on top of the intrin_math primitives (v_exp/v_log/v_sin/v_cos/v_sqrt/v_erf/v_max). f32/f64 compute natively, f16/bf16 ride the f32 hub inside the kernel (vx_load_pair_as / v_store_pair_as) - no materialized casts. Continuity collapse + the halide right-edge backoff, suppressed in-place (it would re-apply Op to already-written values). tanh = (e^2x-1)/(e^2x+1) with the input clamped to +/-10 (f32) / +/-20 (f64) - unclamped saturation hits inf/inf = NaN. erf has no f64 SIMD primitive: std::erf per lane. - selectKernel(mask, x, y): 1-byte mask expanded to lane width and tested against zero in the INTEGER domain (immune to DAZ/FTZ), branches of any depth by element size, broadcast branches supported. - emitUnary: math over a float input is T -> T now (f16 in -> f16 out, native kernel when input and result depths match); integer inputs still compute in the float domain and land in f32. - emitTernary/select: literal branches are typed via typedConstFrom (an OP_CAST of a depth-less flex const crashed); a non-1-byte mask is normalized by an explicit "mask != 0" compare, never a value cast. texpr already parsed the function names - they now execute. Tests: per-depth accuracy of all 8 ops against the double std:: reference, integer input, in-place, select over 4 depths / const branch / float mask. Perf vs the classic kernels (1920x1080 f32, 16 threads, AVX2): exp 3.7x, log 2.6x, sqrt 5.7x faster; polarToCart expressed as (r*cos(a), r*sin(a)) 4.0x. Accuracy improves too (max rel err vs f64 reference): exp 8.1e-8 vs 2.1e-7, log 8.0e-8 vs 1.5e-7. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * engine: OP_COPY_MASK folded into OP_SELECT; selectKernel moved to arithm.simd.hpp copyMask(dst, mask, src) is select(mask, src, dst) - one masking primitive instead of two. The compiler emits the masked-op tail as addInsn(OP_SELECT, mask, r, out, out): the output slot rides as both arg2 and the result, so unmasked elements are preserved by reading them back through the b-branch. OP_COPY_MASK, copyMaskKernel and getCopyMaskFunc are gone. selectKernel (moved from math.simd.hpp to arithm.simd.hpp) inherits every copyMaskKernel optimization: - the interleaved multichannel fast path (2..4 channels under a per-pixel mask: expand the mask once per VECSZ rows, v_store_interleave across lanes); - the per-row scalar path with the row-skip when the selected source row IS dst (the "leave the output untouched" half of copyMask); - plus the select-specific ones: branch broadcasts (stepx == 0) and the right-edge tail backoff under dst-aliases-a-branch - safe because re-running select over already-blended elements is idempotent; only dst == mask keeps the backoff off (the store would rewrite mask bytes before the re-read). Masked-add perf is on par with the old copyMask (1280x720, 1 thread: 8UC3 204 -> 198 us, 32FC3 1395 -> 1344, 8UC1/32FC1 within noise). Full core suite 24117 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * engine: dedicated vectorized pow kernel (moved from arithm to math.simd.hpp) pow was the last scalar-only binary op (scalarBinaryKernel + std::pow, f32/f64 only). The new powKernel keeps exact std::pow semantics and is T x T -> T over all four float depths (f16/bf16 via the f32 hub): - scalar exponent (the dominant call shape - texpr literals ride as 0-dim broadcast consts) is dispatched PER ROW to the special cases: y==2 -> x*x, y==3 -> x*x*x, y==0.5 -> v_sqrt, y==1 -> copy, y==0 -> fill 1; - everything else - including a per-element exponent array - runs the general vectorized exp(y * log(x)) path, valid for x > 0; a vector pair containing any x <= 0 lane falls back to scalar std::pow for that pair (v_check_any), which preserves every std::pow subtlety: signed results for integer y on negative bases, NaN for fractional y, the x == 0 family; - no right-edge tail backoff: pow is not idempotent, in-place calls finish rows in the scalar tail. Perf vs the classic cv::pow (1920x1080 f32, 16 threads, AVX2): p=2 1.4x (classic special-cases it too), p=3 6.1x, p=0.5 6.3x, fractional p 4.5x with slightly better accuracy (6.1e-7 vs 7.4e-7 max rel err). Tests: exponent sweep 2/3/0.5/1/0/2.5/-1.5 vs the double std::pow reference on f32/f64, negative bases (exact signed cubes, NaN for fractional), array exponent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * powKernel: halide right-edge tail backoff in every SIMD loop Same shape as vecBinaryKernel: the final partial vector re-processes [width - VECSZ*2, width) instead of finishing scalar, suppressed when dst aliases an input (pow is not idempotent - the overlap region must be recomputed from an untouched source, which the no-alias case guarantees). Modest measured win (~2% on ROI rows for the special-cased exponents; the general path tail was already cheap - modern libm powf is fast), no regressions; mainly aligns the kernel with the house style, where every SIMD loop ends vector-wide. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix MSVC 2019 C2975: function-local constexpr as a template argument inside a lambda MSVC 2019 loses the constexpr-ness of function-local constants (LOCAL_OPS, MAX_DIMS, ...) when they are used as template arguments inside a lambda body (AutoBuffer<Slice, LOCAL_OPS> / std::array<int, MAX_DIMS> in the parallel bodies of BroadcastOp::run and TExpr::exec). Hoist them to namespace scope - no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ocl_arithm_op: route 16U multiply to the CPU engine on Apple OpenCL The Apple OpenCL driver miscompiles the 16U multiply kernel: products near the top of the u16 range come back wrapped instead of saturated (CPU vs GPU NORM_INF up to 65535 in OCL_Arithm/Mul.Mat CV_16U cases). The same arithm.cl kernel is correct on Intel NEO and NVIDIA drivers - verified not to reproduce on Linux/Intel iGPU - so gate the decline to __APPLE__ only; the CPU engine computes 16u multiply exactly (integer SIMD path). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * engine: neg/abs as compositions, clamp kernel, ** and ?: operators, abs(a-b) peephole - OP_NEG and OP_ABS need no kernels: neg = sub(0, a), abs = absdiff(a, 0) - including the engine absdiff auto-type rule (signed |a| lands in the UNSIGNED type of the same width: |SHRT_MIN| fits u16 exactly instead of saturating; NB the public cv::absdiff auto depth keeps the source type for 4.x compatibility - values agree, the depth rule is the engine own). - peephole: abs(x - y) rewrites to absdiff(x, y) ALWAYS. On integers the literal semantics differ (the subtract saturates first: u8 gives max(x-y, 0)), but whoever writes abs(a - b) means absdiff - we deliberately hand out the useful semantics instead of the saturation artifact. The just- emitted OP_SUB is retired via the moveToOutput manoeuvre, so the program shrinks to the single absdiff instruction. abs(x), abs(x - 0) and absdiff(x, 0) all give one result. - OP_CLAMP kernel (arithm.simd.hpp): v_min(v_max(x, lo), hi) over u8/s8/u16/s16/u32/s32/f32 (+f64 with 64-bit SIMD), scalar f16/bf16/64-bit ints; lo/hi may broadcast (the common clamp(img, a, b) shape) or be full arrays; the tail backoff stays on under dst-aliases-x (clamp is idempotent). emitTernary types literal bounds via typedConstFrom (same flex-const crash select had) and keeps the auto result type pinned to x. - parser: "a ** b" == pow(a, b), precedence above * /, RIGHT-associative (a ** 2 ** 3 == a ** 8); "cond ? a : b" == select(cond, a, b), precedence below everything, right-associative chains (f1 ? a : f2 ? b : c) work without parentheses. parseTernary() is the expression entry point now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * cv::exp/log/sqrt on the engine via math_op; hal functions wrapped as engine kernels math_op is the master function of the unary math family (the arithm_op analogue): same-shape same-type output over f16/bf16/f32/f64 (classic exp/log accepted f32/f64 only - the half floats are new), two tiers: - small (<= 100000 elements) and continuous: call the kernel DIRECTLY over the flattened data - no TExpr, no broadcastOp, no parallel_for setup; - everything else: the usual single-instruction program via compile()/exec() (parallelism for large arrays, real steps for ROIs). getMathFunc routes OP_EXP/OP_LOG at f32/f64 through the full cv::hal stack - an external vendor HAL (CALL_HAL), IPP, or the built-in table kernels, whichever is installed - by wrapping hal::exp32f/exp64f/log32f/log64f as engine kernels with the function pointer in TKernel::userdata, the same mechanism castKernel uses for core BinaryFuncs. The engine adds tiling and parallelism on top, so every tier gets the best available scalar-span implementation. v_exp/v_log remain for f16/bf16 (the f32 hub) and the ops hal has no entry points for. v_log_default_32f: the degree-8 polynomial is evaluated by Estrin pairing (4 dependent levels) instead of an 8-FMA Horner chain (~5% on the f16 hub path). An exp64 Taylor-without-division rewrite was tried and benched SLOWER than the Cephes Pade scheme (the evaluation is FMA-throughput-bound, and vdivpd pipelines well enough) - reverted; a table-based reduction is the only way further there. cv::exp f32 (16 threads, AVX2+IPP build), old -> new: 640 elements 0.16 -> 0.15 us, 16k 2.79 -> 2.49, 640x480 47 -> 20, 1920x1080 452 -> 60 us (the old CPU loops were single-threaded); f64 exp 1080p 1295 -> 161 us. No size regresses; small arrays now run at installed-HAL speed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * getMathFunc: IPP tier + raw-HAL probing; the exp/log table kernels are deleted The raw cv_hal_* entry points return int for a reason: without an installed HAL they are stubs returning CV_HAL_ERROR_NOT_IMPLEMENTED. getMathFunc now selects the exp/log implementation in three tiers: 1. HAVE_IPP && ipp::useIPP(): ippsExp/ippsLn through thin int adapters (IPP is not routed through the cv_hal_ hooks, so it needs its own tier); 2. the raw cv_hal_exp32f/... hook, PROBED once with a 1-element call on the safe input 1.0 (cached in magic statics): implemented -> wrapped as an engine kernel with the function pointer in TKernel::userdata; 3. the engine own v_exp/v_log kernels. Whichever wins, the engine adds tiling and parallelism on top. The EXPTAB/LOGTAB table kernels and their tables (~790 lines in mathfuncs_core.simd.hpp + mathfuncs.cpp) are DELETED: they benched within ~15% of v_exp/v_log, not worth a second implementation. The public cv::hal::exp32f/exp64f/log32f/log64f keep their contract - CALL_HAL, then IPP, then the built-in implementation - but the built-in is now the engine vector kernel via ew::mathSpanEngine (one contiguous span, exported from math.dispatch.cpp). All unary math kernels (vec/scalar/hal wrappers, pow) also handle the vertical-broadcast tile (s0y == 0, a row expanded into a matrix): the first row is computed, the rest are memcpy of it - transcendentals cost far more than a row copy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * texpr: hypot(x, y) binary op (alias: mag) hypot = sqrt(x^2 + y^2), NAIVE like cv::magnitude (not the overflow-safe std::hypot), computed in the float work type; kernels for the four float depths only (T x T -> T; integer inputs ride the usual f32-compute + cast). A 10-line EwHypot functor on top of vecBinaryKernel in arithm.simd.hpp - broadcast branches, continuity collapse and the tail backoff come for free. Registered in the parser as both "hypot" (the C/numpy name) and "mag" (the cv::magnitude-flavored alias). A building block for the future cartToPolar. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * texpr: atan2(y, x) binary op - radians, standard C range v_atan2 (arithm.simd.hpp, generic over the universal-intrinsic float vector): the fastAtan2 minimax polynomial from mathfuncs_core v_atan_f32 reworked to plain radians - the 180/pi factor dropped from the coefficients and the C quadrant logic instead of the [0, 360) wrap, so the result matches std::atan2 over (-pi, pi]. Measured absolute accuracy ~1.6e-4 rad. (v_atan_f32 itself is untouched - cv::phase/fastAtan2 keep their degree semantics.) EwAtan2 rides vecBinaryKernel: f16/bf16/f32 through v_atan2 (the f32 hub), f64 through exact scalar std::atan2. arg0 = y, arg1 = x, like std::atan2; float depths only, same emitBinary policy as pow/hypot. Parser name "atan2". Together with hypot this completes the cartToPolar building blocks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix the RISC-V RVV build and two ARM64 warnings The new f64 kernel registrations (hypot, pow, the unary math family) gate on CV_SIMD_64F || CV_SIMD_SCALABLE_64F, but the vx_setall_as(const double*, v_float64&) helper family in arithm.simd.hpp was still CV_SIMD_64F-only - scalable platforms (RVV) have v_float64 with CV_SIMD_64F == 0, so vecBinaryKernel<double, ...> failed to instantiate there. Widen the helper gate to match (verified with a riscv64 rv64gcv cross-build of opencv_core - the engine f64 paths now vectorize on RVV instead of not compiling). cv::exp/cv::log: the depth local is consumed by CV_OCL_RUN only - CV_UNUSED for OpenCL-less builds (ARM64 -Wunused-variable). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * silence the remaining ARM64 gcc warnings - compare boundary-rewrite: {}-init the fixed kind/bound arrays (filled for every channel used below, but gcc cannot prove it across the cn <= 4 loop); - cv::exp/log: [[maybe_unused]] on the depth local (consumed by CV_OCL_RUN only), instead of the CV_UNUSED idiom; - AutoBuffer::reserve: a targeted -Wmaybe-uninitialized suppression around the live-element copy loop - only [0, sz) is read, all written before, but gcc inlining a grow-from-inline-storage chain cannot see that. An annotation for the analyzer, no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * saturating 32-bit add/sub kernels; cv::texpr python binding; two CI warnings - v_add_sat/v_sub_sat for v_int32/v_uint32, local to arithm.simd.hpp for now (the plan is to grow them into proper universal intrinsics later): NEON single-instruction vqadd/vqsub, elsewhere the Hacker Delight bit tricks over universal intrinsics (u32 add is 2 ops: or with the wrapped-compare mask). EwAdd/EwSub overload vec() for the 32-bit lanes and getAddSubFunc routes 32S/32U T->T through vecBinaryKernel instead of the former pure scalar kernel. Semantics unchanged - the scalar int64 tail already saturated; directed boundary tests added (both rails, 0 - INT_MIN, u32 cases, a full-range random block vs an exact int64 reference). 640x480 32S add: 0.48x of 5.x -> parity (memory-bound); 1080p: 6-8x. - cv::texpr becomes CV_EXPORTS_W: python gets cv.texpr(expr, [inputs]) -> tuple of ndarrays, so `res, = cv.texpr(...)` and `mag, ang = cv.texpr(...)` unpacking both work. modules/python/test/test_expr.py covers arithmetic, the fused abs(a-b), casts, broadcasting, ?: and ** operators, math functions vs numpy, clamp, named temporaries, tuple outputs, the one-line cartToPolar and the int32 saturation cases. - warnings: {}-init the parser args array (gcc -Wmaybe-uninitialized on Ubuntu 20/22); the compare short-row block gates sizeof(T) <= 4 as constexpr so the f64 instantiation does not leave set-but-unused locals (gcc 9 -Wunused-but-set-variable). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * arithm_op: direct-kernel fast path for small continuous arrays Building and compiling the 1-instruction program plus the BroadcastOp setup costs ~40-250ns per call - negligible on big images, dominant at 127x61-class sizes where the classic 5.x functions were 1.3-2x faster. Mirror math_op two tiers in arithm_op: two same-type same-shape continuous arrays, no mask, no scalar, result depth == input depth, <= 100k elements -> call the T x T -> T kernel directly over the flattened elements (checks ordered cheapest-first). Applies to add/subtract/min/max/absdiff/multiply/addWeighted/and/or/xor; compare and divide lower to more than a single kernel (boundary rewrites, int guards) and keep the ordinary path. addWeighted falls through automatically for the 32/64-bit int types whose lowering is wide-compute + cast (getElemwiseFunc returns no direct kernel there). 127x61 vs 5.x, was -> now: add/subtract 8UC1 0.76x -> 1.3x, min/max u8 0.6x -> ~1x, addWeighted 1.0x -> 1.1-1.4x (32SC1 stays 4.5x); the one remaining laggard is add/sub 32SC1 (0.74-0.82x) - the price of the new SATURATING semantics (7-instruction AVX2 emulation vs the wrapping single add of 5.x; single-instruction on NEON). The 127x61 size is ADDED PERMANENTLY to the arithmetic/addWeighted/compare perf grids: per-call overhead regressions in these base functions must be caught by CI, not discovered by users. dst creation goes through createSameSize (whole-shape transfer including layout and future metadata, not piecemeal dims+sizes) here and in math_op. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * cv::pow rebuilt on the engine; integer-exponent and 1/sqrt(x) kernel branches Routing: p = 0/1/2 keep their early special cases (fill/copy/multiply); an INTEGER array with an INTEGER power keeps the classic iPow multiply chain - bit-exact compatibility, including its wrap-around quirks (iPow squares in int, so e.g. pow(255,4) on u8 wraps negative and saturates to 0 - somebody may rely on that). Everything else goes through the engine with the math_op two-tier scheme: small continuous arrays call powKernel directly (the exponent rides as a broadcast T scalar), the rest run the tiled parallel program. Integer arrays with fractional powers compute in the float domain and saturate back; the 32U/64-bit depths (classic iPow asserted on them) and f16/bf16 (the classic float path misread them) now just work. powKernel gets two new per-row exponent branches: - p == -0.5: 1/v_sqrt(x) (the classic path used IPP ippsInvSqrt_A21, a 21-bit approximation; ours is exact - slightly slower on small arrays, 4.5x faster at 1080p via parallelism); - any other INTEGER p (|p| <= 65536): LSB-first binary exponentiation, the same multiply chain and order as iPow, fully vectorized - a few ulp accurate vs ~2e-7 of exp(p*log x), and exact on non-positive bases (the sign falls out of the multiplies, 0^negative divides to inf) - no scalar patching. cv::pow f32 vs 5.x: p=0.5 365 -> 61 us at 1080p (6x), p=3 7.7x, p=5 7x AND faster at every size (the old scalar chain: 0.81 -> 0.47 us at 127x61), p=2.5 5.3x. The s16^5 iPow path is untouched (118.7 == 118.5 us). pow_exponents accuracy tests extended to 11 exponents x f32/f64 against the double std::pow reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf: SANITY_CHECK_NOTHING for the tests whose grids got the 127x61 size The 127x61 entry added to guard per-call overhead has no regression data in opencv_extra, so the legacy SANITY_CHECK in addWeighted/compare failed on CI (locally it passes silently without the test-data path). Accuracy of both functions is covered by the accuracy suite; the perf tests should measure time. PatchNaNs/finiteMask keep their SANITY_CHECK - their grids are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix a temp-buffer double release in the liveness pass ("d*d" crash) When the same temp is passed as SEVERAL arguments of its last-use instruction (e.g. the named-intermediate expression "d = {0} - {1}; d*d", where the MUL consumes slot d twice), the buffer-reuse scan pushed the temp's physical buffer onto the free list once per argument. That overflows the ntemps-sized freeBufs array (caught by the AutoBuffer range check in Debug: python test_expr.py::test_named_temporary) and, in larger programs, would hand the same physical buffer to two live temps. Release the buffer once by retiring lastUse[t] after the first hit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * executor: recalibrate opCost to vectorized cycles + clamp the stripe count The per-element op costs fed into the parallel_for_ stripe hint were scalar-era estimates (~20x above the vectorized reality: the atan2/exp polynomials run at ~1.5 cycles/element, not 30). The hint therefore split transcendental/divide work into hundreds of ~1 us jobs, which the macOS/GCD backend dispatches poorly under sustained load, on top of the P/E-core equal-share straggler effect. Measured on M4 Max (12P+4E), sustained medians @1920x1080 f32: texpr atan2 320 -> 133 us, cv::exp 280 -> 141 us, cv::log 301 -> ~200 us, cv::pow(x,2.5) 388 -> 376 us; the PR tables' math rows improved ~1.5-2x across the board. - opCost is now in units of ~1/4 cycle/element of the SIMD kernels: cheap ops 1 (unchanged), div/sqrt/hypot/convert_scale 10 -> 3, transcendentals 30 -> 6. - the stripe hint is clamped by min(4*nthreads, max(32, 3*nthreads)): ~4 stripes/thread is plenty of granularity for element-wise work, and the ceiling is 32 pieces except on machines with many (heterogeneous) cores, where anything coarser than ~3 pieces/worker turns the slow cores into equal-share bottlenecks (measured: 32 stripes on 16 threads is the worst point of the curve - 193 us vs 137 us at 48 for atan2). getNumThreads() is clamped from below (WINRT/plugin backends may report 0). Not addressed here (needs cross-machine data, M2/M3 Ultra): streaming memory-bound ops saturate the M4 Max fabric at ~8 fat stripes and E-core participation only adds contention - a cost model cannot express that; candidate follow-up is a bytes-aware clamp or a GCD-backend-level fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * multiply: v_mul_sat integer kernels (full product clamped to the type) v_mul_sat(V, V) -> V for u8/s8/u16/s16/u32/s32 - the full-precision product clamped to the lane type, which is exactly cv::multiply's integer semantics at scale == 1. Local to arithm.simd.hpp for now, next to v_add_sat/v_sub_sat, to be promoted into proper universal intrinsics later. NEON: widening vmull + saturating narrow (vqmovn); other backends: the portable v_mul_expand + saturating v_pack composition for 8/16-bit lanes. 32-bit lanes have no universal widening multiply (no v_mul_expand for s32), so the 32-bit integer fast path is NEON-only for now and the other backends keep the previous f64 work-vector kernels (which measure well on x86 with IPP-free AVX2). EwMul::vec() now routes the integer lane types through v_mul_sat, and getMulFunc_ passes the NATIVE lane vector as the scale==1 fast-path type: whole-register loads/stores, the widening happens inside the multiply. Replaces both the half-register widening loads (u8/s8/u16/s16) and the scalar-equivalent f64 path for 32S/32U on NEON. M4 Max, 640x480 (the sizes where the old kernels lost to carotene): 8U 22.0 -> 13.6 us, 8S 15.0 -> 11.1, 16S 21.2 -> 17.4, 32S 84.8 -> 30.5 (parity with the classic path everywhere, 32S was 0.38x). 1920x1080: 8S 1.30x -> 1.78x, 16S 2.57x -> 2.98x, 32S 2.20x -> 4.02x vs 5.x. Correctness: exhaustive 8-bit (all 65536 pairs per sign), directed saturation corners for 16/32-bit (46341^2, INT_MIN*-1, 65536*65536, all sign combinations) against an exact int64 reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * NEON: make v_cvt_f64(v_int32) exact (was via f32, losing bits > 2^24) The NEON implementations of v_cvt_f64/v_cvt_f64_high for v_int32 did s32 -> f32 -> f64 (vcvt_f32_s32 + vcvt_f64_f32), silently rounding any |x| > 2^24. Every vectorized f64 work path with int32 inputs on AArch64 was affected: the engine's addWeighted/divide 32S kernels, convertTo 32S -> 64F, etc. Found via addWeighted 32SC1 on values ~1e9: max error was 32 vs the exact double reference (the classic carotene path is worse still - it computes in f32 end-to-end with f32-truncated weights, max error 96 on the same data). The exact sequence sxtl + scvtf (vmovl_s32 + vcvtq_f64_s64) is the same 2 instructions, so there is no cost. addWeighted 32SC1 on the engine now matches the exact-double reference bit-for-bit and stays at parity/1.4x vs the classic path (640x480/1920x1080). Pre-existing upstream bug (same code in 4.x) - worth a standalone backport with directed large-value tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * math_op: drop the temporary direct-IPP tier for exp/log Upstream moved the IPP math wrappers into the hal/ipp HAL module (cv_hal_exp32f/ log32f & co now resolve to ipp_hal_* which honor cv::ipp::useIPP via CV_HAL_CHECK_USE_IPP). The engine's single probeHalUnary(cv_hal_*) probe already picks that up uniformly, so the stopgap #ifdef HAVE_IPP ippExp/ippLog tier and its ipp::useIPP() branch in getMathFunc are now redundant - removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * arithm: restore cv::hal::and8u/or8u/xor8u/not8u as engine wrappers These public CV_EXPORTS entry points (core/hal/hal.hpp) lost their definitions when the bitwise ops moved to the element-wise engine, but they are still declared and called by other modules (opencv_objdetect's aruco) and external code - the link broke with undefined references to cv::hal::and8u/xor8u. Restore them as thin forwarders over the engine's byte-wise bitwise kernels (getBitwiseFunc / getNotFunc), mirroring the existing mul8u wrapper. CV_Assert guards the kernel lookup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * blobdetector: guard empty contour when computing blob radius The new AutoBuffer leaves its tail uninitialized for trivial types, which surfaced a -Wmaybe-uninitialized in findBlobs where the median of per-point distances is read. Use a std::vector, default the radius to 0, and compute the median only for a non-empty contour - no unproven size invariant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
4279 lines
137 KiB
C++
4279 lines
137 KiB
C++
// This file is part of OpenCV project.
|
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
|
// of this distribution and at http://opencv.org/license.html.
|
|
#include "test_precomp.hpp"
|
|
#include "ref_reduce_arg.impl.hpp"
|
|
#include <algorithm>
|
|
|
|
namespace opencv_test { namespace {
|
|
|
|
const int ARITHM_NTESTS = 1000;
|
|
const int ARITHM_RNG_SEED = -1;
|
|
const int ARITHM_MAX_CHANNELS = 4;
|
|
const int ARITHM_MAX_NDIMS = 4;
|
|
const int ARITHM_MAX_SIZE_LOG = 10;
|
|
|
|
// fp8 (E4M3) is excluded from the tolerance-checked element-wise pool: its 3-bit
|
|
// mantissa can't meet these tests' error bounds and out-of-range inputs overflow to
|
|
// NaN. fp8 conversion/arithmetic is covered directly in test_fp8.cpp.
|
|
static const _OutputArray::DepthMask DEPTH_MASK_ALL_NO_FP8 =
|
|
_OutputArray::DepthMask(_OutputArray::DEPTH_MASK_ALL &
|
|
~((1 << CV_8F_E4M3FN) | (1 << CV_8F_E4M3FNUZ)));
|
|
static const _OutputArray::DepthMask DEPTH_MASK_ALL_BUT_8S_NO_FP8 =
|
|
_OutputArray::DepthMask(DEPTH_MASK_ALL_NO_FP8 & ~_OutputArray::DEPTH_MASK_8S);
|
|
|
|
struct BaseElemWiseOp
|
|
{
|
|
enum
|
|
{
|
|
FIX_ALPHA=1, FIX_BETA=2, FIX_GAMMA=4, REAL_GAMMA=8,
|
|
SUPPORT_MASK=16, SCALAR_OUTPUT=32, SUPPORT_MULTICHANNELMASK=64,
|
|
MIXED_TYPE=128
|
|
};
|
|
BaseElemWiseOp(int _ninputs, int _flags, double _alpha, double _beta,
|
|
Scalar _gamma=Scalar::all(0), int _context=1)
|
|
: ninputs(_ninputs), flags(_flags), alpha(_alpha), beta(_beta), gamma(_gamma), context(_context) {}
|
|
BaseElemWiseOp() { flags = 0; alpha = beta = 0; gamma = Scalar::all(0); ninputs = 0; context = 1; }
|
|
virtual ~BaseElemWiseOp() {}
|
|
virtual void op(const vector<Mat>&, Mat&, const Mat&) {}
|
|
virtual void refop(const vector<Mat>&, Mat&, const Mat&) {}
|
|
virtual void getValueRange(int depth, double& minval, double& maxval)
|
|
{
|
|
minval = depth < CV_32S ? cvtest::getMinVal(depth) : depth == CV_32S ? -1000000 : -1000.;
|
|
maxval = depth < CV_32S ? cvtest::getMaxVal(depth) : depth == CV_32S ? 1000000 : 1000.;
|
|
}
|
|
|
|
virtual void getRandomSize(RNG& rng, vector<int>& size)
|
|
{
|
|
cvtest::randomSize(rng, 2, ARITHM_MAX_NDIMS, ARITHM_MAX_SIZE_LOG, size);
|
|
}
|
|
|
|
virtual int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, DEPTH_MASK_ALL_BUT_8S_NO_FP8, 1,
|
|
ninputs > 1 ? ARITHM_MAX_CHANNELS : 4);
|
|
}
|
|
|
|
virtual double getMaxErr(int depth)
|
|
{
|
|
return depth < CV_32F || depth == CV_32U || depth == CV_64U || depth == CV_64S ? 1 :
|
|
depth == CV_16F || depth == CV_16BF ? 1e-2 : depth == CV_32F ? 1e-5 : 1e-12;
|
|
}
|
|
virtual void generateScalars(int depth, RNG& rng)
|
|
{
|
|
const double m = 3.;
|
|
|
|
if( !(flags & FIX_ALPHA) )
|
|
{
|
|
alpha = exp(rng.uniform(-0.5, 0.1)*m*2*CV_LOG2);
|
|
alpha *= rng.uniform(0, 2) ? 1 : -1;
|
|
}
|
|
if( !(flags & FIX_BETA) )
|
|
{
|
|
beta = exp(rng.uniform(-0.5, 0.1)*m*2*CV_LOG2);
|
|
beta *= rng.uniform(0, 2) ? 1 : -1;
|
|
}
|
|
|
|
if( !(flags & FIX_GAMMA) )
|
|
{
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
gamma[i] = exp(rng.uniform(-1, 6)*m*CV_LOG2);
|
|
gamma[i] *= rng.uniform(0, 2) ? 1 : -1;
|
|
}
|
|
if( flags & REAL_GAMMA )
|
|
gamma = Scalar::all(gamma[0]);
|
|
}
|
|
|
|
if( depth == CV_32F )
|
|
{
|
|
Mat fl, db;
|
|
|
|
db = Mat(1, 1, CV_64F, &alpha);
|
|
db.convertTo(fl, CV_32F);
|
|
fl.convertTo(db, CV_64F);
|
|
|
|
db = Mat(1, 1, CV_64F, &beta);
|
|
db.convertTo(fl, CV_32F);
|
|
fl.convertTo(db, CV_64F);
|
|
|
|
db = Mat(1, 4, CV_64F, &gamma[0]);
|
|
db.convertTo(fl, CV_32F);
|
|
fl.convertTo(db, CV_64F);
|
|
}
|
|
}
|
|
|
|
int ninputs;
|
|
int flags;
|
|
double alpha;
|
|
double beta;
|
|
Scalar gamma;
|
|
int context;
|
|
};
|
|
|
|
static const _OutputArray::DepthMask baseArithmTypeMask =
|
|
_OutputArray::DepthMask(
|
|
_OutputArray::DEPTH_MASK_8U |
|
|
_OutputArray::DEPTH_MASK_16U |
|
|
_OutputArray::DEPTH_MASK_16S |
|
|
_OutputArray::DEPTH_MASK_32S |
|
|
_OutputArray::DEPTH_MASK_32F |
|
|
_OutputArray::DEPTH_MASK_64F |
|
|
_OutputArray::DEPTH_MASK_16F |
|
|
_OutputArray::DEPTH_MASK_16BF |
|
|
_OutputArray::DEPTH_MASK_32U |
|
|
_OutputArray::DEPTH_MASK_64U |
|
|
_OutputArray::DEPTH_MASK_64S );
|
|
|
|
struct BaseArithmOp : public BaseElemWiseOp
|
|
{
|
|
BaseArithmOp(int _ninputs, int _flags, double _alpha, double _beta, Scalar _gamma=Scalar::all(0))
|
|
: BaseElemWiseOp(_ninputs, _flags, _alpha, _beta, _gamma) {}
|
|
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, baseArithmTypeMask, 1,
|
|
ninputs > 1 ? ARITHM_MAX_CHANNELS : 4);
|
|
}
|
|
};
|
|
|
|
struct BaseAddOp : public BaseArithmOp
|
|
{
|
|
BaseAddOp(int _ninputs, int _flags, double _alpha, double _beta, Scalar _gamma=Scalar::all(0))
|
|
: BaseArithmOp(_ninputs, _flags, _alpha, _beta, _gamma) {}
|
|
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
int dstType = (flags & MIXED_TYPE) ? dst.type() : src[0].type();
|
|
if( !mask.empty() )
|
|
{
|
|
Mat temp;
|
|
cvtest::add(src[0], alpha, src.size() > 1 ? src[1] : Mat(), beta, gamma, temp, dstType);
|
|
cvtest::copy(temp, dst, mask);
|
|
}
|
|
else
|
|
cvtest::add(src[0], alpha, src.size() > 1 ? src[1] : Mat(), beta, gamma, dst, dstType);
|
|
}
|
|
|
|
double getMaxErr(int depth)
|
|
{
|
|
return depth == CV_16BF ? 1e-2 : depth == CV_16F ? 1e-3 : depth == CV_32F ? 1e-4 : depth == CV_64F ? 1e-12 : 2;
|
|
}
|
|
};
|
|
|
|
|
|
struct AddOp : public BaseAddOp
|
|
{
|
|
AddOp() : BaseAddOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::add(src[0], src[1], dst, mask, dtype);
|
|
}
|
|
};
|
|
|
|
|
|
struct SubOp : public BaseAddOp
|
|
{
|
|
SubOp() : BaseAddOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK, 1, -1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::subtract(src[0], src[1], dst, mask, dtype);
|
|
}
|
|
};
|
|
|
|
|
|
struct AddSOp : public BaseAddOp
|
|
{
|
|
AddSOp() : BaseAddOp(1, FIX_ALPHA+FIX_BETA+SUPPORT_MASK, 1, 0, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::add(src[0], gamma, dst, mask, dtype);
|
|
}
|
|
};
|
|
|
|
|
|
struct SubRSOp : public BaseAddOp
|
|
{
|
|
SubRSOp() : BaseAddOp(1, FIX_ALPHA+FIX_BETA+SUPPORT_MASK, -1, 0, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::subtract(gamma, src[0], dst, mask, dtype);
|
|
}
|
|
};
|
|
|
|
|
|
struct ScaleAddOp : public BaseAddOp
|
|
{
|
|
ScaleAddOp() : BaseAddOp(2, FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::scaleAdd(src[0], alpha, src[1], dst);
|
|
}
|
|
double getMaxErr(int depth)
|
|
{
|
|
return depth == CV_16BF ? 1e-2 : depth == CV_16F ? 1e-3 : depth == CV_32F ? 3e-5 : depth == CV_64F ? 1e-12 : 2;
|
|
}
|
|
};
|
|
|
|
|
|
struct AddWeightedOp : public BaseAddOp
|
|
{
|
|
AddWeightedOp() : BaseAddOp(2, REAL_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::addWeighted(src[0], alpha, src[1], beta, gamma[0], dst, dtype);
|
|
}
|
|
};
|
|
|
|
struct MulOp : public BaseArithmOp
|
|
{
|
|
MulOp() : BaseArithmOp(2, FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void getValueRange(int depth, double& minval, double& maxval)
|
|
{
|
|
minval = depth < CV_32S ? cvtest::getMinVal(depth) : depth == CV_32S ? -1000000 : -1000.;
|
|
maxval = depth < CV_32S ? cvtest::getMaxVal(depth) : depth == CV_32S ? 1000000 : 1000.;
|
|
minval = std::max(minval, -30000.);
|
|
maxval = std::min(maxval, 30000.);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::multiply(src[0], src[1], dst, alpha, dtype);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cvtest::multiply(src[0], src[1], dst, alpha, dtype);
|
|
}
|
|
};
|
|
|
|
struct MulSOp : public BaseArithmOp
|
|
{
|
|
MulSOp() : BaseArithmOp(1, FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void getValueRange(int depth, double& minval, double& maxval)
|
|
{
|
|
minval = depth < CV_32S ? cvtest::getMinVal(depth) : depth == CV_32S ? -1000000 : -1000.;
|
|
maxval = depth < CV_32S ? cvtest::getMaxVal(depth) : depth == CV_32S ? 1000000 : 1000.;
|
|
minval = std::max(minval, -30000.);
|
|
maxval = std::min(maxval, 30000.);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::multiply(src[0], alpha, dst, /* scale */ 1.0, dtype);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cvtest::multiply(Mat(), src[0], dst, alpha, dtype);
|
|
}
|
|
};
|
|
|
|
struct DivOp : public BaseArithmOp
|
|
{
|
|
DivOp() : BaseArithmOp(2, FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::divide(src[0], src[1], dst, alpha, dtype);
|
|
if (flags & MIXED_TYPE)
|
|
{
|
|
// div by zero result is implementation-defined
|
|
// since it may involve conversions to/from intermediate format
|
|
Mat zeroMask = src[1] == 0;
|
|
dst.setTo(0, zeroMask);
|
|
}
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cvtest::divide(src[0], src[1], dst, alpha, dtype);
|
|
}
|
|
};
|
|
|
|
struct RecipOp : public BaseArithmOp
|
|
{
|
|
RecipOp() : BaseArithmOp(1, FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cv::divide(alpha, src[0], dst, dtype);
|
|
if (flags & MIXED_TYPE)
|
|
{
|
|
// div by zero result is implementation-defined
|
|
// since it may involve conversions to/from intermediate format
|
|
Mat zeroMask = src[0] == 0;
|
|
dst.setTo(0, zeroMask);
|
|
}
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
int dtype = (flags & MIXED_TYPE) ? dst.type() : -1;
|
|
cvtest::divide(Mat(), src[0], dst, alpha, dtype);
|
|
}
|
|
};
|
|
|
|
struct AbsDiffOp : public BaseAddOp
|
|
{
|
|
AbsDiffOp() : BaseAddOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, -1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
absdiff(src[0], src[1], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::add(src[0], 1, src[1], -1, Scalar::all(0), dst, src[0].type(), true);
|
|
}
|
|
};
|
|
|
|
struct AbsDiffSOp : public BaseAddOp
|
|
{
|
|
AbsDiffSOp() : BaseAddOp(1, FIX_ALPHA+FIX_BETA, 1, 0, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
absdiff(src[0], gamma, dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::add(src[0], 1, Mat(), 0, -gamma, dst, src[0].type(), true);
|
|
}
|
|
};
|
|
|
|
struct LogicOp : public BaseElemWiseOp
|
|
{
|
|
LogicOp(char _opcode) : BaseElemWiseOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK, 1, 1, Scalar::all(0)), opcode(_opcode) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
if( opcode == '&' )
|
|
cv::bitwise_and(src[0], src[1], dst, mask);
|
|
else if( opcode == '|' )
|
|
cv::bitwise_or(src[0], src[1], dst, mask);
|
|
else
|
|
cv::bitwise_xor(src[0], src[1], dst, mask);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
Mat temp;
|
|
if( !mask.empty() )
|
|
{
|
|
cvtest::logicOp(src[0], src[1], temp, opcode);
|
|
cvtest::copy(temp, dst, mask);
|
|
}
|
|
else
|
|
cvtest::logicOp(src[0], src[1], dst, opcode);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
char opcode;
|
|
};
|
|
|
|
struct LogicSOp : public BaseElemWiseOp
|
|
{
|
|
LogicSOp(char _opcode)
|
|
: BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+(_opcode != '~' ? SUPPORT_MASK : 0), 1, 1, Scalar::all(0)), opcode(_opcode) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
if( opcode == '&' )
|
|
cv::bitwise_and(src[0], gamma, dst, mask);
|
|
else if( opcode == '|' )
|
|
cv::bitwise_or(src[0], gamma, dst, mask);
|
|
else if( opcode == '^' )
|
|
cv::bitwise_xor(src[0], gamma, dst, mask);
|
|
else
|
|
cv::bitwise_not(src[0], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
Mat temp;
|
|
if( !mask.empty() )
|
|
{
|
|
cvtest::logicOp(src[0], gamma, temp, opcode);
|
|
cvtest::copy(temp, dst, mask);
|
|
}
|
|
else
|
|
cvtest::logicOp(src[0], gamma, dst, opcode);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
char opcode;
|
|
};
|
|
|
|
struct MinOp : public BaseArithmOp
|
|
{
|
|
MinOp() : BaseArithmOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::min(src[0], src[1], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::min(src[0], src[1], dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct MaxOp : public BaseArithmOp
|
|
{
|
|
MaxOp() : BaseArithmOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::max(src[0], src[1], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::max(src[0], src[1], dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct MinSOp : public BaseArithmOp
|
|
{
|
|
MinSOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+REAL_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::min(src[0], gamma[0], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::min(src[0], gamma[0], dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct MaxSOp : public BaseArithmOp
|
|
{
|
|
MaxSOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+REAL_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::max(src[0], gamma[0], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::max(src[0], gamma[0], dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct CmpOp : public BaseArithmOp
|
|
{
|
|
CmpOp() : BaseArithmOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) { cmpop = 0; }
|
|
void generateScalars(int depth, RNG& rng)
|
|
{
|
|
BaseElemWiseOp::generateScalars(depth, rng);
|
|
cmpop = rng.uniform(0, 6);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::compare(src[0], src[1], dst, cmpop);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::compare(src[0], src[1], dst, cmpop);
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, baseArithmTypeMask, 1, 1);
|
|
}
|
|
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
int cmpop;
|
|
};
|
|
|
|
struct CmpSOp : public BaseArithmOp
|
|
{
|
|
CmpSOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+REAL_GAMMA, 1, 1, Scalar::all(0)) { cmpop = 0; }
|
|
void generateScalars(int depth, RNG& rng)
|
|
{
|
|
BaseElemWiseOp::generateScalars(depth, rng);
|
|
cmpop = rng.uniform(0, 6);
|
|
if( depth != CV_16F && depth != CV_16BF && depth != CV_32F && depth != CV_64F )
|
|
gamma[0] = cvRound(gamma[0]);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::compare(src[0], gamma[0], dst, cmpop);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::compare(src[0], gamma[0], dst, cmpop);
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, baseArithmTypeMask, 1, 1);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
int cmpop;
|
|
};
|
|
|
|
|
|
struct CopyOp : public BaseElemWiseOp
|
|
{
|
|
CopyOp() : BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK+SUPPORT_MULTICHANNELMASK, 1, 1, Scalar::all(0)) { }
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
src[0].copyTo(dst, mask);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
cvtest::copy(src[0], dst, mask);
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_ALL, 1, ARITHM_MAX_CHANNELS);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
struct SetOp : public BaseElemWiseOp
|
|
{
|
|
SetOp() : BaseElemWiseOp(0, FIX_ALPHA+FIX_BETA+SUPPORT_MASK+SUPPORT_MULTICHANNELMASK, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>&, Mat& dst, const Mat& mask)
|
|
{
|
|
dst.setTo(gamma, mask);
|
|
}
|
|
void refop(const vector<Mat>&, Mat& dst, const Mat& mask)
|
|
{
|
|
cvtest::set(dst, gamma, mask);
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_ALL, 1, ARITHM_MAX_CHANNELS);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
template<typename _Tp, typename _WTp=_Tp> static void
|
|
inRangeS_(const _Tp* src, const _WTp* a, const _WTp* b, uchar* dst, size_t total, int cn)
|
|
{
|
|
size_t i;
|
|
int c;
|
|
for( i = 0; i < total; i++ )
|
|
{
|
|
_WTp val = (_WTp)src[i*cn];
|
|
dst[i] = (a[0] <= val && val <= b[0]) ? uchar(255) : 0;
|
|
}
|
|
for( c = 1; c < cn; c++ )
|
|
{
|
|
for( i = 0; i < total; i++ )
|
|
{
|
|
_WTp val = (_WTp)src[i*cn + c];
|
|
dst[i] = a[c] <= val && val <= b[c] ? dst[i] : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename _Tp, typename _WTp=_Tp> static void
|
|
inRange_(const _Tp* src, const _Tp* a, const _Tp* b,
|
|
uchar* dst, size_t total, int cn)
|
|
{
|
|
size_t i;
|
|
int c;
|
|
for( i = 0; i < total; i++ )
|
|
{
|
|
_Tp val = src[i*cn];
|
|
dst[i] = a[i*cn] <= val && val <= b[i*cn] ? 255 : 0;
|
|
}
|
|
for( c = 1; c < cn; c++ )
|
|
{
|
|
for( i = 0; i < total; i++ )
|
|
{
|
|
_Tp val = src[i*cn + c];
|
|
dst[i] = a[i*cn + c] <= val && val <= b[i*cn + c] ? dst[i] : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace reference {
|
|
|
|
static void inRange(const Mat& src, const Mat& lb, const Mat& rb, Mat& dst)
|
|
{
|
|
CV_Assert( src.type() == lb.type() && src.type() == rb.type() &&
|
|
src.size == lb.size && src.size == rb.size );
|
|
dst.create( src.size, CV_8U );
|
|
const Mat *arrays[]={&src, &lb, &rb, &dst, 0};
|
|
Mat planes[4];
|
|
|
|
NAryMatIterator it(arrays, planes);
|
|
size_t total = planes[0].total();
|
|
size_t i, nplanes = it.nplanes;
|
|
int depth = src.depth(), cn = src.channels();
|
|
|
|
for( i = 0; i < nplanes; i++, ++it )
|
|
{
|
|
const uchar* sptr = planes[0].ptr();
|
|
const uchar* aptr = planes[1].ptr();
|
|
const uchar* bptr = planes[2].ptr();
|
|
uchar* dptr = planes[3].ptr();
|
|
|
|
switch( depth )
|
|
{
|
|
case CV_8U:
|
|
inRange_((const uchar*)sptr, (const uchar*)aptr, (const uchar*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_8S:
|
|
inRange_((const schar*)sptr, (const schar*)aptr, (const schar*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_16U:
|
|
inRange_((const ushort*)sptr, (const ushort*)aptr, (const ushort*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_16S:
|
|
inRange_((const short*)sptr, (const short*)aptr, (const short*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_32U:
|
|
inRange_((const unsigned*)sptr, (const unsigned*)aptr, (const unsigned*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_32S:
|
|
inRange_((const int*)sptr, (const int*)aptr, (const int*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_64U:
|
|
inRange_((const uint64*)sptr, (const uint64*)aptr, (const uint64*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_64S:
|
|
inRange_((const int64*)sptr, (const int64*)aptr, (const int64*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_32F:
|
|
inRange_((const float*)sptr, (const float*)aptr, (const float*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_64F:
|
|
inRange_((const double*)sptr, (const double*)aptr, (const double*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_16F:
|
|
inRange_<cv::hfloat, float>((const cv::hfloat*)sptr, (const cv::hfloat*)aptr,
|
|
(const cv::hfloat*)bptr, dptr, total, cn);
|
|
break;
|
|
case CV_16BF:
|
|
inRange_<cv::bfloat, float>((const cv::bfloat*)sptr, (const cv::bfloat*)aptr,
|
|
(const cv::bfloat*)bptr, dptr, total, cn);
|
|
break;
|
|
default:
|
|
CV_Error(cv::Error::StsUnsupportedFormat, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void inRangeS(const Mat& src, const Scalar& lb, const Scalar& rb, Mat& dst)
|
|
{
|
|
dst.create( src.size, CV_8U );
|
|
const Mat *arrays[]={&src, &dst, 0};
|
|
Mat planes[2];
|
|
|
|
NAryMatIterator it(arrays, planes);
|
|
size_t total = planes[0].total();
|
|
size_t i, nplanes = it.nplanes;
|
|
int depth = src.depth(), cn = src.channels();
|
|
union { double d[4]; float f[4]; int i[4]; unsigned u[4]; int64 L[4]; uint64 UL[4]; } lbuf, rbuf;
|
|
int wtype = CV_MAKETYPE((depth <= CV_32S ? CV_32S :
|
|
depth == CV_16F || depth == CV_16BF || depth == CV_32F ? CV_32F : depth), cn);
|
|
scalarToRawData(lb, lbuf.d, wtype, cn);
|
|
scalarToRawData(rb, rbuf.d, wtype, cn);
|
|
|
|
for( i = 0; i < nplanes; i++, ++it )
|
|
{
|
|
const uchar* sptr = planes[0].ptr();
|
|
uchar* dptr = planes[1].ptr();
|
|
|
|
switch( depth )
|
|
{
|
|
case CV_8U:
|
|
inRangeS_((const uchar*)sptr, lbuf.i, rbuf.i, dptr, total, cn);
|
|
break;
|
|
case CV_8S:
|
|
inRangeS_((const schar*)sptr, lbuf.i, rbuf.i, dptr, total, cn);
|
|
break;
|
|
case CV_16U:
|
|
inRangeS_((const ushort*)sptr, lbuf.i, rbuf.i, dptr, total, cn);
|
|
break;
|
|
case CV_16S:
|
|
inRangeS_((const short*)sptr, lbuf.i, rbuf.i, dptr, total, cn);
|
|
break;
|
|
case CV_32U:
|
|
inRangeS_((const unsigned*)sptr, lbuf.u, rbuf.u, dptr, total, cn);
|
|
break;
|
|
case CV_32S:
|
|
inRangeS_((const int*)sptr, lbuf.i, rbuf.i, dptr, total, cn);
|
|
break;
|
|
case CV_64U:
|
|
inRangeS_((const uint64*)sptr, lbuf.UL, rbuf.UL, dptr, total, cn);
|
|
break;
|
|
case CV_64S:
|
|
inRangeS_((const int64*)sptr, lbuf.L, rbuf.L, dptr, total, cn);
|
|
break;
|
|
case CV_32F:
|
|
inRangeS_((const float*)sptr, lbuf.f, rbuf.f, dptr, total, cn);
|
|
break;
|
|
case CV_64F:
|
|
inRangeS_((const double*)sptr, lbuf.d, rbuf.d, dptr, total, cn);
|
|
break;
|
|
case CV_16F:
|
|
inRangeS_((const cv::hfloat*)sptr, lbuf.f, rbuf.f, dptr, total, cn);
|
|
break;
|
|
case CV_16BF:
|
|
inRangeS_((const cv::bfloat*)sptr, lbuf.f, rbuf.f, dptr, total, cn);
|
|
break;
|
|
default:
|
|
CV_Error(cv::Error::StsUnsupportedFormat, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
CVTEST_GUARD_SYMBOL(inRange)
|
|
|
|
struct InRangeSOp : public BaseArithmOp
|
|
{
|
|
InRangeSOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::inRange(src[0], gamma, gamma1, dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
reference::inRangeS(src[0], gamma, gamma1, dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
void generateScalars(int depth, RNG& rng)
|
|
{
|
|
BaseElemWiseOp::generateScalars(depth, rng);
|
|
Scalar temp = gamma;
|
|
BaseElemWiseOp::generateScalars(depth, rng);
|
|
for( int i = 0; i < 4; i++ )
|
|
{
|
|
gamma1[i] = std::max(gamma[i], temp[i]);
|
|
gamma[i] = std::min(gamma[i], temp[i]);
|
|
}
|
|
}
|
|
Scalar gamma1;
|
|
};
|
|
|
|
|
|
struct InRangeOp : public BaseArithmOp
|
|
{
|
|
InRangeOp() : BaseArithmOp(3, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
Mat lb, rb;
|
|
cvtest::min(src[1], src[2], lb);
|
|
cvtest::max(src[1], src[2], rb);
|
|
|
|
cv::inRange(src[0], lb, rb, dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
Mat lb, rb;
|
|
cvtest::min(src[1], src[2], lb);
|
|
cvtest::max(src[1], src[2], rb);
|
|
|
|
reference::inRange(src[0], lb, rb, dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
namespace reference {
|
|
|
|
template<typename _Tp>
|
|
struct SoftType;
|
|
|
|
template<>
|
|
struct SoftType<float>
|
|
{
|
|
typedef softfloat type;
|
|
};
|
|
|
|
template<>
|
|
struct SoftType<double>
|
|
{
|
|
typedef softdouble type;
|
|
};
|
|
|
|
|
|
template <typename _Tp>
|
|
static void finiteMask_(const _Tp *src, uchar *dst, size_t total, int cn)
|
|
{
|
|
for(size_t i = 0; i < total; i++ )
|
|
{
|
|
bool good = true;
|
|
for (int c = 0; c < cn; c++)
|
|
{
|
|
_Tp val = src[i * cn + c];
|
|
typename SoftType<_Tp>::type sval(val);
|
|
|
|
good = good && !sval.isNaN() && !sval.isInf();
|
|
}
|
|
dst[i] = good ? 255 : 0;
|
|
}
|
|
}
|
|
|
|
static void finiteMask(const Mat& src, Mat& dst)
|
|
{
|
|
dst.create(src.size, CV_8UC1);
|
|
|
|
const Mat *arrays[]={&src, &dst, 0};
|
|
Mat planes[2];
|
|
NAryMatIterator it(arrays, planes);
|
|
size_t total = planes[0].total();
|
|
size_t i, nplanes = it.nplanes;
|
|
int depth = src.depth(), cn = src.channels();
|
|
|
|
for( i = 0; i < nplanes; i++, ++it )
|
|
{
|
|
const uchar* sptr = planes[0].ptr();
|
|
uchar* dptr = planes[1].ptr();
|
|
|
|
switch( depth )
|
|
{
|
|
case CV_32F: finiteMask_<float >((const float*)sptr, dptr, total, cn); break;
|
|
case CV_64F: finiteMask_<double>((const double*)sptr, dptr, total, cn); break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
struct FiniteMaskOp : public BaseElemWiseOp
|
|
{
|
|
FiniteMaskOp() : BaseElemWiseOp(1, 0, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::finiteMask(src[0], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
reference::finiteMask(src[0], dst);
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_FLT, 1, 4);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
struct ConvertScaleOp : public BaseElemWiseOp
|
|
{
|
|
ConvertScaleOp() : BaseElemWiseOp(1, FIX_BETA+REAL_GAMMA, 1, 1, Scalar::all(0)), ddepth(0) { }
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
src[0].convertTo(dst, ddepth, alpha, gamma[0]);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::convert(src[0], dst, CV_MAKETYPE(ddepth, src[0].channels()), alpha, gamma[0]);
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
int srctype = cvtest::randomType(rng, DEPTH_MASK_ALL_NO_FP8, 1, ARITHM_MAX_CHANNELS);
|
|
ddepth = cvtest::randomType(rng, DEPTH_MASK_ALL_NO_FP8, 1, 1);
|
|
return srctype;
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return ddepth <= CV_32S || ddepth == CV_32U || ddepth == CV_64U || ddepth == CV_64S ? 2 : ddepth == CV_64F ? 1e-12 : ddepth == CV_Bool ? 0 : ddepth == CV_16BF ? 1e-2 : 2e-3;
|
|
}
|
|
void generateScalars(int depth, RNG& rng)
|
|
{
|
|
if( rng.uniform(0, 2) )
|
|
BaseElemWiseOp::generateScalars(depth, rng);
|
|
else
|
|
{
|
|
alpha = 1;
|
|
gamma = Scalar::all(0);
|
|
}
|
|
}
|
|
int ddepth;
|
|
};
|
|
|
|
struct ConvertScaleFp16Op : public BaseElemWiseOp
|
|
{
|
|
ConvertScaleFp16Op() : BaseElemWiseOp(1, FIX_BETA+REAL_GAMMA, 1, 1, Scalar::all(0)), nextRange(0) { }
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
Mat m;
|
|
if (src[0].depth() == CV_32F)
|
|
{
|
|
src[0].convertTo(m, CV_16F);
|
|
m.convertTo(dst, CV_32F);
|
|
}
|
|
else
|
|
{
|
|
src[0].convertTo(m, CV_32F);
|
|
m.convertTo(dst, CV_16F);
|
|
}
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::copy(src[0], dst);
|
|
}
|
|
int getRandomType(RNG&)
|
|
{
|
|
// 0: FP32 -> FP16 -> FP32
|
|
// 1: FP16 -> FP32 -> FP16
|
|
int srctype = (nextRange & 1) == 0 ? CV_32F : CV_16F;
|
|
return srctype;
|
|
}
|
|
void getValueRange(int, double& minval, double& maxval)
|
|
{
|
|
// 0: FP32 -> FP16 -> FP32
|
|
// 1: FP16 -> FP32 -> FP16
|
|
if( (nextRange & 1) == 0 )
|
|
{
|
|
// largest integer number that fp16 can express exactly
|
|
maxval = 2048.f;
|
|
minval = -maxval;
|
|
}
|
|
else
|
|
{
|
|
// 0: positive number range
|
|
// 1: negative number range
|
|
if( (nextRange & 2) == 0 )
|
|
{
|
|
minval = 0; // 0x0000 +0
|
|
maxval = 31744; // 0x7C00 +Inf
|
|
}
|
|
else
|
|
{
|
|
minval = -32768; // 0x8000 -0
|
|
maxval = -1024; // 0xFC00 -Inf
|
|
}
|
|
}
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0.5f;
|
|
}
|
|
void generateScalars(int, RNG& rng)
|
|
{
|
|
nextRange = rng.next();
|
|
}
|
|
int nextRange;
|
|
};
|
|
|
|
struct ConvertScaleAbsOp : public BaseElemWiseOp
|
|
{
|
|
ConvertScaleAbsOp() : BaseElemWiseOp(1, FIX_BETA+REAL_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::convertScaleAbs(src[0], dst, alpha, gamma[0]);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::add(src[0], alpha, Mat(), 0, Scalar::all(gamma[0]), dst, CV_8UC(src[0].channels()), true);
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, DEPTH_MASK_ALL_NO_FP8, 1,
|
|
ninputs > 1 ? ARITHM_MAX_CHANNELS : 4);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 1;
|
|
}
|
|
void generateScalars(int depth, RNG& rng)
|
|
{
|
|
if( rng.uniform(0, 2) )
|
|
BaseElemWiseOp::generateScalars(depth, rng);
|
|
else
|
|
{
|
|
alpha = 1;
|
|
gamma = Scalar::all(0);
|
|
}
|
|
}
|
|
};
|
|
|
|
namespace reference {
|
|
|
|
// does not support inplace operation
|
|
static void flip(const Mat& src, Mat& dst, int flipcode)
|
|
{
|
|
CV_Assert(src.dims <= 2);
|
|
dst.create(src.size, src.type());
|
|
int i, j, k, esz = (int)src.elemSize(), width = src.cols*esz;
|
|
|
|
for( i = 0; i < dst.rows; i++ )
|
|
{
|
|
const uchar* sptr = src.ptr(flipcode == 1 ? i : dst.rows - i - 1);
|
|
uchar* dptr = dst.ptr(i);
|
|
if( flipcode == 0 )
|
|
memcpy(dptr, sptr, width);
|
|
else
|
|
{
|
|
for( j = 0; j < width; j += esz )
|
|
for( k = 0; k < esz; k++ )
|
|
dptr[j + k] = sptr[width - j - esz + k];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void flip_inplace(Mat& dst, int flipcode)
|
|
{
|
|
Mat m;
|
|
m.create(dst.size(), dst.type());
|
|
reference::flip(dst, m, flipcode);
|
|
memcpy(dst.ptr<uchar>(), m.ptr<uchar>(), dst.total() * dst.elemSize());
|
|
}
|
|
|
|
static void rotate(const Mat& src, Mat& dst, int rotateMode)
|
|
{
|
|
Mat tmp;
|
|
switch (rotateMode)
|
|
{
|
|
case ROTATE_90_CLOCKWISE:
|
|
cvtest::transpose(src, tmp);
|
|
reference::flip(tmp, dst, 1);
|
|
break;
|
|
case ROTATE_180:
|
|
reference::flip(src, dst, -1);
|
|
break;
|
|
case ROTATE_90_COUNTERCLOCKWISE:
|
|
cvtest::transpose(src, tmp);
|
|
reference::flip(tmp, dst, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void setIdentity(Mat& dst, const Scalar& s)
|
|
{
|
|
CV_Assert( dst.dims == 2 && dst.channels() <= 4 );
|
|
double buf[4];
|
|
scalarToRawData(s, buf, dst.type(), 0);
|
|
int i, k, esz = (int)dst.elemSize(), width = dst.cols*esz;
|
|
|
|
for( i = 0; i < dst.rows; i++ )
|
|
{
|
|
uchar* dptr = dst.ptr(i);
|
|
memset( dptr, 0, width );
|
|
if( i < dst.cols )
|
|
for( k = 0; k < esz; k++ )
|
|
dptr[i*esz + k] = ((uchar*)buf)[k];
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct FlipOp : public BaseElemWiseOp
|
|
{
|
|
FlipOp() : BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) { flipcode = 0; }
|
|
void getRandomSize(RNG& rng, vector<int>& size)
|
|
{
|
|
cvtest::randomSize(rng, 2, 2, ARITHM_MAX_SIZE_LOG, size);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::flip(src[0], dst, flipcode);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
reference::flip(src[0], dst, flipcode);
|
|
}
|
|
void generateScalars(int, RNG& rng)
|
|
{
|
|
flipcode = rng.uniform(0, 3) - 1;
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
int flipcode;
|
|
};
|
|
|
|
struct FlipInplaceOp : public BaseElemWiseOp
|
|
{
|
|
FlipInplaceOp() : BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) { flipcode = 0; }
|
|
void getRandomSize(RNG& rng, vector<int>& size)
|
|
{
|
|
cvtest::randomSize(rng, 2, 2, ARITHM_MAX_SIZE_LOG, size);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
dst.create(src[0].size(), src[0].type());
|
|
memcpy(dst.ptr<uchar>(), src[0].ptr<uchar>(), src[0].total() * src[0].elemSize());
|
|
cv::flip(dst, dst, flipcode);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
dst.create(src[0].size(), src[0].type());
|
|
memcpy(dst.ptr<uchar>(), src[0].ptr<uchar>(), src[0].total() * src[0].elemSize());
|
|
reference::flip_inplace(dst, flipcode);
|
|
}
|
|
void generateScalars(int, RNG& rng)
|
|
{
|
|
flipcode = rng.uniform(0, 3) - 1;
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
int flipcode;
|
|
};
|
|
|
|
struct RotateOp : public BaseElemWiseOp
|
|
{
|
|
RotateOp() : BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) { rotatecode = 0; }
|
|
void getRandomSize(RNG& rng, vector<int>& size)
|
|
{
|
|
cvtest::randomSize(rng, 2, 2, ARITHM_MAX_SIZE_LOG, size);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::rotate(src[0], dst, rotatecode);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
reference::rotate(src[0], dst, rotatecode);
|
|
}
|
|
void generateScalars(int, RNG& rng)
|
|
{
|
|
rotatecode = rng.uniform(0, 3);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
int rotatecode;
|
|
};
|
|
|
|
struct TransposeOp : public BaseElemWiseOp
|
|
{
|
|
TransposeOp() : BaseElemWiseOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void getRandomSize(RNG& rng, vector<int>& size)
|
|
{
|
|
cvtest::randomSize(rng, 2, 2, ARITHM_MAX_SIZE_LOG, size);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::transpose(src[0], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::transpose(src[0], dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct SetIdentityOp : public BaseElemWiseOp
|
|
{
|
|
SetIdentityOp() : BaseElemWiseOp(0, FIX_ALPHA+FIX_BETA, 1, 1, Scalar::all(0)) {}
|
|
void getRandomSize(RNG& rng, vector<int>& size)
|
|
{
|
|
cvtest::randomSize(rng, 2, 2, ARITHM_MAX_SIZE_LOG, size);
|
|
}
|
|
void op(const vector<Mat>&, Mat& dst, const Mat&)
|
|
{
|
|
cv::setIdentity(dst, gamma);
|
|
}
|
|
void refop(const vector<Mat>&, Mat& dst, const Mat&)
|
|
{
|
|
reference::setIdentity(dst, gamma);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct SetZeroOp : public BaseElemWiseOp
|
|
{
|
|
SetZeroOp() : BaseElemWiseOp(0, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
void op(const vector<Mat>&, Mat& dst, const Mat&)
|
|
{
|
|
dst = Scalar::all(0);
|
|
}
|
|
void refop(const vector<Mat>&, Mat& dst, const Mat&)
|
|
{
|
|
cvtest::set(dst, Scalar::all(0));
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
namespace reference {
|
|
static void exp(const Mat& src, Mat& dst)
|
|
{
|
|
dst.create( src.size, src.type() );
|
|
const Mat *arrays[]={&src, &dst, 0};
|
|
Mat planes[2];
|
|
|
|
NAryMatIterator it(arrays, planes);
|
|
size_t j, total = planes[0].total()*src.channels();
|
|
size_t i, nplanes = it.nplanes;
|
|
int depth = src.depth();
|
|
|
|
for( i = 0; i < nplanes; i++, ++it )
|
|
{
|
|
const uchar* sptr = planes[0].ptr();
|
|
uchar* dptr = planes[1].ptr();
|
|
|
|
if( depth == CV_32F )
|
|
{
|
|
for( j = 0; j < total; j++ )
|
|
((float*)dptr)[j] = std::exp(((const float*)sptr)[j]);
|
|
}
|
|
else if( depth == CV_64F )
|
|
{
|
|
for( j = 0; j < total; j++ )
|
|
((double*)dptr)[j] = std::exp(((const double*)sptr)[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void log(const Mat& src, Mat& dst)
|
|
{
|
|
dst.create( src.size, src.type() );
|
|
const Mat *arrays[]={&src, &dst, 0};
|
|
Mat planes[2];
|
|
|
|
NAryMatIterator it(arrays, planes);
|
|
size_t j, total = planes[0].total()*src.channels();
|
|
size_t i, nplanes = it.nplanes;
|
|
int depth = src.depth();
|
|
|
|
for( i = 0; i < nplanes; i++, ++it )
|
|
{
|
|
const uchar* sptr = planes[0].ptr();
|
|
uchar* dptr = planes[1].ptr();
|
|
|
|
if( depth == CV_32F )
|
|
{
|
|
for( j = 0; j < total; j++ )
|
|
((float*)dptr)[j] = (float)std::log(fabs(((const float*)sptr)[j]));
|
|
}
|
|
else if( depth == CV_64F )
|
|
{
|
|
for( j = 0; j < total; j++ )
|
|
((double*)dptr)[j] = std::log(fabs(((const double*)sptr)[j]));
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct ExpOp : public BaseArithmOp
|
|
{
|
|
ExpOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_FLT, 1, ARITHM_MAX_CHANNELS);
|
|
}
|
|
void getValueRange(int depth, double& minval, double& maxval)
|
|
{
|
|
maxval = depth == CV_32F ? 80 : 700;
|
|
minval = -maxval;
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
cv::exp(src[0], dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
reference::exp(src[0], dst);
|
|
}
|
|
double getMaxErr(int depth)
|
|
{
|
|
return depth == CV_32F ? 1e-5 : 1e-12;
|
|
}
|
|
};
|
|
|
|
|
|
struct LogOp : public BaseArithmOp
|
|
{
|
|
LogOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)) {}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_FLT, 1, ARITHM_MAX_CHANNELS);
|
|
}
|
|
void getValueRange(int depth, double& minval, double& maxval)
|
|
{
|
|
maxval = depth == CV_32F ? 50 : 100;
|
|
minval = -maxval;
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
Mat temp;
|
|
reference::exp(src[0], temp);
|
|
cv::log(temp, dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
Mat temp;
|
|
reference::exp(src[0], temp);
|
|
reference::log(temp, dst);
|
|
}
|
|
double getMaxErr(int depth)
|
|
{
|
|
return depth == CV_32F ? 1e-5 : 1e-12;
|
|
}
|
|
};
|
|
|
|
|
|
namespace reference {
|
|
static void cartToPolar(const Mat& mx, const Mat& my, Mat& mmag, Mat& mangle, bool angleInDegrees)
|
|
{
|
|
CV_Assert( (mx.type() == CV_32F || mx.type() == CV_64F) &&
|
|
mx.type() == my.type() && mx.size == my.size );
|
|
mmag.create( mx.size, mx.type() );
|
|
mangle.create( mx.size, mx.type() );
|
|
const Mat *arrays[]={&mx, &my, &mmag, &mangle, 0};
|
|
Mat planes[4];
|
|
|
|
NAryMatIterator it(arrays, planes);
|
|
size_t j, total = planes[0].total();
|
|
size_t i, nplanes = it.nplanes;
|
|
int depth = mx.depth();
|
|
double scale = angleInDegrees ? 180/CV_PI : 1;
|
|
|
|
for( i = 0; i < nplanes; i++, ++it )
|
|
{
|
|
if( depth == CV_32F )
|
|
{
|
|
const float* xptr = planes[0].ptr<float>();
|
|
const float* yptr = planes[1].ptr<float>();
|
|
float* mptr = planes[2].ptr<float>();
|
|
float* aptr = planes[3].ptr<float>();
|
|
|
|
for( j = 0; j < total; j++ )
|
|
{
|
|
mptr[j] = std::sqrt(xptr[j]*xptr[j] + yptr[j]*yptr[j]);
|
|
double a = atan2((double)yptr[j], (double)xptr[j]);
|
|
if( a < 0 ) a += CV_PI*2;
|
|
aptr[j] = (float)(a*scale);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const double* xptr = planes[0].ptr<double>();
|
|
const double* yptr = planes[1].ptr<double>();
|
|
double* mptr = planes[2].ptr<double>();
|
|
double* aptr = planes[3].ptr<double>();
|
|
|
|
for( j = 0; j < total; j++ )
|
|
{
|
|
mptr[j] = std::sqrt(xptr[j]*xptr[j] + yptr[j]*yptr[j]);
|
|
double a = atan2(yptr[j], xptr[j]);
|
|
if( a < 0 ) a += CV_PI*2;
|
|
aptr[j] = a*scale;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct CartToPolarToCartOp : public BaseArithmOp
|
|
{
|
|
CartToPolarToCartOp() : BaseArithmOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0))
|
|
{
|
|
context = 3;
|
|
angleInDegrees = true;
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, _OutputArray::DEPTH_MASK_FLT, 1, 1);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
Mat mag, angle, x, y;
|
|
|
|
cv::cartToPolar(src[0], src[1], mag, angle, angleInDegrees);
|
|
cv::polarToCart(mag, angle, x, y, angleInDegrees);
|
|
|
|
Mat msrc[] = {mag, angle, x, y};
|
|
int pairs[] = {0, 0, 1, 1, 2, 2, 3, 3};
|
|
dst.create(src[0].size, CV_MAKETYPE(src[0].depth(), 4));
|
|
cv::mixChannels(msrc, 4, &dst, 1, pairs, 4);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
Mat mag, angle;
|
|
reference::cartToPolar(src[0], src[1], mag, angle, angleInDegrees);
|
|
Mat msrc[] = {mag, angle, src[0], src[1]};
|
|
int pairs[] = {0, 0, 1, 1, 2, 2, 3, 3};
|
|
dst.create(src[0].size, CV_MAKETYPE(src[0].depth(), 4));
|
|
cv::mixChannels(msrc, 4, &dst, 1, pairs, 4);
|
|
}
|
|
void generateScalars(int, RNG& rng)
|
|
{
|
|
angleInDegrees = rng.uniform(0, 2) != 0;
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 1e-3;
|
|
}
|
|
bool angleInDegrees;
|
|
};
|
|
|
|
|
|
struct MeanOp : public BaseArithmOp
|
|
{
|
|
MeanOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK+SCALAR_OUTPUT, 1, 1, Scalar::all(0))
|
|
{
|
|
context = 3;
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
dst.create(1, 1, CV_64FC4);
|
|
dst.at<Scalar>(0,0) = cv::mean(src[0], mask);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
dst.create(1, 1, CV_64FC4);
|
|
dst.at<Scalar>(0,0) = cvtest::mean(src[0], mask);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 1e-5;
|
|
}
|
|
};
|
|
|
|
|
|
struct SumOp : public BaseArithmOp
|
|
{
|
|
SumOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SCALAR_OUTPUT, 1, 1, Scalar::all(0))
|
|
{
|
|
context = 3;
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
dst.create(1, 1, CV_64FC4);
|
|
dst.at<Scalar>(0,0) = cv::sum(src[0]);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&)
|
|
{
|
|
dst.create(1, 1, CV_64FC4);
|
|
dst.at<Scalar>(0,0) = cvtest::mean(src[0])*(double)src[0].total();
|
|
}
|
|
double getMaxErr(int depth)
|
|
{
|
|
return depth == CV_16F || depth == CV_16BF ? 1e-3 : 1e-5;
|
|
}
|
|
};
|
|
|
|
|
|
struct CountNonZeroOp : public BaseArithmOp
|
|
{
|
|
CountNonZeroOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SCALAR_OUTPUT+SUPPORT_MASK, 1, 1, Scalar::all(0))
|
|
{}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, baseArithmTypeMask, 1, 1);
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
Mat temp;
|
|
src[0].copyTo(temp);
|
|
if( !mask.empty() )
|
|
temp.setTo(Scalar::all(0), mask);
|
|
dst.create(1, 1, CV_32S);
|
|
dst.at<int>(0,0) = cv::countNonZero(temp);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
Mat temp;
|
|
cvtest::compare(src[0], 0, temp, CMP_NE);
|
|
if( !mask.empty() )
|
|
cvtest::set(temp, Scalar::all(0), mask);
|
|
dst.create(1, 1, CV_32S);
|
|
dst.at<int>(0,0) = saturate_cast<int>(cvtest::mean(temp)[0]/255*temp.total());
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
struct MeanStdDevOp : public BaseArithmOp
|
|
{
|
|
Scalar sqmeanRef;
|
|
int cn;
|
|
|
|
MeanStdDevOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK+SCALAR_OUTPUT, 1, 1, Scalar::all(0))
|
|
{
|
|
cn = 0;
|
|
context = 7;
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
dst.create(1, 2, CV_64FC4);
|
|
cv::meanStdDev(src[0], dst.at<Scalar>(0,0), dst.at<Scalar>(0,1), mask);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
Mat temp;
|
|
cvtest::convert(src[0], temp, CV_64F);
|
|
cvtest::multiply(temp, temp, temp);
|
|
Scalar mean = cvtest::mean(src[0], mask);
|
|
Scalar sqmean = cvtest::mean(temp, mask);
|
|
|
|
sqmeanRef = sqmean;
|
|
cn = temp.channels();
|
|
|
|
for( int c = 0; c < 4; c++ )
|
|
sqmean[c] = std::sqrt(std::max(sqmean[c] - mean[c]*mean[c], 0.));
|
|
|
|
dst.create(1, 2, CV_64FC4);
|
|
dst.at<Scalar>(0,0) = mean;
|
|
dst.at<Scalar>(0,1) = sqmean;
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
CV_Assert(cn > 0);
|
|
double err = sqmeanRef[0];
|
|
for(int i = 1; i < cn; ++i)
|
|
err = std::max(err, sqmeanRef[i]);
|
|
return 3e-7 * err;
|
|
}
|
|
};
|
|
|
|
|
|
struct NormOp : public BaseArithmOp
|
|
{
|
|
NormOp() : BaseArithmOp(2, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK+SCALAR_OUTPUT, 1, 1, Scalar::all(0))
|
|
{
|
|
context = 1;
|
|
normType = 0;
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
int type = cvtest::randomType(rng, baseArithmTypeMask, 1, 4);
|
|
for(;;)
|
|
{
|
|
normType = rng.uniform(1, 8);
|
|
if( normType == NORM_INF || normType == NORM_L1 ||
|
|
normType == NORM_L2 || normType == NORM_L2SQR ||
|
|
normType == NORM_HAMMING || normType == NORM_HAMMING2 )
|
|
break;
|
|
}
|
|
if( normType == NORM_HAMMING || normType == NORM_HAMMING2 )
|
|
{
|
|
type = CV_8U;
|
|
}
|
|
return type;
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
dst.create(1, 2, CV_64FC1);
|
|
dst.at<double>(0,0) = cv::norm(src[0], normType, mask);
|
|
dst.at<double>(0,1) = cv::norm(src[0], src[1], normType, mask);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
dst.create(1, 2, CV_64FC1);
|
|
dst.at<double>(0,0) = cvtest::norm(src[0], normType, mask);
|
|
dst.at<double>(0,1) = cvtest::norm(src[0], src[1], normType, mask);
|
|
}
|
|
void generateScalars(int, RNG& /*rng*/)
|
|
{
|
|
}
|
|
double getMaxErr(int depth)
|
|
{
|
|
return normType == NORM_INF && depth <= CV_32S ? 0 :
|
|
depth == CV_16F || depth == CV_16BF ? 1e-5 : 1e-6;
|
|
}
|
|
int normType;
|
|
};
|
|
|
|
|
|
struct MinMaxLocOp : public BaseArithmOp
|
|
{
|
|
MinMaxLocOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA+SUPPORT_MASK+SCALAR_OUTPUT, 1, 1, Scalar::all(0))
|
|
{
|
|
context = ARITHM_MAX_NDIMS*2 + 2;
|
|
}
|
|
int getRandomType(RNG& rng)
|
|
{
|
|
return cvtest::randomType(rng, baseArithmTypeMask, 1, 1);
|
|
}
|
|
void saveOutput(const vector<int>& minidx, const vector<int>& maxidx,
|
|
double minval, double maxval, Mat& dst)
|
|
{
|
|
int i, ndims = (int)minidx.size();
|
|
dst.create(1, ndims*2 + 2, CV_64FC1);
|
|
|
|
for( i = 0; i < ndims; i++ )
|
|
{
|
|
dst.at<double>(0,i) = minidx[i];
|
|
dst.at<double>(0,i+ndims) = maxidx[i];
|
|
}
|
|
dst.at<double>(0,ndims*2) = minval;
|
|
dst.at<double>(0,ndims*2+1) = maxval;
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
int ndims = src[0].dims;
|
|
vector<int> minidx(ndims), maxidx(ndims);
|
|
double minval=0, maxval=0;
|
|
cv::minMaxIdx(src[0], &minval, &maxval, &minidx[0], &maxidx[0], mask);
|
|
saveOutput(minidx, maxidx, minval, maxval, dst);
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat& mask)
|
|
{
|
|
int ndims=src[0].dims;
|
|
vector<int> minidx(ndims), maxidx(ndims);
|
|
double minval=0, maxval=0;
|
|
cvtest::minMaxLoc(src[0], &minval, &maxval, &minidx, &maxidx, mask);
|
|
saveOutput(minidx, maxidx, minval, maxval, dst);
|
|
}
|
|
double getMaxErr(int)
|
|
{
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct reduceArgMinMaxOp : public BaseArithmOp
|
|
{
|
|
reduceArgMinMaxOp() : BaseArithmOp(1, FIX_ALPHA+FIX_BETA+FIX_GAMMA, 1, 1, Scalar::all(0)),
|
|
isLast(false), isMax(false), axis(0)
|
|
{
|
|
context = ARITHM_MAX_NDIMS*2 + 2;
|
|
}
|
|
int getRandomType(RNG& rng) override
|
|
{
|
|
return cvtest::randomType(rng, baseArithmTypeMask, 1, 1);
|
|
}
|
|
void getRandomSize(RNG& rng, vector<int>& size) override
|
|
{
|
|
cvtest::randomSize(rng, 2, ARITHM_MAX_NDIMS, 6, size);
|
|
}
|
|
void generateScalars(int depth, RNG& rng) override
|
|
{
|
|
BaseElemWiseOp::generateScalars(depth, rng);
|
|
isLast = (randInt(rng) % 2 == 0);
|
|
isMax = (randInt(rng) % 2 == 0);
|
|
axis = randInt(rng);
|
|
}
|
|
int getAxis(const Mat& src) const
|
|
{
|
|
int dims = src.dims;
|
|
return static_cast<int>(axis % (2 * dims)) - dims; // [-dims; dims - 1]
|
|
}
|
|
void op(const vector<Mat>& src, Mat& dst, const Mat&) override
|
|
{
|
|
const Mat& inp = src[0];
|
|
const int axis_ = getAxis(inp);
|
|
if (isMax)
|
|
{
|
|
cv::reduceArgMax(inp, dst, axis_, isLast);
|
|
}
|
|
else
|
|
{
|
|
cv::reduceArgMin(inp, dst, axis_, isLast);
|
|
}
|
|
}
|
|
void refop(const vector<Mat>& src, Mat& dst, const Mat&) override
|
|
{
|
|
const Mat& inp = src[0];
|
|
const int axis_ = getAxis(inp);
|
|
|
|
if (!isLast && !isMax)
|
|
{
|
|
cvtest::MinMaxReducer<std::less>::reduce(inp, dst, axis_);
|
|
}
|
|
else if (!isLast && isMax)
|
|
{
|
|
cvtest::MinMaxReducer<std::greater>::reduce(inp, dst, axis_);
|
|
}
|
|
else if (isLast && !isMax)
|
|
{
|
|
cvtest::MinMaxReducer<std::less_equal>::reduce(inp, dst, axis_);
|
|
}
|
|
else
|
|
{
|
|
cvtest::MinMaxReducer<std::greater_equal>::reduce(inp, dst, axis_);
|
|
}
|
|
}
|
|
|
|
bool isLast;
|
|
bool isMax;
|
|
uint32_t axis;
|
|
};
|
|
|
|
|
|
typedef Ptr<BaseElemWiseOp> ElemWiseOpPtr;
|
|
class ElemWiseTest : public ::testing::TestWithParam<ElemWiseOpPtr> {};
|
|
|
|
TEST_P(ElemWiseTest, accuracy)
|
|
{
|
|
ElemWiseOpPtr op = GetParam();
|
|
|
|
int testIdx = 0;
|
|
RNG rng((uint64)ARITHM_RNG_SEED);
|
|
for( testIdx = 0; testIdx < ARITHM_NTESTS; testIdx++ )
|
|
{
|
|
vector<int> size;
|
|
op->getRandomSize(rng, size);
|
|
int type = op->getRandomType(rng);
|
|
int depth = CV_MAT_DEPTH(type);
|
|
bool haveMask = ((op->flags & BaseElemWiseOp::SUPPORT_MASK) != 0
|
|
|| (op->flags & BaseElemWiseOp::SUPPORT_MULTICHANNELMASK) != 0) && rng.uniform(0, 4) == 0;
|
|
|
|
double minval=0, maxval=0;
|
|
op->getValueRange(depth, minval, maxval);
|
|
int i, ninputs = op->ninputs;
|
|
vector<Mat> src(ninputs);
|
|
for( i = 0; i < ninputs; i++ )
|
|
src[i] = cvtest::randomMat(rng, size, type, minval, maxval, true);
|
|
Mat dst0, dst, mask;
|
|
if( haveMask ) {
|
|
bool multiChannelMask = (op->flags & BaseElemWiseOp::SUPPORT_MULTICHANNELMASK) != 0
|
|
&& rng.uniform(0, 2) == 0;
|
|
int masktype = CV_8UC(multiChannelMask ? CV_MAT_CN(type) : 1);
|
|
mask = cvtest::randomMat(rng, size, masktype, 0, 2, true);
|
|
}
|
|
|
|
if( (haveMask || ninputs == 0) && !(op->flags & BaseElemWiseOp::SCALAR_OUTPUT))
|
|
{
|
|
dst0 = cvtest::randomMat(rng, size, type, minval, maxval, false);
|
|
dst = cvtest::randomMat(rng, size, type, minval, maxval, true);
|
|
cvtest::copy(dst, dst0);
|
|
}
|
|
op->generateScalars(depth, rng);
|
|
|
|
/*printf("testIdx=%d, depth=%d, channels=%d, have_mask=%d\n", testIdx, depth, src[0].channels(), (int)haveMask);
|
|
if (testIdx == 22)
|
|
printf(">>>\n");*/
|
|
|
|
op->refop(src, dst0, mask);
|
|
op->op(src, dst, mask);
|
|
|
|
double maxErr = op->getMaxErr(depth);
|
|
|
|
ASSERT_PRED_FORMAT2(cvtest::MatComparator(maxErr, op->context), dst0, dst) << "\nsrc[0] ~ " <<
|
|
cvtest::MatInfo(!src.empty() ? src[0] : Mat()) << "\ntestCase #" << testIdx << "\n";
|
|
}
|
|
}
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_Copy, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new CopyOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Set, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new SetOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_SetZero, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new SetZeroOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_ConvertScale, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new ConvertScaleOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_ConvertScaleFp16, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new ConvertScaleFp16Op)));
|
|
INSTANTIATE_TEST_CASE_P(Core_ConvertScaleAbs, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new ConvertScaleAbsOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_Add, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new AddOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Sub, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new SubOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_AddS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new AddSOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_SubRS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new SubRSOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_ScaleAdd, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new ScaleAddOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_AddWeighted, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new AddWeightedOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_AbsDiff, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new AbsDiffOp)));
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_AbsDiffS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new AbsDiffSOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_And, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogicOp('&'))));
|
|
INSTANTIATE_TEST_CASE_P(Core_AndS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogicSOp('&'))));
|
|
INSTANTIATE_TEST_CASE_P(Core_Or, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogicOp('|'))));
|
|
INSTANTIATE_TEST_CASE_P(Core_OrS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogicSOp('|'))));
|
|
INSTANTIATE_TEST_CASE_P(Core_Xor, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogicOp('^'))));
|
|
INSTANTIATE_TEST_CASE_P(Core_XorS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogicSOp('^'))));
|
|
INSTANTIATE_TEST_CASE_P(Core_Not, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogicSOp('~'))));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_Max, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MaxOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_MaxS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MaxSOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Min, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MinOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_MinS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MinSOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_Mul, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MulOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Div, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new DivOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Recip, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new RecipOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_Cmp, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new CmpOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_CmpS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new CmpSOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_InRangeS, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new InRangeSOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_InRange, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new InRangeOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_FiniteMask, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new FiniteMaskOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_Flip, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new FlipOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_FlipInplace, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new FlipInplaceOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Rotate, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new RotateOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Transpose, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new TransposeOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_SetIdentity, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new SetIdentityOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_Exp, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new ExpOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Log, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new LogOp)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_CountNonZero, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new CountNonZeroOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Mean, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MeanOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_MeanStdDev, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MeanStdDevOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Sum, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new SumOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_Norm, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new NormOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_MinMaxLoc, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new MinMaxLocOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_reduceArgMinMax, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new reduceArgMinMaxOp)));
|
|
INSTANTIATE_TEST_CASE_P(Core_CartToPolarToCart, ElemWiseTest, ::testing::Values(ElemWiseOpPtr(new CartToPolarToCartOp)));
|
|
|
|
// Mixed Type Arithmetic Operations
|
|
|
|
typedef std::tuple<ElemWiseOpPtr, std::tuple<cvtest::MatDepth, cvtest::MatDepth>, int> SomeType;
|
|
class ArithmMixedTest : public ::testing::TestWithParam<SomeType> {};
|
|
|
|
TEST_P(ArithmMixedTest, accuracy)
|
|
{
|
|
auto p = GetParam();
|
|
ElemWiseOpPtr op = std::get<0>(p);
|
|
int srcDepth = std::get<0>(std::get<1>(p));
|
|
int dstDepth = std::get<1>(std::get<1>(p));
|
|
int channels = std::get<2>(p);
|
|
|
|
int srcType = CV_MAKETYPE(srcDepth, channels);
|
|
int dstType = CV_MAKETYPE(dstDepth, channels);
|
|
op->flags |= BaseElemWiseOp::MIXED_TYPE;
|
|
int testIdx = 0;
|
|
RNG rng((uint64)ARITHM_RNG_SEED);
|
|
for( testIdx = 0; testIdx < ARITHM_NTESTS; testIdx++ )
|
|
{
|
|
vector<int> size;
|
|
op->getRandomSize(rng, size);
|
|
bool haveMask = ((op->flags & BaseElemWiseOp::SUPPORT_MASK) != 0) && rng.uniform(0, 4) == 0;
|
|
|
|
double minval=0, maxval=0;
|
|
op->getValueRange(srcDepth, minval, maxval);
|
|
int ninputs = op->ninputs;
|
|
vector<Mat> src(ninputs);
|
|
for(int i = 0; i < ninputs; i++ )
|
|
src[i] = cvtest::randomMat(rng, size, srcType, minval, maxval, true);
|
|
Mat dst0, dst, mask;
|
|
if( haveMask )
|
|
{
|
|
mask = cvtest::randomMat(rng, size, CV_8UC1, 0, 2, true);
|
|
}
|
|
|
|
dst0 = cvtest::randomMat(rng, size, dstType, minval, maxval, false);
|
|
dst = cvtest::randomMat(rng, size, dstType, minval, maxval, true);
|
|
cvtest::copy(dst, dst0);
|
|
|
|
op->generateScalars(dstDepth, rng);
|
|
|
|
op->refop(src, dst0, mask);
|
|
op->op(src, dst, mask);
|
|
|
|
double maxErr = op->getMaxErr(dstDepth);
|
|
ASSERT_PRED_FORMAT2(cvtest::MatComparator(maxErr, op->context), dst0, dst) << "\nsrc[0] ~ " <<
|
|
cvtest::MatInfo(!src.empty() ? src[0] : Mat()) << "\ntestCase #" << testIdx << "\n";
|
|
}
|
|
}
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_AddMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new AddOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_AddScalarMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new AddSOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_AddWeightedMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new AddWeightedOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_SubMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new SubOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_SubScalarMinusArgMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new SubRSOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_MulMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new MulOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_MulScalarMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new MulSOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_DivMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new DivOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_16S},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_32F},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
INSTANTIATE_TEST_CASE_P(Core_RecipMixed, ArithmMixedTest,
|
|
::testing::Combine(::testing::Values(ElemWiseOpPtr(new RecipOp)),
|
|
::testing::Values(std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8U, CV_16U},
|
|
std::tuple<cvtest::MatDepth, cvtest::MatDepth>{CV_8S, CV_32F}),
|
|
::testing::Values(1, 3, 4)));
|
|
|
|
TEST(Core_ArithmMask, uninitialized)
|
|
{
|
|
RNG& rng = theRNG();
|
|
const int MAX_DIM=3;
|
|
int sizes[MAX_DIM];
|
|
for( int iter = 0; iter < 100; iter++ )
|
|
{
|
|
int dims = rng.uniform(1, MAX_DIM+1);
|
|
int depth = rng.uniform(CV_8U, CV_64F+1);
|
|
int cn = rng.uniform(1, 6);
|
|
int type = CV_MAKETYPE(depth, cn);
|
|
int op = rng.uniform(0, depth < CV_32F ? 5 : 2); // don't run binary operations between floating-point values
|
|
int depth1 = op <= 1 ? CV_64F : depth;
|
|
for (int k = 0; k < MAX_DIM; k++)
|
|
{
|
|
sizes[k] = k < dims ? rng.uniform(1, 30) : 0;
|
|
}
|
|
SCOPED_TRACE(cv::format("iter=%d dims=%d depth=%d cn=%d type=%d op=%d depth1=%d dims=[%d; %d; %d]",
|
|
iter, dims, depth, cn, type, op, depth1, sizes[0], sizes[1], sizes[2]));
|
|
|
|
Mat a(dims, sizes, type), a1;
|
|
Mat b(dims, sizes, type), b1;
|
|
Mat mask(dims, sizes, CV_8U);
|
|
Mat mask1;
|
|
Mat c, d;
|
|
|
|
rng.fill(a, RNG::UNIFORM, 0, 100);
|
|
rng.fill(b, RNG::UNIFORM, 0, 100);
|
|
|
|
// [-2,2) range means that the each generated random number
|
|
// will be one of -2, -1, 0, 1. Saturated to [0,255], it will become
|
|
// 0, 0, 0, 1 => the mask will be filled by ~25%.
|
|
rng.fill(mask, RNG::UNIFORM, -2, 2);
|
|
|
|
a.convertTo(a1, depth1);
|
|
b.convertTo(b1, depth1);
|
|
// invert the mask
|
|
cv::compare(mask, 0, mask1, CMP_EQ);
|
|
a1.setTo(0, mask1);
|
|
b1.setTo(0, mask1);
|
|
|
|
if( op == 0 )
|
|
{
|
|
cv::add(a, b, c, mask);
|
|
cv::add(a1, b1, d);
|
|
}
|
|
else if( op == 1 )
|
|
{
|
|
cv::subtract(a, b, c, mask);
|
|
cv::subtract(a1, b1, d);
|
|
}
|
|
else if( op == 2 )
|
|
{
|
|
cv::bitwise_and(a, b, c, mask);
|
|
cv::bitwise_and(a1, b1, d);
|
|
}
|
|
else if( op == 3 )
|
|
{
|
|
cv::bitwise_or(a, b, c, mask);
|
|
cv::bitwise_or(a1, b1, d);
|
|
}
|
|
else if( op == 4 )
|
|
{
|
|
cv::bitwise_xor(a, b, c, mask);
|
|
cv::bitwise_xor(a1, b1, d);
|
|
}
|
|
Mat d1;
|
|
d.convertTo(d1, depth);
|
|
EXPECT_LE(cvtest::norm(c, d1, NORM_INF), DBL_EPSILON);
|
|
}
|
|
|
|
Mat_<uchar> tmpSrc(100,100);
|
|
tmpSrc = 124;
|
|
Mat_<uchar> tmpMask(100,100);
|
|
tmpMask = 255;
|
|
Mat_<uchar> tmpDst(100,100);
|
|
tmpDst = 2;
|
|
tmpSrc.copyTo(tmpDst,tmpMask);
|
|
}
|
|
|
|
TEST(Multiply, FloatingPointRounding)
|
|
{
|
|
cv::Mat src(1, 1, CV_8UC1, cv::Scalar::all(110)), dst;
|
|
cv::Scalar s(147.286359696927, 1, 1 ,1);
|
|
|
|
cv::multiply(src, s, dst, 1, CV_16U);
|
|
// with CV_32F this produce result 16202
|
|
ASSERT_EQ(dst.at<ushort>(0,0), 16201);
|
|
}
|
|
|
|
TEST(Core_Add, AddToColumnWhen3Rows)
|
|
{
|
|
cv::Mat m1 = (cv::Mat_<double>(3, 2) << 1, 2, 3, 4, 5, 6);
|
|
m1.col(1) += 10;
|
|
|
|
cv::Mat m2 = (cv::Mat_<double>(3, 2) << 1, 12, 3, 14, 5, 16);
|
|
cv::MatExpr diff = m1 - m2;
|
|
int nz = countNonZero(diff);
|
|
|
|
ASSERT_EQ(0, nz);
|
|
}
|
|
|
|
TEST(Core_Add, AddToColumnWhen4Rows)
|
|
{
|
|
cv::Mat m1 = (cv::Mat_<double>(4, 2) << 1, 2, 3, 4, 5, 6, 7, 8);
|
|
m1.col(1) += 10;
|
|
|
|
cv::Mat m2 = (cv::Mat_<double>(4, 2) << 1, 12, 3, 14, 5, 16, 7, 18);
|
|
|
|
ASSERT_EQ(0, countNonZero(m1 - m2));
|
|
}
|
|
|
|
TEST(Core_round, CvRound)
|
|
{
|
|
ASSERT_EQ(2, cvRound(2.0));
|
|
ASSERT_EQ(2, cvRound(2.1));
|
|
ASSERT_EQ(-2, cvRound(-2.1));
|
|
ASSERT_EQ(3, cvRound(2.8));
|
|
ASSERT_EQ(-3, cvRound(-2.8));
|
|
ASSERT_EQ(2, cvRound(2.5));
|
|
ASSERT_EQ(4, cvRound(3.5));
|
|
ASSERT_EQ(-2, cvRound(-2.5));
|
|
ASSERT_EQ(-4, cvRound(-3.5));
|
|
}
|
|
|
|
|
|
typedef testing::TestWithParam<Size> Mul1;
|
|
|
|
TEST_P(Mul1, One)
|
|
{
|
|
Size size = GetParam();
|
|
cv::Mat src(size, CV_32FC1, cv::Scalar::all(2)), dst,
|
|
ref_dst(size, CV_32FC1, cv::Scalar::all(6));
|
|
|
|
cv::multiply(3, src, dst);
|
|
|
|
ASSERT_EQ(0, cvtest::norm(dst, ref_dst, cv::NORM_INF));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(Arithm, Mul1, testing::Values(Size(2, 2), Size(1, 1)));
|
|
|
|
class SubtractOutputMatNotEmpty : public testing::TestWithParam< tuple<cv::Size, perf::MatType, perf::MatDepth, bool> >
|
|
{
|
|
public:
|
|
cv::Size size;
|
|
int src_type;
|
|
int dst_depth;
|
|
bool fixed;
|
|
|
|
void SetUp()
|
|
{
|
|
size = get<0>(GetParam());
|
|
src_type = get<1>(GetParam());
|
|
dst_depth = get<2>(GetParam());
|
|
fixed = get<3>(GetParam());
|
|
}
|
|
};
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Mat_Mat)
|
|
{
|
|
cv::Mat src1(size, src_type, cv::Scalar::all(16));
|
|
cv::Mat src2(size, src_type, cv::Scalar::all(16));
|
|
|
|
cv::Mat dst;
|
|
|
|
if (!fixed)
|
|
{
|
|
cv::subtract(src1, src2, dst, cv::noArray(), dst_depth);
|
|
}
|
|
else
|
|
{
|
|
const cv::Mat fixed_dst(size, CV_MAKE_TYPE((dst_depth > 0 ? dst_depth : CV_16S), src1.channels()));
|
|
cv::subtract(src1, src2, fixed_dst, cv::noArray(), dst_depth);
|
|
dst = fixed_dst;
|
|
dst_depth = fixed_dst.depth();
|
|
}
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src1.size(), dst.size());
|
|
ASSERT_EQ(dst_depth > 0 ? dst_depth : src1.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Mat_Mat_WithMask)
|
|
{
|
|
cv::Mat src1(size, src_type, cv::Scalar::all(16));
|
|
cv::Mat src2(size, src_type, cv::Scalar::all(16));
|
|
cv::Mat mask(size, CV_8UC1, cv::Scalar::all(255));
|
|
|
|
cv::Mat dst;
|
|
|
|
if (!fixed)
|
|
{
|
|
cv::subtract(src1, src2, dst, mask, dst_depth);
|
|
}
|
|
else
|
|
{
|
|
const cv::Mat fixed_dst(size, CV_MAKE_TYPE((dst_depth > 0 ? dst_depth : CV_16S), src1.channels()));
|
|
cv::subtract(src1, src2, fixed_dst, mask, dst_depth);
|
|
dst = fixed_dst;
|
|
dst_depth = fixed_dst.depth();
|
|
}
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src1.size(), dst.size());
|
|
ASSERT_EQ(dst_depth > 0 ? dst_depth : src1.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Mat_Mat_Expr)
|
|
{
|
|
cv::Mat src1(size, src_type, cv::Scalar::all(16));
|
|
cv::Mat src2(size, src_type, cv::Scalar::all(16));
|
|
|
|
cv::Mat dst = src1 - src2;
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src1.size(), dst.size());
|
|
ASSERT_EQ(src1.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Mat_Scalar)
|
|
{
|
|
cv::Mat src(size, src_type, cv::Scalar::all(16));
|
|
|
|
cv::Mat dst;
|
|
|
|
if (!fixed)
|
|
{
|
|
cv::subtract(src, cv::Scalar::all(16), dst, cv::noArray(), dst_depth);
|
|
}
|
|
else
|
|
{
|
|
const cv::Mat fixed_dst(size, CV_MAKE_TYPE((dst_depth > 0 ? dst_depth : CV_16S), src.channels()));
|
|
cv::subtract(src, cv::Scalar::all(16), fixed_dst, cv::noArray(), dst_depth);
|
|
dst = fixed_dst;
|
|
dst_depth = fixed_dst.depth();
|
|
}
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src.size(), dst.size());
|
|
ASSERT_EQ(dst_depth > 0 ? dst_depth : src.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Mat_Scalar_WithMask)
|
|
{
|
|
cv::Mat src(size, src_type, cv::Scalar::all(16));
|
|
cv::Mat mask(size, CV_8UC1, cv::Scalar::all(255));
|
|
|
|
cv::Mat dst;
|
|
|
|
if (!fixed)
|
|
{
|
|
cv::subtract(src, cv::Scalar::all(16), dst, mask, dst_depth);
|
|
}
|
|
else
|
|
{
|
|
const cv::Mat fixed_dst(size, CV_MAKE_TYPE((dst_depth > 0 ? dst_depth : CV_16S), src.channels()));
|
|
cv::subtract(src, cv::Scalar::all(16), fixed_dst, mask, dst_depth);
|
|
dst = fixed_dst;
|
|
dst_depth = fixed_dst.depth();
|
|
}
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src.size(), dst.size());
|
|
ASSERT_EQ(dst_depth > 0 ? dst_depth : src.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Scalar_Mat)
|
|
{
|
|
cv::Mat src(size, src_type, cv::Scalar::all(16));
|
|
|
|
cv::Mat dst;
|
|
|
|
if (!fixed)
|
|
{
|
|
cv::subtract(cv::Scalar::all(16), src, dst, cv::noArray(), dst_depth);
|
|
}
|
|
else
|
|
{
|
|
const cv::Mat fixed_dst(size, CV_MAKE_TYPE((dst_depth > 0 ? dst_depth : CV_16S), src.channels()));
|
|
cv::subtract(cv::Scalar::all(16), src, fixed_dst, cv::noArray(), dst_depth);
|
|
dst = fixed_dst;
|
|
dst_depth = fixed_dst.depth();
|
|
}
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src.size(), dst.size());
|
|
ASSERT_EQ(dst_depth > 0 ? dst_depth : src.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Scalar_Mat_WithMask)
|
|
{
|
|
cv::Mat src(size, src_type, cv::Scalar::all(16));
|
|
cv::Mat mask(size, CV_8UC1, cv::Scalar::all(255));
|
|
|
|
cv::Mat dst;
|
|
|
|
if (!fixed)
|
|
{
|
|
cv::subtract(cv::Scalar::all(16), src, dst, mask, dst_depth);
|
|
}
|
|
else
|
|
{
|
|
const cv::Mat fixed_dst(size, CV_MAKE_TYPE((dst_depth > 0 ? dst_depth : CV_16S), src.channels()));
|
|
cv::subtract(cv::Scalar::all(16), src, fixed_dst, mask, dst_depth);
|
|
dst = fixed_dst;
|
|
dst_depth = fixed_dst.depth();
|
|
}
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src.size(), dst.size());
|
|
ASSERT_EQ(dst_depth > 0 ? dst_depth : src.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
TEST_P(SubtractOutputMatNotEmpty, Mat_Mat_3d)
|
|
{
|
|
int dims[] = {5, size.height, size.width};
|
|
|
|
cv::Mat src1(3, dims, src_type, cv::Scalar::all(16));
|
|
cv::Mat src2(3, dims, src_type, cv::Scalar::all(16));
|
|
|
|
cv::Mat dst;
|
|
|
|
if (!fixed)
|
|
{
|
|
cv::subtract(src1, src2, dst, cv::noArray(), dst_depth);
|
|
}
|
|
else
|
|
{
|
|
const cv::Mat fixed_dst(3, dims, CV_MAKE_TYPE((dst_depth > 0 ? dst_depth : CV_16S), src1.channels()));
|
|
cv::subtract(src1, src2, fixed_dst, cv::noArray(), dst_depth);
|
|
dst = fixed_dst;
|
|
dst_depth = fixed_dst.depth();
|
|
}
|
|
|
|
ASSERT_FALSE(dst.empty());
|
|
ASSERT_EQ(src1.dims, dst.dims);
|
|
ASSERT_EQ(src1.size, dst.size);
|
|
ASSERT_EQ(dst_depth > 0 ? dst_depth : src1.depth(), dst.depth());
|
|
ASSERT_EQ(0, cv::countNonZero(dst.reshape(1)));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(Arithm, SubtractOutputMatNotEmpty, testing::Combine(
|
|
testing::Values(cv::Size(16, 16), cv::Size(13, 13), cv::Size(16, 13), cv::Size(13, 16)),
|
|
testing::Values(perf::MatType(CV_8UC1), CV_8UC3, CV_8UC4, CV_16SC1, CV_16SC3),
|
|
testing::Values(-1, CV_16S, CV_32S, CV_32F),
|
|
testing::Bool()));
|
|
|
|
TEST(Core_FindNonZero, regression)
|
|
{
|
|
Mat img(10, 10, CV_8U, Scalar::all(0));
|
|
vector<Point> pts, pts2(5);
|
|
findNonZero(img, pts);
|
|
findNonZero(img, pts2);
|
|
ASSERT_TRUE(pts.empty() && pts2.empty());
|
|
|
|
RNG rng((uint64)-1);
|
|
size_t nz = 0;
|
|
for( int i = 0; i < 10; i++ )
|
|
{
|
|
int idx = rng.uniform(0, img.rows*img.cols);
|
|
if( !img.data[idx] ) nz++;
|
|
img.data[idx] = (uchar)rng.uniform(1, 256);
|
|
}
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_8S );
|
|
pts.clear();
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_16U );
|
|
pts.resize(pts.size()*2);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_16S );
|
|
pts.resize(pts.size()*3);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_32S );
|
|
pts.resize(pts.size()*4);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_32U );
|
|
pts.resize(pts.size()*3);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_64U );
|
|
pts.resize(pts.size()*2);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_64S );
|
|
pts.resize(pts.size()*5);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_16F );
|
|
pts.resize(pts.size()*3);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_16BF );
|
|
pts.resize(pts.size()*4);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_32F );
|
|
pts.resize(pts.size()*5);
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
|
|
img.convertTo( img, CV_64F );
|
|
pts.clear();
|
|
findNonZero(img, pts);
|
|
ASSERT_TRUE(pts.size() == nz);
|
|
}
|
|
|
|
TEST(Core_BoolVector, support)
|
|
{
|
|
std::vector<bool> test;
|
|
int i, n = 205;
|
|
int nz = 0;
|
|
test.resize(n);
|
|
for( i = 0; i < n; i++ )
|
|
{
|
|
test[i] = theRNG().uniform(0, 2) != 0;
|
|
nz += (int)test[i];
|
|
}
|
|
ASSERT_EQ( nz, countNonZero(test) );
|
|
ASSERT_FLOAT_EQ((float)nz/n, (float)(cv::mean(test)[0]));
|
|
}
|
|
|
|
TEST(MinMaxLoc, Mat_UcharMax_Without_Loc)
|
|
{
|
|
Mat_<uchar> mat(50, 50);
|
|
uchar iMaxVal = std::numeric_limits<uchar>::max();
|
|
mat.setTo(iMaxVal);
|
|
|
|
double min, max;
|
|
Point minLoc, maxLoc;
|
|
|
|
minMaxLoc(mat, &min, &max, &minLoc, &maxLoc, Mat());
|
|
|
|
ASSERT_EQ(iMaxVal, min);
|
|
ASSERT_EQ(iMaxVal, max);
|
|
|
|
ASSERT_EQ(Point(0, 0), minLoc);
|
|
ASSERT_EQ(Point(0, 0), maxLoc);
|
|
}
|
|
|
|
TEST(MinMaxLoc, Mat_IntMax_Without_Mask)
|
|
{
|
|
Mat_<int> mat(50, 50);
|
|
int iMaxVal = std::numeric_limits<int>::max();
|
|
mat.setTo(iMaxVal);
|
|
|
|
double min, max;
|
|
Point minLoc, maxLoc;
|
|
|
|
minMaxLoc(mat, &min, &max, &minLoc, &maxLoc, Mat());
|
|
|
|
ASSERT_EQ(iMaxVal, min);
|
|
ASSERT_EQ(iMaxVal, max);
|
|
|
|
ASSERT_EQ(Point(0, 0), minLoc);
|
|
ASSERT_EQ(Point(0, 0), maxLoc);
|
|
}
|
|
|
|
TEST(Normalize, regression_5876_inplace_change_type)
|
|
{
|
|
double initial_values[] = {1, 2, 5, 4, 3};
|
|
float result_values[] = {0, 0.25, 1, 0.75, 0.5};
|
|
Mat m(Size(5, 1), CV_64FC1, initial_values);
|
|
Mat result(Size(5, 1), CV_32FC1, result_values);
|
|
|
|
normalize(m, m, 1, 0, NORM_MINMAX, CV_32F);
|
|
EXPECT_EQ(0, cvtest::norm(m, result, NORM_INF));
|
|
}
|
|
|
|
TEST(Normalize, regression_6125)
|
|
{
|
|
float initial_values[] = {
|
|
1888, 1692, 369, 263, 199,
|
|
280, 326, 129, 143, 126,
|
|
233, 221, 130, 126, 150,
|
|
249, 575, 574, 63, 12
|
|
};
|
|
|
|
Mat src(Size(20, 1), CV_32F, initial_values);
|
|
float min = 0., max = 400.;
|
|
normalize(src, src, 0, 400, NORM_MINMAX, CV_32F);
|
|
for(int i = 0; i < 20; i++)
|
|
{
|
|
EXPECT_GE(src.at<float>(i), min) << "Value should be >= 0";
|
|
EXPECT_LE(src.at<float>(i), max) << "Value should be <= 400";
|
|
}
|
|
}
|
|
|
|
TEST(MinMaxLoc, regression_4955_nans)
|
|
{
|
|
cv::Mat one_mat(2, 2, CV_32F, cv::Scalar(1));
|
|
cv::minMaxLoc(one_mat, NULL, NULL, NULL, NULL);
|
|
|
|
cv::Mat nan_mat(2, 2, CV_32F, cv::Scalar(std::numeric_limits<float>::quiet_NaN()));
|
|
cv::minMaxLoc(nan_mat, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
TEST(Subtract, scalarc1_matc3)
|
|
{
|
|
int scalar = 255;
|
|
cv::Mat srcImage(5, 5, CV_8UC3, cv::Scalar::all(5)), destImage;
|
|
cv::subtract(scalar, srcImage, destImage);
|
|
|
|
ASSERT_EQ(0, cv::norm(cv::Mat(5, 5, CV_8UC3, cv::Scalar::all(250)), destImage, cv::NORM_INF));
|
|
}
|
|
|
|
TEST(Subtract, scalarc4_matc4)
|
|
{
|
|
cv::Scalar sc(255, 255, 255, 255);
|
|
cv::Mat srcImage(5, 5, CV_8UC4, cv::Scalar::all(5)), destImage;
|
|
cv::subtract(sc, srcImage, destImage);
|
|
|
|
ASSERT_EQ(0, cv::norm(cv::Mat(5, 5, CV_8UC4, cv::Scalar::all(250)), destImage, cv::NORM_INF));
|
|
}
|
|
|
|
TEST(Compare, empty)
|
|
{
|
|
cv::Mat temp, dst1, dst2;
|
|
EXPECT_NO_THROW(cv::compare(temp, temp, dst1, cv::CMP_EQ));
|
|
EXPECT_TRUE(dst1.empty());
|
|
EXPECT_THROW(dst2 = temp > 5, cv::Exception);
|
|
}
|
|
|
|
TEST(Compare, regression_8999)
|
|
{
|
|
// Issue #8999 predates broadcasting element-wise ops: comparing a 4x1 array against a 1x1 operand
|
|
// used to throw (both look like a Scalar). It now broadcasts the 1x1 operand across the 4x1 array.
|
|
Mat_<double> A(4,1); A << 1, 3, 2, 4;
|
|
Mat_<double> B(1,1); B << 2;
|
|
Mat C;
|
|
cv::compare(A, B, C, CMP_LT);
|
|
Mat expected = (Mat_<uchar>(4,1) << 255, 0, 0, 0); // A < 2
|
|
EXPECT_EQ(0, cvtest::norm(C, expected, NORM_INF));
|
|
}
|
|
|
|
TEST(Compare, regression_16F_do_not_crash)
|
|
{
|
|
cv::Mat mat1(2, 2, CV_16F, cv::Scalar(1));
|
|
cv::Mat mat2(2, 2, CV_16F, cv::Scalar(2));
|
|
cv::Mat dst;
|
|
EXPECT_NO_THROW(cv::compare(mat1, mat2, dst, cv::CMP_EQ));
|
|
}
|
|
|
|
TEST(Core_minMaxIdx, regression_9207_1)
|
|
{
|
|
const int rows = 4;
|
|
const int cols = 3;
|
|
uchar mask_[rows*cols] = {
|
|
255, 255, 255,
|
|
255, 0, 255,
|
|
0, 255, 255,
|
|
0, 0, 255
|
|
};
|
|
uchar src_[rows*cols] = {
|
|
1, 1, 1,
|
|
1, 1, 1,
|
|
2, 1, 1,
|
|
2, 2, 1
|
|
};
|
|
Mat mask(Size(cols, rows), CV_8UC1, mask_);
|
|
Mat src(Size(cols, rows), CV_8UC1, src_);
|
|
double minVal = -0.0, maxVal = -0.0;
|
|
int minIdx[2] = { -2, -2 }, maxIdx[2] = { -2, -2 };
|
|
cv::minMaxIdx(src, &minVal, &maxVal, minIdx, maxIdx, mask);
|
|
EXPECT_EQ(0, minIdx[0]);
|
|
EXPECT_EQ(0, minIdx[1]);
|
|
EXPECT_EQ(0, maxIdx[0]);
|
|
EXPECT_EQ(0, maxIdx[1]);
|
|
}
|
|
|
|
class TransposeND : public testing::TestWithParam< tuple<std::vector<int>, perf::MatType> >
|
|
{
|
|
public:
|
|
std::vector<int> m_shape;
|
|
int m_type;
|
|
|
|
void SetUp()
|
|
{
|
|
std::tie(m_shape, m_type) = GetParam();
|
|
}
|
|
};
|
|
|
|
|
|
TEST_P(TransposeND, basic)
|
|
{
|
|
Mat inp(m_shape, m_type);
|
|
randu(inp, 0, 255);
|
|
|
|
std::vector<int> order(m_shape.size());
|
|
std::iota(order.begin(), order.end(), 0);
|
|
auto transposer = [&order] (const std::vector<int>& id)
|
|
{
|
|
std::vector<int> ret(id.size());
|
|
for (size_t i = 0; i < id.size(); ++i)
|
|
{
|
|
ret[i] = id[order[i]];
|
|
}
|
|
return ret;
|
|
};
|
|
auto advancer = [&inp] (std::vector<int>& id)
|
|
{
|
|
for (int j = static_cast<int>(id.size() - 1); j >= 0; --j)
|
|
{
|
|
++id[j];
|
|
if (id[j] != inp.size[j])
|
|
{
|
|
break;
|
|
}
|
|
id[j] = 0;
|
|
}
|
|
};
|
|
|
|
do
|
|
{
|
|
Mat out;
|
|
cv::transposeND(inp, order, out);
|
|
std::vector<int> id(order.size());
|
|
for (size_t i = 0; i < inp.total(); ++i)
|
|
{
|
|
auto new_id = transposer(id);
|
|
switch (inp.type())
|
|
{
|
|
case CV_8UC1:
|
|
ASSERT_EQ(inp.at<uint8_t>(id.data()), out.at<uint8_t>(new_id.data()));
|
|
break;
|
|
case CV_32FC1:
|
|
ASSERT_EQ(inp.at<float>(id.data()), out.at<float>(new_id.data()));
|
|
break;
|
|
default:
|
|
FAIL() << "Unsupported type: " << inp.type();
|
|
}
|
|
advancer(id);
|
|
}
|
|
} while (std::next_permutation(order.begin(), order.end()));
|
|
}
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(Arithm, TransposeND, testing::Combine(
|
|
testing::Values(std::vector<int>{2, 3, 4}, std::vector<int>{5, 10}),
|
|
testing::Values(perf::MatType(CV_8UC1), CV_32FC1)
|
|
));
|
|
|
|
class FlipND : public testing::TestWithParam< tuple<std::vector<int>, perf::MatType> >
|
|
{
|
|
public:
|
|
std::vector<int> m_shape;
|
|
int m_type;
|
|
|
|
void SetUp()
|
|
{
|
|
std::tie(m_shape, m_type) = GetParam();
|
|
}
|
|
};
|
|
|
|
TEST_P(FlipND, basic)
|
|
{
|
|
Mat inp(m_shape, m_type);
|
|
randu(inp, 0, 255);
|
|
|
|
int ndim = static_cast<int>(m_shape.size());
|
|
std::vector<int> axes(ndim*2); // [-shape, shape)
|
|
std::iota(axes.begin(), axes.end(), -ndim);
|
|
auto get_flipped_indices = [&inp, ndim] (size_t total, std::vector<int>& indices, int axis)
|
|
{
|
|
const int* shape = inp.size.p;
|
|
size_t t = total, idx;
|
|
for (int i = ndim - 1; i >= 0; --i)
|
|
{
|
|
idx = t / shape[i];
|
|
indices[i] = int(t - idx * shape[i]);
|
|
t = idx;
|
|
}
|
|
|
|
int _axis = (axis + ndim) % ndim;
|
|
std::vector<int> flipped_indices = indices;
|
|
flipped_indices[_axis] = shape[_axis] - 1 - indices[_axis];
|
|
return flipped_indices;
|
|
};
|
|
|
|
for (size_t i = 0; i < axes.size(); ++i)
|
|
{
|
|
int axis = axes[i];
|
|
Mat out;
|
|
cv::flipND(inp, out, axis);
|
|
// check values
|
|
std::vector<int> indices(ndim, 0);
|
|
for (size_t j = 0; j < inp.total(); ++j)
|
|
{
|
|
auto flipped_indices = get_flipped_indices(j, indices, axis);
|
|
switch (inp.type())
|
|
{
|
|
case CV_8UC1:
|
|
ASSERT_EQ(inp.at<uint8_t>(indices.data()), out.at<uint8_t>(flipped_indices.data()));
|
|
break;
|
|
case CV_32FC1:
|
|
ASSERT_EQ(inp.at<float>(indices.data()), out.at<float>(flipped_indices.data()));
|
|
break;
|
|
default:
|
|
FAIL() << "Unsupported type: " << inp.type();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(Arithm, FlipND, testing::Combine(
|
|
testing::Values(std::vector<int>{5, 10}, std::vector<int>{2, 3, 4}),
|
|
testing::Values(perf::MatType(CV_8UC1), CV_32FC1)
|
|
));
|
|
|
|
TEST(BroadcastTo, basic) {
|
|
std::vector<int> shape_src{2, 1};
|
|
std::vector<int> data_src{1, 2};
|
|
Mat src(static_cast<int>(shape_src.size()), shape_src.data(), CV_32SC1, data_src.data());
|
|
|
|
auto get_index = [](const std::vector<int>& shape, size_t cnt) {
|
|
std::vector<int> index(shape.size());
|
|
size_t t = cnt;
|
|
for (int i = static_cast<int>(shape.size() - 1); i >= 0; --i) {
|
|
size_t idx = t / shape[i];
|
|
index[i] = static_cast<int>(t - idx * shape[i]);
|
|
t = idx;
|
|
}
|
|
return index;
|
|
};
|
|
|
|
auto fn_verify = [&get_index](const Mat& ref, const Mat& res) {
|
|
// check type
|
|
EXPECT_EQ(ref.type(), res.type());
|
|
// check shape
|
|
EXPECT_EQ(ref.dims, res.dims);
|
|
for (int i = 0; i < ref.dims; ++i) {
|
|
EXPECT_EQ(ref.size[i], res.size[i]);
|
|
}
|
|
// check value
|
|
std::vector<int> shape{ref.size.p, ref.size.p + ref.dims};
|
|
for (size_t i = 0; i < ref.total(); ++i) {
|
|
auto index = get_index(shape, i);
|
|
switch (ref.type()) {
|
|
case CV_32SC1: {
|
|
ASSERT_EQ(ref.at<int>(index.data()), res.at<int>(index.data()));
|
|
} break;
|
|
case CV_8UC1: {
|
|
ASSERT_EQ(ref.at<uint8_t>(index.data()), res.at<uint8_t>(index.data()));
|
|
} break;
|
|
case CV_32FC1: {
|
|
ASSERT_EQ(ref.at<float>(index.data()), res.at<float>(index.data()));
|
|
} break;
|
|
default: FAIL() << "Unsupported type: " << ref.type();
|
|
}
|
|
}
|
|
};
|
|
|
|
{
|
|
std::vector<int> shape{4, 2, 3};
|
|
std::vector<int> data_ref{
|
|
1, 1, 1, // [0, 0, :]
|
|
2, 2, 2, // [0, 1, :]
|
|
1, 1, 1, // [1, 0, :]
|
|
2, 2, 2, // [1, 1, :]
|
|
1, 1, 1, // [2, 0, :]
|
|
2, 2, 2, // [2, 1, :]
|
|
1, 1, 1, // [3, 0, :]
|
|
2, 2, 2 // [3, 1, :]
|
|
};
|
|
Mat ref(static_cast<int>(shape.size()), shape.data(), src.type(), data_ref.data());
|
|
Mat dst;
|
|
broadcast(src, shape, dst);
|
|
fn_verify(ref, dst);
|
|
}
|
|
|
|
{
|
|
Mat _src;
|
|
src.convertTo(_src, CV_8U);
|
|
std::vector<int> shape{4, 2, 3};
|
|
std::vector<uint8_t> data_ref{
|
|
1, 1, 1, // [0, 0, :]
|
|
2, 2, 2, // [0, 1, :]
|
|
1, 1, 1, // [1, 0, :]
|
|
2, 2, 2, // [1, 1, :]
|
|
1, 1, 1, // [2, 0, :]
|
|
2, 2, 2, // [2, 1, :]
|
|
1, 1, 1, // [3, 0, :]
|
|
2, 2, 2 // [3, 1, :]
|
|
};
|
|
Mat ref(static_cast<int>(shape.size()), shape.data(), _src.type(), data_ref.data());
|
|
Mat dst;
|
|
broadcast(_src, shape, dst);
|
|
fn_verify(ref, dst);
|
|
}
|
|
|
|
{
|
|
Mat _src;
|
|
src.convertTo(_src, CV_32F);
|
|
std::vector<int> shape{1, 1, 2, 1}; // {2, 1}
|
|
std::vector<float> data_ref{
|
|
1.f, // [0, 0, 0, 0]
|
|
2.f, // [0, 0, 1, 0]
|
|
};
|
|
Mat ref(static_cast<int>(shape.size()), shape.data(), _src.type(), data_ref.data());
|
|
Mat dst;
|
|
broadcast(_src, shape, dst);
|
|
fn_verify(ref, dst);
|
|
}
|
|
|
|
{
|
|
std::vector<int> _shape_src{2, 3, 4};
|
|
std::vector<float> _data_src{
|
|
1.f, 2.f, 3.f, 4.f, // [0, 0, :]
|
|
2.f, 3.f, 4.f, 5.f, // [0, 1, :]
|
|
3.f, 4.f, 5.f, 6.f, // [0, 2, :]
|
|
|
|
4.f, 5.f, 6.f, 7.f, // [1, 0, :]
|
|
5.f, 6.f, 7.f, 8.f, // [1, 1, :]
|
|
6.f, 7.f, 8.f, 9.f, // [1, 2, :]
|
|
};
|
|
Mat _src(static_cast<int>(_shape_src.size()), _shape_src.data(), CV_32FC1, _data_src.data());
|
|
|
|
std::vector<int> shape{2, 1, 2, 3, 4};
|
|
std::vector<float> data_ref{
|
|
1.f, 2.f, 3.f, 4.f, // [0, 0, 0, 0, :]
|
|
2.f, 3.f, 4.f, 5.f, // [0, 0, 0, 1, :]
|
|
3.f, 4.f, 5.f, 6.f, // [0, 0, 0, 2, :]
|
|
|
|
4.f, 5.f, 6.f, 7.f, // [0, 0, 1, 0, :]
|
|
5.f, 6.f, 7.f, 8.f, // [0, 0, 1, 1, :]
|
|
6.f, 7.f, 8.f, 9.f, // [0, 0, 1, 2, :]
|
|
|
|
1.f, 2.f, 3.f, 4.f, // [1, 0, 0, 0, :]
|
|
2.f, 3.f, 4.f, 5.f, // [1, 0, 0, 1, :]
|
|
3.f, 4.f, 5.f, 6.f, // [1, 0, 0, 2, :]
|
|
|
|
4.f, 5.f, 6.f, 7.f, // [1, 0, 1, 0, :]
|
|
5.f, 6.f, 7.f, 8.f, // [1, 0, 1, 1, :]
|
|
6.f, 7.f, 8.f, 9.f, // [1, 0, 1, 2, :]
|
|
};
|
|
Mat ref(static_cast<int>(shape.size()), shape.data(), _src.type(), data_ref.data());
|
|
Mat dst;
|
|
broadcast(_src, shape, dst);
|
|
fn_verify(ref, dst);
|
|
}
|
|
}
|
|
|
|
TEST(BroadcastTo, regression_dst_dp_zero_when_last_dim_is_one)
|
|
{
|
|
std::vector<int> shape_src{10, 1, 1};
|
|
std::vector<float> data_src(10);
|
|
for (int i = 0; i < 10; ++i)
|
|
{
|
|
data_src[i] = static_cast<float>(i + 1);
|
|
}
|
|
Mat src(static_cast<int>(shape_src.size()), shape_src.data(), CV_32FC1, data_src.data());
|
|
|
|
std::vector<int> shape_dst{10, 5, 1};
|
|
Mat dst;
|
|
|
|
// Regression for broadcast() path where the innermost destination dimension is 1
|
|
// and flattened destination step can legitimately be 0.
|
|
ASSERT_NO_THROW(broadcast(src, shape_dst, dst));
|
|
|
|
EXPECT_EQ(dst.dims, 3);
|
|
EXPECT_EQ(dst.size[0], 10);
|
|
EXPECT_EQ(dst.size[1], 5);
|
|
EXPECT_EQ(dst.size[2], 1);
|
|
EXPECT_EQ(dst.type(), CV_32FC1);
|
|
|
|
for (int i = 0; i < shape_dst[0]; ++i)
|
|
{
|
|
for (int j = 0; j < shape_dst[1]; ++j)
|
|
{
|
|
int idx[] = {i, j, 0};
|
|
EXPECT_FLOAT_EQ(dst.at<float>(idx), static_cast<float>(i + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(Core_minMaxIdx, regression_9207_2)
|
|
{
|
|
const int rows = 13;
|
|
const int cols = 15;
|
|
uchar mask_[rows*cols] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
|
|
0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
|
|
255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255,
|
|
255, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 255,
|
|
255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 255, 255, 255, 0,
|
|
255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 255, 0,
|
|
255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 0,
|
|
255, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 0,
|
|
255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
uchar src_[15*13] = {
|
|
5, 5, 5, 5, 5, 6, 5, 2, 0, 4, 6, 6, 4, 1, 0,
|
|
6, 5, 4, 4, 5, 6, 6, 5, 2, 0, 4, 6, 5, 2, 0,
|
|
3, 2, 1, 1, 2, 4, 6, 6, 4, 2, 3, 4, 4, 2, 0,
|
|
1, 0, 0, 0, 0, 1, 4, 5, 4, 4, 4, 4, 3, 2, 0,
|
|
0, 0, 0, 0, 0, 0, 2, 3, 4, 4, 4, 3, 2, 1, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 3, 2, 1, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 3, 3, 1, 0, 1,
|
|
0, 0, 0, 0, 0, 0, 1, 4, 5, 6, 5, 4, 3, 2, 0,
|
|
1, 0, 0, 0, 0, 0, 3, 5, 5, 4, 3, 4, 4, 3, 0,
|
|
2, 0, 0, 0, 0, 2, 5, 6, 5, 2, 2, 5, 4, 3, 0
|
|
};
|
|
Mat mask(Size(cols, rows), CV_8UC1, mask_);
|
|
Mat src(Size(cols, rows), CV_8UC1, src_);
|
|
double minVal = -0.0, maxVal = -0.0;
|
|
int minIdx[2] = { -2, -2 }, maxIdx[2] = { -2, -2 };
|
|
cv::minMaxIdx(src, &minVal, &maxVal, minIdx, maxIdx, mask);
|
|
EXPECT_EQ(0, minIdx[0]);
|
|
EXPECT_EQ(14, minIdx[1]);
|
|
EXPECT_EQ(0, maxIdx[0]);
|
|
EXPECT_EQ(14, maxIdx[1]);
|
|
}
|
|
|
|
TEST(Core_MinMaxIdx, MatND)
|
|
{
|
|
const int shape[3] = {5,5,3};
|
|
cv::Mat src = cv::Mat(3, shape, CV_8UC1);
|
|
src.setTo(1);
|
|
src.data[1] = 0;
|
|
src.data[5*5*3-2] = 2;
|
|
|
|
int minIdx[3];
|
|
int maxIdx[3];
|
|
double minVal, maxVal;
|
|
|
|
cv::minMaxIdx(src, &minVal, &maxVal, minIdx, maxIdx);
|
|
|
|
EXPECT_EQ(0, minVal);
|
|
EXPECT_EQ(2, maxVal);
|
|
|
|
EXPECT_EQ(0, minIdx[0]);
|
|
EXPECT_EQ(0, minIdx[1]);
|
|
EXPECT_EQ(1, minIdx[2]);
|
|
|
|
EXPECT_EQ(4, maxIdx[0]);
|
|
EXPECT_EQ(4, maxIdx[1]);
|
|
EXPECT_EQ(1, maxIdx[2]);
|
|
}
|
|
|
|
TEST(Core_Set, regression_11044)
|
|
{
|
|
Mat testFloat(Size(3, 3), CV_32FC1);
|
|
Mat testDouble(Size(3, 3), CV_64FC1);
|
|
|
|
testFloat.setTo(1);
|
|
EXPECT_EQ(1, testFloat.at<float>(0,0));
|
|
testFloat.setTo(std::numeric_limits<float>::infinity());
|
|
EXPECT_EQ(std::numeric_limits<float>::infinity(), testFloat.at<float>(0, 0));
|
|
testFloat.setTo(1);
|
|
EXPECT_EQ(1, testFloat.at<float>(0, 0));
|
|
testFloat.setTo(std::numeric_limits<double>::infinity());
|
|
EXPECT_EQ(std::numeric_limits<float>::infinity(), testFloat.at<float>(0, 0));
|
|
|
|
testDouble.setTo(1);
|
|
EXPECT_EQ(1, testDouble.at<double>(0, 0));
|
|
testDouble.setTo(std::numeric_limits<float>::infinity());
|
|
EXPECT_EQ(std::numeric_limits<double>::infinity(), testDouble.at<double>(0, 0));
|
|
testDouble.setTo(1);
|
|
EXPECT_EQ(1, testDouble.at<double>(0, 0));
|
|
testDouble.setTo(std::numeric_limits<double>::infinity());
|
|
EXPECT_EQ(std::numeric_limits<double>::infinity(), testDouble.at<double>(0, 0));
|
|
|
|
Mat testMask(Size(3, 3), CV_8UC1, Scalar(1));
|
|
|
|
testFloat.setTo(1);
|
|
EXPECT_EQ(1, testFloat.at<float>(0, 0));
|
|
testFloat.setTo(std::numeric_limits<float>::infinity(), testMask);
|
|
EXPECT_EQ(std::numeric_limits<float>::infinity(), testFloat.at<float>(0, 0));
|
|
testFloat.setTo(1);
|
|
EXPECT_EQ(1, testFloat.at<float>(0, 0));
|
|
testFloat.setTo(std::numeric_limits<double>::infinity(), testMask);
|
|
EXPECT_EQ(std::numeric_limits<float>::infinity(), testFloat.at<float>(0, 0));
|
|
|
|
|
|
testDouble.setTo(1);
|
|
EXPECT_EQ(1, testDouble.at<double>(0, 0));
|
|
testDouble.setTo(std::numeric_limits<float>::infinity(), testMask);
|
|
EXPECT_EQ(std::numeric_limits<double>::infinity(), testDouble.at<double>(0, 0));
|
|
testDouble.setTo(1);
|
|
EXPECT_EQ(1, testDouble.at<double>(0, 0));
|
|
testDouble.setTo(std::numeric_limits<double>::infinity(), testMask);
|
|
EXPECT_EQ(std::numeric_limits<double>::infinity(), testDouble.at<double>(0, 0));
|
|
}
|
|
|
|
TEST(Core_Norm, IPP_regression_NORM_L1_16UC3_small)
|
|
{
|
|
int cn = 3;
|
|
Size sz(9, 4); // width < 16
|
|
Mat a(sz, CV_MAKE_TYPE(CV_16U, cn), Scalar::all(1));
|
|
Mat b(sz, CV_MAKE_TYPE(CV_16U, cn), Scalar::all(2));
|
|
uchar mask_[9*4] = {
|
|
255, 255, 255, 0, 255, 255, 0, 255, 0,
|
|
0, 255, 0, 0, 255, 255, 255, 255, 0,
|
|
0, 0, 0, 255, 0, 255, 0, 255, 255,
|
|
0, 0, 255, 0, 255, 255, 255, 0, 255
|
|
};
|
|
Mat mask(sz, CV_8UC1, mask_);
|
|
|
|
EXPECT_EQ((double)9*4*cn, cv::norm(a, b, NORM_L1)); // without mask, IPP works well
|
|
EXPECT_EQ((double)20*cn, cv::norm(a, b, NORM_L1, mask));
|
|
}
|
|
|
|
TEST(Core_Norm, NORM_L2_8UC4)
|
|
{
|
|
// Tests there is no integer overflow in norm computation for multiple channels.
|
|
const int kSide = 100;
|
|
cv::Mat4b a(kSide, kSide, cv::Scalar(255, 255, 255, 255));
|
|
cv::Mat4b b = cv::Mat4b::zeros(kSide, kSide);
|
|
const double kNorm = 2.*kSide*255.;
|
|
EXPECT_EQ(kNorm, cv::norm(a, b, NORM_L2));
|
|
}
|
|
|
|
TEST(Core_Norm, NORM_L2SQR_16SC4_large)
|
|
{
|
|
const int sizes[] = {1, 116, 40};
|
|
Mat src(3, sizes, CV_16SC4, Scalar::all(16384));
|
|
const double expected = static_cast<double>(src.total()) * src.channels() * 16384.0 * 16384.0;
|
|
EXPECT_EQ(expected, cv::norm(src, NORM_L2SQR));
|
|
}
|
|
|
|
TEST(Core_ConvertTo, regression_12121)
|
|
{
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(-1));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_8U);
|
|
EXPECT_EQ(0, dst.at<uchar>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(INT_MIN));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_8U);
|
|
EXPECT_EQ(0, dst.at<uchar>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(INT_MIN + 32767));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_8U);
|
|
EXPECT_EQ(0, dst.at<uchar>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(INT_MIN + 32768));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_8U);
|
|
EXPECT_EQ(0, dst.at<uchar>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(32768));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_8U);
|
|
EXPECT_EQ(255, dst.at<uchar>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(INT_MIN));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_16U);
|
|
EXPECT_EQ(0, dst.at<ushort>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(INT_MIN + 32767));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_16U);
|
|
EXPECT_EQ(0, dst.at<ushort>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(INT_MIN + 32768));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_16U);
|
|
EXPECT_EQ(0, dst.at<ushort>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
|
|
{
|
|
Mat src(4, 64, CV_32SC1, Scalar(65536));
|
|
Mat dst;
|
|
src.convertTo(dst, CV_16U);
|
|
EXPECT_EQ(65535, dst.at<ushort>(0, 0)) << "src=" << src.at<int>(0, 0);
|
|
}
|
|
}
|
|
|
|
TEST(Core_MeanStdDev, regression_multichannel)
|
|
{
|
|
{
|
|
uchar buf[] = { 1, 2, 3, 4, 5, 6, 7, 8,
|
|
3, 4, 5, 6, 7, 8, 9, 10 };
|
|
double ref_buf[] = { 2., 3., 4., 5., 6., 7., 8., 9.,
|
|
1., 1., 1., 1., 1., 1., 1., 1. };
|
|
Mat src(1, 2, CV_MAKETYPE(CV_8U, 8), buf);
|
|
Mat ref_m(8, 1, CV_64FC1, ref_buf);
|
|
Mat ref_sd(8, 1, CV_64FC1, ref_buf + 8);
|
|
Mat dst_m, dst_sd;
|
|
meanStdDev(src, dst_m, dst_sd);
|
|
EXPECT_EQ(0, cv::norm(dst_m, ref_m, NORM_L1));
|
|
EXPECT_EQ(0, cv::norm(dst_sd, ref_sd, NORM_L1));
|
|
}
|
|
}
|
|
|
|
// Related issue : https://github.com/opencv/opencv/issues/26861
|
|
TEST(Core_MeanStdDevTest, LargeImage)
|
|
{
|
|
applyTestTag(CV_TEST_TAG_VERYLONG);
|
|
applyTestTag(CV_TEST_TAG_MEMORY_14GB);
|
|
// (1<<16) * ((1<<15)+10) = ~2.147 billion
|
|
cv::Mat largeImage = cv::Mat::ones((1 << 16), ((1 << 15) + 10), CV_8U);
|
|
cv::Scalar mean, stddev;
|
|
cv::meanStdDev(largeImage, mean, stddev);
|
|
EXPECT_NEAR(mean[0], 1.0, 1e-5);
|
|
EXPECT_NEAR(stddev[0], 0.0, 1e-5);
|
|
}
|
|
|
|
template <typename T> static inline
|
|
void testDivideInitData(Mat& src1, Mat& src2)
|
|
{
|
|
CV_StaticAssert(std::numeric_limits<T>::is_integer, "");
|
|
const static T src1_[] = {
|
|
0, 0, 0, 0,
|
|
8, 8, 8, 8,
|
|
-8, -8, -8, -8
|
|
};
|
|
Mat(3, 4, traits::Type<T>::value, (void*)src1_).copyTo(src1);
|
|
const static T src2_[] = {
|
|
1, 2, 0, std::numeric_limits<T>::max(),
|
|
1, 2, 0, std::numeric_limits<T>::max(),
|
|
1, 2, 0, std::numeric_limits<T>::max(),
|
|
};
|
|
Mat(3, 4, traits::Type<T>::value, (void*)src2_).copyTo(src2);
|
|
}
|
|
|
|
template <typename T> static inline
|
|
void testDivideInitDataFloat(Mat& src1, Mat& src2)
|
|
{
|
|
CV_StaticAssert(!std::numeric_limits<T>::is_integer, "");
|
|
const static T src1_[] = {
|
|
0, 0, 0, 0,
|
|
8, 8, 8, 8,
|
|
-8, -8, -8, -8
|
|
};
|
|
Mat(3, 4, traits::Type<T>::value, (void*)src1_).copyTo(src1);
|
|
const static T src2_[] = {
|
|
1, 2, 0, std::numeric_limits<T>::infinity(),
|
|
1, 2, 0, std::numeric_limits<T>::infinity(),
|
|
1, 2, 0, std::numeric_limits<T>::infinity(),
|
|
};
|
|
Mat(3, 4, traits::Type<T>::value, (void*)src2_).copyTo(src2);
|
|
}
|
|
|
|
template <> inline void testDivideInitData<float>(Mat& src1, Mat& src2) { testDivideInitDataFloat<float>(src1, src2); }
|
|
template <> inline void testDivideInitData<double>(Mat& src1, Mat& src2) { testDivideInitDataFloat<double>(src1, src2); }
|
|
|
|
|
|
template <typename T> static inline
|
|
void testDivideChecks(const Mat& dst)
|
|
{
|
|
ASSERT_FALSE(dst.empty());
|
|
CV_StaticAssert(std::numeric_limits<T>::is_integer, "");
|
|
for (int y = 0; y < dst.rows; y++)
|
|
{
|
|
for (int x = 0; x < dst.cols; x++)
|
|
{
|
|
if ((x % 4) == 2)
|
|
{
|
|
EXPECT_EQ(0, dst.at<T>(y, x)) << "dst(" << y << ", " << x << ") = " << dst.at<T>(y, x);
|
|
}
|
|
else
|
|
{
|
|
EXPECT_TRUE(0 == cvIsNaN((double)dst.at<T>(y, x))) << "dst(" << y << ", " << x << ") = " << dst.at<T>(y, x);
|
|
EXPECT_TRUE(0 == cvIsInf((double)dst.at<T>(y, x))) << "dst(" << y << ", " << x << ") = " << dst.at<T>(y, x);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T> static inline
|
|
void testDivideChecksFP(const Mat& dst)
|
|
{
|
|
ASSERT_FALSE(dst.empty());
|
|
CV_StaticAssert(!std::numeric_limits<T>::is_integer, "");
|
|
for (int y = 0; y < dst.rows; y++)
|
|
{
|
|
for (int x = 0; x < dst.cols; x++)
|
|
{
|
|
if ((y % 3) == 0 && (x % 4) == 2)
|
|
{
|
|
EXPECT_TRUE(cvIsNaN(dst.at<T>(y, x))) << "dst(" << y << ", " << x << ") = " << dst.at<T>(y, x);
|
|
}
|
|
else if ((x % 4) == 2)
|
|
{
|
|
EXPECT_TRUE(cvIsInf(dst.at<T>(y, x))) << "dst(" << y << ", " << x << ") = " << dst.at<T>(y, x);
|
|
}
|
|
else
|
|
{
|
|
EXPECT_FALSE(cvIsNaN(dst.at<T>(y, x))) << "dst(" << y << ", " << x << ") = " << dst.at<T>(y, x);
|
|
EXPECT_FALSE(cvIsInf(dst.at<T>(y, x))) << "dst(" << y << ", " << x << ") = " << dst.at<T>(y, x);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <> inline void testDivideChecks<float>(const Mat& dst) { testDivideChecksFP<float>(dst); }
|
|
template <> inline void testDivideChecks<double>(const Mat& dst) { testDivideChecksFP<double>(dst); }
|
|
|
|
|
|
template <typename T> static inline
|
|
void testDivide(bool isUMat, double scale, bool largeSize, bool tailProcessing, bool roi)
|
|
{
|
|
Mat src1, src2;
|
|
testDivideInitData<T>(src1, src2);
|
|
ASSERT_FALSE(src1.empty()); ASSERT_FALSE(src2.empty());
|
|
|
|
if (largeSize)
|
|
{
|
|
repeat(src1.clone(), 1, 8, src1);
|
|
repeat(src2.clone(), 1, 8, src2);
|
|
}
|
|
if (tailProcessing)
|
|
{
|
|
src1 = src1(Rect(0, 0, src1.cols - 1, src1.rows));
|
|
src2 = src2(Rect(0, 0, src2.cols - 1, src2.rows));
|
|
}
|
|
if (!roi && tailProcessing)
|
|
{
|
|
src1 = src1.clone();
|
|
src2 = src2.clone();
|
|
}
|
|
|
|
Mat dst;
|
|
if (!isUMat)
|
|
{
|
|
cv::divide(src1, src2, dst, scale);
|
|
}
|
|
else
|
|
{
|
|
UMat usrc1, usrc2, udst;
|
|
src1.copyTo(usrc1);
|
|
src2.copyTo(usrc2);
|
|
cv::divide(usrc1, usrc2, udst, scale);
|
|
udst.copyTo(dst);
|
|
}
|
|
|
|
testDivideChecks<T>(dst);
|
|
|
|
if (::testing::Test::HasFailure())
|
|
{
|
|
std::cout << "src1 = " << std::endl << src1 << std::endl;
|
|
std::cout << "src2 = " << std::endl << src2 << std::endl;
|
|
std::cout << "dst = " << std::endl << dst << std::endl;
|
|
}
|
|
}
|
|
|
|
typedef tuple<bool, double, bool, bool, bool> DivideRulesParam;
|
|
typedef testing::TestWithParam<DivideRulesParam> Core_DivideRules;
|
|
|
|
TEST_P(Core_DivideRules, type_32s)
|
|
{
|
|
DivideRulesParam param = GetParam();
|
|
testDivide<int>(get<0>(param), get<1>(param), get<2>(param), get<3>(param), get<4>(param));
|
|
}
|
|
TEST_P(Core_DivideRules, type_16s)
|
|
{
|
|
DivideRulesParam param = GetParam();
|
|
testDivide<short>(get<0>(param), get<1>(param), get<2>(param), get<3>(param), get<4>(param));
|
|
}
|
|
TEST_P(Core_DivideRules, type_32f)
|
|
{
|
|
DivideRulesParam param = GetParam();
|
|
testDivide<float>(get<0>(param), get<1>(param), get<2>(param), get<3>(param), get<4>(param));
|
|
}
|
|
TEST_P(Core_DivideRules, type_64f)
|
|
{
|
|
DivideRulesParam param = GetParam();
|
|
testDivide<double>(get<0>(param), get<1>(param), get<2>(param), get<3>(param), get<4>(param));
|
|
}
|
|
|
|
|
|
INSTANTIATE_TEST_CASE_P(/* */, Core_DivideRules, testing::Combine(
|
|
/* isMat */ testing::Values(false),
|
|
/* scale */ testing::Values(1.0, 5.0),
|
|
/* largeSize */ testing::Bool(),
|
|
/* tail */ testing::Bool(),
|
|
/* roi */ testing::Bool()
|
|
));
|
|
|
|
INSTANTIATE_TEST_CASE_P(UMat, Core_DivideRules, testing::Combine(
|
|
/* isMat */ testing::Values(true),
|
|
/* scale */ testing::Values(1.0, 5.0),
|
|
/* largeSize */ testing::Bool(),
|
|
/* tail */ testing::Bool(),
|
|
/* roi */ testing::Bool()
|
|
));
|
|
|
|
|
|
TEST(Core_MinMaxIdx, rows_overflow)
|
|
{
|
|
const int N = 65536 + 1;
|
|
const int M = 1;
|
|
{
|
|
setRNGSeed(123);
|
|
Mat m(N, M, CV_32FC1);
|
|
randu(m, -100, 100);
|
|
double minVal = 0, maxVal = 0;
|
|
int minIdx[CV_MAX_DIM] = { 0 }, maxIdx[CV_MAX_DIM] = { 0 };
|
|
cv::minMaxIdx(m, &minVal, &maxVal, minIdx, maxIdx);
|
|
|
|
double minVal0 = 0, maxVal0 = 0;
|
|
int minIdx0[CV_MAX_DIM] = { 0 }, maxIdx0[CV_MAX_DIM] = { 0 };
|
|
cv::ipp::setUseIPP(false);
|
|
cv::minMaxIdx(m, &minVal0, &maxVal0, minIdx0, maxIdx0);
|
|
cv::ipp::setUseIPP(true);
|
|
|
|
EXPECT_FALSE(fabs(minVal0 - minVal) > 1e-6 || fabs(maxVal0 - maxVal) > 1e-6) << "NxM=" << N << "x" << M <<
|
|
" min=" << minVal0 << " vs " << minVal <<
|
|
" max=" << maxVal0 << " vs " << maxVal;
|
|
}
|
|
}
|
|
|
|
TEST(Core_Magnitude, regression_19506)
|
|
{
|
|
for (int N = 1; N <= 64; ++N)
|
|
{
|
|
Mat a(1, N, CV_32FC1, Scalar::all(1e-20));
|
|
Mat res;
|
|
magnitude(a, a, res);
|
|
EXPECT_LE(cvtest::norm(res, NORM_L1), 1e-15) << N;
|
|
}
|
|
}
|
|
|
|
PARAM_TEST_CASE(Core_CartPolar_reverse, int, bool)
|
|
{
|
|
int depth;
|
|
bool angleInDegrees;
|
|
|
|
virtual void SetUp()
|
|
{
|
|
depth = GET_PARAM(0);
|
|
angleInDegrees = GET_PARAM(1);
|
|
}
|
|
};
|
|
|
|
TEST_P(Core_CartPolar_reverse, reverse)
|
|
{
|
|
const int type = CV_MAKETYPE(depth, 1);
|
|
cv::Mat A[2] = {cv::Mat(10, 10, type), cv::Mat(10, 10, type)};
|
|
cv::Mat B[2], C[2];
|
|
cv::UMat uA[2];
|
|
cv::UMat uB[2];
|
|
cv::UMat uC[2];
|
|
|
|
for(int i = 0; i < 2; ++i)
|
|
{
|
|
cvtest::randUni(rng, A[i], Scalar::all(-1000), Scalar::all(1000));
|
|
A[i].copyTo(uA[i]);
|
|
}
|
|
|
|
// Reverse
|
|
cv::cartToPolar(A[0], A[1], B[0], B[1], angleInDegrees);
|
|
cv::polarToCart(B[0], B[1], C[0], C[1], angleInDegrees);
|
|
EXPECT_MAT_NEAR(A[0], C[0], 2);
|
|
EXPECT_MAT_NEAR(A[1], C[1], 2);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_CartPolar, Core_CartPolar_reverse,
|
|
testing::Combine(
|
|
testing::Values(CV_32F, CV_64F),
|
|
testing::Values(false, true)
|
|
)
|
|
);
|
|
|
|
PARAM_TEST_CASE(Core_CartToPolar_inplace, int, bool)
|
|
{
|
|
int depth;
|
|
bool angleInDegrees;
|
|
|
|
virtual void SetUp()
|
|
{
|
|
depth = GET_PARAM(0);
|
|
angleInDegrees = GET_PARAM(1);
|
|
}
|
|
};
|
|
|
|
TEST_P(Core_CartToPolar_inplace, inplace)
|
|
{
|
|
const int type = CV_MAKETYPE(depth, 1);
|
|
cv::Mat A[2] = {cv::Mat(10, 10, type), cv::Mat(10, 10, type)};
|
|
cv::Mat B[2], C[2];
|
|
cv::UMat uA[2];
|
|
cv::UMat uB[2];
|
|
cv::UMat uC[2];
|
|
|
|
for(int i = 0; i < 2; ++i)
|
|
{
|
|
cvtest::randUni(rng, A[i], Scalar::all(-1000), Scalar::all(1000));
|
|
A[i].copyTo(uA[i]);
|
|
}
|
|
|
|
// Inplace x<->mag y<->angle
|
|
for(int i = 0; i < 2; ++i)
|
|
A[i].copyTo(B[i]);
|
|
cv::cartToPolar(A[0], A[1], C[0], C[1], angleInDegrees);
|
|
cv::cartToPolar(B[0], B[1], B[0], B[1], angleInDegrees);
|
|
EXPECT_MAT_NEAR(C[0], B[0], 2);
|
|
EXPECT_MAT_NEAR(C[1], B[1], 2);
|
|
|
|
// Inplace x<->angle y<->mag
|
|
for(int i = 0; i < 2; ++i)
|
|
A[i].copyTo(B[i]);
|
|
cv::cartToPolar(A[0], A[1], C[0], C[1], angleInDegrees);
|
|
cv::cartToPolar(B[0], B[1], B[1], B[0], angleInDegrees);
|
|
EXPECT_MAT_NEAR(C[0], B[1], 2);
|
|
EXPECT_MAT_NEAR(C[1], B[0], 2);
|
|
|
|
// Inplace OCL x<->mag y<->angle
|
|
for(int i = 0; i < 2; ++i)
|
|
uA[i].copyTo(uB[i]);
|
|
cv::cartToPolar(uA[0], uA[1], uC[0], uC[1], angleInDegrees);
|
|
cv::cartToPolar(uB[0], uB[1], uB[0], uB[1], angleInDegrees);
|
|
EXPECT_MAT_NEAR(uC[0], uB[0], 2);
|
|
EXPECT_MAT_NEAR(uC[1], uB[1], 2);
|
|
|
|
// Inplace OCL x<->angle y<->mag
|
|
for(int i = 0; i < 2; ++i)
|
|
uA[i].copyTo(uB[i]);
|
|
cv::cartToPolar(uA[0], uA[1], uC[0], uC[1], angleInDegrees);
|
|
cv::cartToPolar(uB[0], uB[1], uB[1], uB[0], angleInDegrees);
|
|
EXPECT_MAT_NEAR(uC[0], uB[1], 2);
|
|
EXPECT_MAT_NEAR(uC[1], uB[0], 2);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_CartPolar, Core_CartToPolar_inplace,
|
|
testing::Combine(
|
|
testing::Values(CV_32F, CV_64F),
|
|
testing::Values(false, true)
|
|
)
|
|
);
|
|
|
|
PARAM_TEST_CASE(Core_PolarToCart_inplace, int, bool, bool)
|
|
{
|
|
int depth;
|
|
bool angleInDegrees;
|
|
bool implicitMagnitude;
|
|
|
|
virtual void SetUp()
|
|
{
|
|
depth = GET_PARAM(0);
|
|
angleInDegrees = GET_PARAM(1);
|
|
implicitMagnitude = GET_PARAM(2);
|
|
}
|
|
};
|
|
|
|
TEST_P(Core_PolarToCart_inplace, inplace)
|
|
{
|
|
const int type = CV_MAKETYPE(depth, 1);
|
|
cv::Mat A[2] = {cv::Mat(10, 10, type), cv::Mat(10, 10, type)};
|
|
cv::Mat B[2], C[2];
|
|
cv::UMat uA[2];
|
|
cv::UMat uB[2];
|
|
cv::UMat uC[2];
|
|
|
|
for(int i = 0; i < 2; ++i)
|
|
{
|
|
cvtest::randUni(rng, A[i], Scalar::all(-1000), Scalar::all(1000));
|
|
A[i].copyTo(uA[i]);
|
|
}
|
|
|
|
// Inplace OCL x<->mag y<->angle
|
|
for(int i = 0; i < 2; ++i)
|
|
A[i].copyTo(B[i]);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : A[0], A[1], C[0], C[1], angleInDegrees);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : B[0], B[1], B[0], B[1], angleInDegrees);
|
|
EXPECT_MAT_NEAR(C[0], B[0], 2);
|
|
EXPECT_MAT_NEAR(C[1], B[1], 2);
|
|
|
|
// Inplace OCL x<->angle y<->mag
|
|
for(int i = 0; i < 2; ++i)
|
|
A[i].copyTo(B[i]);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : A[0], A[1], C[0], C[1], angleInDegrees);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : B[0], B[1], B[1], B[0], angleInDegrees);
|
|
EXPECT_MAT_NEAR(C[0], B[1], 2);
|
|
EXPECT_MAT_NEAR(C[1], B[0], 2);
|
|
|
|
// Inplace OCL x<->mag y<->angle
|
|
for(int i = 0; i < 2; ++i)
|
|
uA[i].copyTo(uB[i]);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : uA[0], uA[1], uC[0], uC[1], angleInDegrees);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : uB[0], uB[1], uB[0], uB[1], angleInDegrees);
|
|
EXPECT_MAT_NEAR(uC[0], uB[0], 2);
|
|
EXPECT_MAT_NEAR(uC[1], uB[1], 2);
|
|
|
|
// Inplace OCL x<->angle y<->mag
|
|
for(int i = 0; i < 2; ++i)
|
|
uA[i].copyTo(uB[i]);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : uA[0], uA[1], uC[0], uC[1], angleInDegrees);
|
|
cv::polarToCart(implicitMagnitude ? cv::noArray() : uB[0], uB[1], uB[1], uB[0], angleInDegrees);
|
|
EXPECT_MAT_NEAR(uC[0], uB[1], 2);
|
|
EXPECT_MAT_NEAR(uC[1], uB[0], 2);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(Core_CartPolar, Core_PolarToCart_inplace,
|
|
testing::Combine(
|
|
testing::Values(CV_32F, CV_64F),
|
|
testing::Values(false, true),
|
|
testing::Values(true, false)
|
|
)
|
|
);
|
|
|
|
// Check different values for finiteMask()
|
|
|
|
template<typename _Tp>
|
|
_Tp randomNan(RNG& rng);
|
|
|
|
template<>
|
|
float randomNan(RNG& rng)
|
|
{
|
|
uint32_t r = rng.next();
|
|
Cv32suf v;
|
|
v.u = r;
|
|
// exp & set a bit to avoid zero mantissa
|
|
v.u = v.u | 0x7f800001;
|
|
return v.f;
|
|
}
|
|
|
|
template<>
|
|
double randomNan(RNG& rng)
|
|
{
|
|
uint32_t r0 = rng.next();
|
|
uint32_t r1 = rng.next();
|
|
Cv64suf v;
|
|
v.u = (uint64_t(r0) << 32) | uint64_t(r1);
|
|
// exp &set a bit to avoid zero mantissa
|
|
v.u = v.u | 0x7ff0000000000001;
|
|
return v.f;
|
|
}
|
|
|
|
template<typename T>
|
|
Mat generateFiniteMaskData(int cn, RNG& rng)
|
|
{
|
|
typedef typename reference::SoftType<T>::type SFT;
|
|
|
|
SFT pinf = SFT::inf();
|
|
SFT ninf = SFT::inf().setSign(true);
|
|
|
|
const int len = 100;
|
|
Mat_<T> plainData(1, cn*len);
|
|
for(int i = 0; i < cn*len; i++)
|
|
{
|
|
int r = rng.uniform(0, 3);
|
|
plainData(i) = r == 0 ? T(rng.uniform(0, 2) ? pinf : ninf) :
|
|
r == 1 ? randomNan<T>(rng) : T(0);
|
|
}
|
|
|
|
return Mat(plainData).reshape(cn);
|
|
}
|
|
|
|
typedef std::tuple<int, int> FiniteMaskFixtureParams;
|
|
class FiniteMaskFixture : public ::testing::TestWithParam<FiniteMaskFixtureParams> {};
|
|
|
|
TEST_P(FiniteMaskFixture, flags)
|
|
{
|
|
auto p = GetParam();
|
|
int depth = get<0>(p);
|
|
int channels = get<1>(p);
|
|
|
|
RNG rng((uint64)ARITHM_RNG_SEED);
|
|
Mat data = (depth == CV_32F) ? generateFiniteMaskData<float >(channels, rng)
|
|
/* CV_64F */ : generateFiniteMaskData<double>(channels, rng);
|
|
|
|
Mat nans, gtNans;
|
|
cv::finiteMask(data, nans);
|
|
reference::finiteMask(data, gtNans);
|
|
|
|
EXPECT_MAT_NEAR(nans, gtNans, 0);
|
|
}
|
|
|
|
// Params are: depth, channels 1 to 4
|
|
INSTANTIATE_TEST_CASE_P(Core_FiniteMask, FiniteMaskFixture, ::testing::Combine(::testing::Values(CV_32F, CV_64F), ::testing::Range(1, 5)));
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
typedef testing::TestWithParam<perf::MatDepth> NonZeroSupportedMatDepth;
|
|
|
|
TEST_P(NonZeroSupportedMatDepth, findNonZero)
|
|
{
|
|
cv::Mat src = cv::Mat::zeros(16,16, CV_MAKETYPE(GetParam(), 1));
|
|
vector<Point> pts;
|
|
EXPECT_NO_THROW(findNonZero(src, pts));
|
|
}
|
|
|
|
TEST_P(NonZeroSupportedMatDepth, countNonZero)
|
|
{
|
|
cv::Mat src = cv::Mat::zeros(16,16, CV_MAKETYPE(GetParam(), 1));
|
|
EXPECT_NO_THROW(countNonZero(src));
|
|
}
|
|
|
|
TEST_P(NonZeroSupportedMatDepth, hasNonZero)
|
|
{
|
|
cv::Mat src = cv::Mat::zeros(16,16, CV_MAKETYPE(GetParam(), 1));
|
|
EXPECT_NO_THROW(hasNonZero(src));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
NonZero,
|
|
NonZeroSupportedMatDepth,
|
|
testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U)
|
|
);
|
|
|
|
typedef testing::TestWithParam<perf::MatDepth> NonZeroAccuracyNewTypes;
|
|
|
|
TEST_P(NonZeroAccuracyNewTypes, accuracy)
|
|
{
|
|
const int depth = GetParam();
|
|
const Size sz(123, 71);
|
|
cv::Mat src = cv::Mat::zeros(sz, CV_MAKETYPE(depth, 1));
|
|
|
|
std::vector<Point> expected_pts;
|
|
const int total = sz.area();
|
|
const int approx_nz = std::max(1, total / 17);
|
|
std::vector<uchar> is_nz(total, 0);
|
|
for (int n = 0; n < approx_nz; )
|
|
{
|
|
int idx = theRNG().uniform(0, total);
|
|
if (is_nz[idx])
|
|
continue;
|
|
is_nz[idx] = 1;
|
|
++n;
|
|
}
|
|
|
|
auto setNonZero = [&](int y, int x)
|
|
{
|
|
switch(depth)
|
|
{
|
|
case CV_Bool: src.at<uchar>(y, x) = 1; break;
|
|
case CV_16BF: src.at<uint16_t>(y, x) = 0x3F80; /* bf16(1.0f) */ break;
|
|
case CV_32U: src.at<uint32_t>(y, x) = 7u; break;
|
|
case CV_64U: src.at<uint64_t>(y, x) = 7ULL; break;
|
|
case CV_64S: src.at<int64_t>(y, x) = -7LL; break;
|
|
default: FAIL() << "Unexpected depth " << depth;
|
|
}
|
|
};
|
|
|
|
int nz_ref = 0;
|
|
for (int y = 0; y < sz.height; ++y)
|
|
for (int x = 0; x < sz.width; ++x)
|
|
if (is_nz[y*sz.width + x])
|
|
{
|
|
setNonZero(y, x);
|
|
expected_pts.emplace_back(x, y);
|
|
++nz_ref;
|
|
}
|
|
|
|
EXPECT_EQ(nz_ref, cv::countNonZero(src));
|
|
EXPECT_EQ(nz_ref > 0, cv::hasNonZero(src));
|
|
cv::Mat zeros = cv::Mat::zeros(sz, src.type());
|
|
EXPECT_FALSE(cv::hasNonZero(zeros));
|
|
|
|
std::vector<Point> pts;
|
|
cv::findNonZero(src, pts);
|
|
ASSERT_EQ(expected_pts.size(), pts.size());
|
|
for (size_t i = 0; i < pts.size(); ++i)
|
|
{
|
|
EXPECT_EQ(expected_pts[i].x, pts[i].x) << "i=" << i;
|
|
EXPECT_EQ(expected_pts[i].y, pts[i].y) << "i=" << i;
|
|
}
|
|
}
|
|
|
|
TEST(NonZeroAccuracyNewTypes_BF16, negative_zero_is_zero)
|
|
{
|
|
const Size sz(64, 32);
|
|
cv::Mat src(sz, CV_16BFC1);
|
|
src.setTo(cv::Scalar::all(-0.0));
|
|
EXPECT_EQ(0, cv::countNonZero(src));
|
|
EXPECT_FALSE(cv::hasNonZero(src));
|
|
std::vector<Point> pts;
|
|
cv::findNonZero(src, pts);
|
|
EXPECT_TRUE(pts.empty());
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
NonZeroAcc,
|
|
NonZeroAccuracyNewTypes,
|
|
testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U)
|
|
);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
typedef testing::TestWithParam<perf::MatDepth> MinMaxSupportedMatDepth;
|
|
|
|
TEST_P(MinMaxSupportedMatDepth, minMaxLoc)
|
|
{
|
|
cv::Mat src = cv::Mat::zeros(16,16, CV_MAKETYPE(GetParam(), 1));
|
|
double minV=0.0, maxV=0.0;
|
|
Point minLoc, maxLoc;
|
|
EXPECT_NO_THROW(cv::minMaxLoc(src, &minV, &maxV, &minLoc, &maxLoc));
|
|
}
|
|
|
|
TEST_P(MinMaxSupportedMatDepth, minMaxIdx)
|
|
{
|
|
cv::Mat src = cv::Mat::zeros(16,16, CV_MAKETYPE(GetParam(), 1));
|
|
double minV=0.0, maxV=0.0;
|
|
int minIdx[2] = {0, 0};
|
|
int maxIdx[2] = {0, 0};
|
|
EXPECT_NO_THROW(cv::minMaxIdx(src, &minV, &maxV, minIdx, maxIdx));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
MinMaxLoc,
|
|
MinMaxSupportedMatDepth,
|
|
testing::Values(perf::MatDepth(CV_16F), CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U)
|
|
);
|
|
|
|
typedef testing::TestWithParam<perf::MatDepth> MinMaxAccuracyNewTypes;
|
|
|
|
TEST_P(MinMaxAccuracyNewTypes, accuracy)
|
|
{
|
|
const int depth = GetParam();
|
|
const Size sz(173, 91);
|
|
|
|
double fill_val = 0, min_val = 0, max_val = 0;
|
|
Point min_pos(11, 7), max_pos(150, 80);
|
|
ASSERT_TRUE(min_pos != max_pos);
|
|
|
|
switch (depth)
|
|
{
|
|
case CV_Bool: fill_val = 0; min_val = 0; max_val = 1; break;
|
|
case CV_16BF: fill_val = 1.5; min_val = -123.5; max_val = 4096.0; break;
|
|
case CV_32U: fill_val = 1000.0; min_val = 7.0;
|
|
max_val = (double)std::numeric_limits<uint32_t>::max() - 1.0; break;
|
|
case CV_64U: fill_val = 1e15; min_val = 0.0; max_val = 1e18; break;
|
|
case CV_64S: fill_val = 0.0; min_val = -1e17; max_val = 1e17; break;
|
|
default: FAIL() << "Unexpected depth " << depth;
|
|
}
|
|
|
|
cv::Mat src(sz, CV_MAKETYPE(depth, 1));
|
|
if (depth == CV_Bool) src.setTo(cv::Scalar::all(0));
|
|
else src.setTo(cv::Scalar::all(fill_val));
|
|
|
|
auto put = [&](Point p, double v)
|
|
{
|
|
switch(depth)
|
|
{
|
|
case CV_Bool: src.at<uchar>(p.y, p.x) = (uchar)v; break;
|
|
case CV_16BF: src.at<cv::bfloat>(p.y, p.x) = cv::bfloat((float)v); break;
|
|
case CV_32U: src.at<uint32_t>(p.y, p.x) = (uint32_t)v; break;
|
|
case CV_64U: src.at<uint64_t>(p.y, p.x) = (uint64_t)v; break;
|
|
case CV_64S: src.at<int64_t>(p.y, p.x) = (int64_t)v; break;
|
|
default: FAIL() << "Unexpected depth " << depth;
|
|
}
|
|
};
|
|
put(min_pos, min_val);
|
|
put(max_pos, max_val);
|
|
|
|
// minMaxLoc: 2D-only, returns Points.
|
|
{
|
|
double minV = 0, maxV = 0;
|
|
Point minLoc(-1, -1), maxLoc(-1, -1);
|
|
cv::minMaxLoc(src, &minV, &maxV, &minLoc, &maxLoc);
|
|
EXPECT_NEAR(min_val, minV, 1e-5 * std::max(1.0, std::fabs(min_val)));
|
|
EXPECT_NEAR(max_val, maxV, 1e-5 * std::max(1.0, std::fabs(max_val)));
|
|
if (depth != CV_Bool)
|
|
{
|
|
EXPECT_EQ(min_pos, minLoc);
|
|
}
|
|
EXPECT_EQ(max_pos, maxLoc);
|
|
}
|
|
|
|
// minMaxIdx: same data, idx[] form (row, col).
|
|
{
|
|
double minV = 0, maxV = 0;
|
|
int minIdx[2] = {-1, -1}, maxIdx[2] = {-1, -1};
|
|
cv::minMaxIdx(src, &minV, &maxV, minIdx, maxIdx);
|
|
EXPECT_NEAR(min_val, minV, 1e-5 * std::max(1.0, std::fabs(min_val)));
|
|
EXPECT_NEAR(max_val, maxV, 1e-5 * std::max(1.0, std::fabs(max_val)));
|
|
if (depth != CV_Bool)
|
|
{
|
|
EXPECT_EQ(min_pos.y, minIdx[0]);
|
|
EXPECT_EQ(min_pos.x, minIdx[1]);
|
|
}
|
|
EXPECT_EQ(max_pos.y, maxIdx[0]);
|
|
EXPECT_EQ(max_pos.x, maxIdx[1]);
|
|
}
|
|
}
|
|
|
|
// Mask-aware accuracy: extreme values outside the mask must be ignored.
|
|
TEST_P(MinMaxAccuracyNewTypes, accuracy_with_mask)
|
|
{
|
|
const int depth = GetParam();
|
|
const Size sz(64, 48);
|
|
cv::Mat src(sz, CV_MAKETYPE(depth, 1));
|
|
cv::Mat mask = cv::Mat::zeros(sz, CV_8UC1);
|
|
|
|
double fill_val = 0, masked_min = 0, masked_max = 0, outlier_min = 0, outlier_max = 0;
|
|
Point min_pos(5, 5), max_pos(40, 30), outlier_min_pos(1, 1), outlier_max_pos(60, 45);
|
|
|
|
switch (depth)
|
|
{
|
|
case CV_Bool:
|
|
fill_val = 0; masked_min = 0; masked_max = 1;
|
|
outlier_min = 0; outlier_max = 1; break;
|
|
case CV_16BF:
|
|
fill_val = 1.0; masked_min = -10.0; masked_max = 10.0;
|
|
outlier_min = -1000.0; outlier_max = 1000.0; break;
|
|
case CV_32U:
|
|
fill_val = 100; masked_min = 1; masked_max = 1000;
|
|
outlier_min = 0; outlier_max = (double)std::numeric_limits<uint32_t>::max(); break;
|
|
case CV_64U:
|
|
fill_val = 1e10; masked_min = 1.0; masked_max = 1e12;
|
|
outlier_min = 0.0; outlier_max = 1e18; break;
|
|
case CV_64S:
|
|
fill_val = 0.0; masked_min = -1e10; masked_max = 1e10;
|
|
outlier_min = -1e17; outlier_max = 1e17; break;
|
|
default: FAIL() << "Unexpected depth " << depth;
|
|
}
|
|
|
|
src.setTo(cv::Scalar::all(fill_val));
|
|
|
|
auto put = [&](Point p, double v)
|
|
{
|
|
switch(depth)
|
|
{
|
|
case CV_Bool: src.at<uchar>(p.y, p.x) = (uchar)v; break;
|
|
case CV_16BF: src.at<cv::bfloat>(p.y, p.x) = cv::bfloat((float)v); break;
|
|
case CV_32U: src.at<uint32_t>(p.y, p.x) = (uint32_t)v; break;
|
|
case CV_64U: src.at<uint64_t>(p.y, p.x) = (uint64_t)v; break;
|
|
case CV_64S: src.at<int64_t>(p.y, p.x) = (int64_t)v; break;
|
|
default: FAIL() << "Unexpected depth " << depth;
|
|
}
|
|
};
|
|
put(min_pos, masked_min);
|
|
put(max_pos, masked_max);
|
|
put(outlier_min_pos, outlier_min);
|
|
put(outlier_max_pos, outlier_max);
|
|
|
|
mask.setTo(255);
|
|
mask.at<uchar>(outlier_min_pos.y, outlier_min_pos.x) = 0;
|
|
mask.at<uchar>(outlier_max_pos.y, outlier_max_pos.x) = 0;
|
|
|
|
double minV = 0, maxV = 0;
|
|
int minIdx[2] = {-1, -1}, maxIdx[2] = {-1, -1};
|
|
cv::minMaxIdx(src, &minV, &maxV, minIdx, maxIdx, mask);
|
|
|
|
EXPECT_NEAR(masked_min, minV, 1e-5 * std::max(1.0, std::fabs(masked_min)));
|
|
EXPECT_NEAR(masked_max, maxV, 1e-5 * std::max(1.0, std::fabs(masked_max)));
|
|
|
|
if (depth != CV_Bool)
|
|
{
|
|
EXPECT_EQ(min_pos.y, minIdx[0]);
|
|
EXPECT_EQ(min_pos.x, minIdx[1]);
|
|
EXPECT_EQ(max_pos.y, maxIdx[0]);
|
|
EXPECT_EQ(max_pos.x, maxIdx[1]);
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
MinMaxAcc,
|
|
MinMaxAccuracyNewTypes,
|
|
testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U)
|
|
);
|
|
|
|
typedef testing::TestWithParam<perf::MatDepth> LUTAccuracyNewDstTypes;
|
|
|
|
TEST_P(LUTAccuracyNewDstTypes, accuracy)
|
|
{
|
|
const int dst_depth = GetParam();
|
|
cv::Mat lut(1, 256, CV_MAKETYPE(dst_depth, 1));
|
|
for (int i = 0; i < 256; ++i)
|
|
{
|
|
switch (dst_depth)
|
|
{
|
|
case CV_Bool: lut.at<uchar>(0, i) = (uchar)(i & 1); break;
|
|
case CV_16BF: lut.at<cv::bfloat>(0, i) = cv::bfloat((float)(i - 128) * 0.5f); break;
|
|
case CV_32U: lut.at<uint32_t>(0, i) = (uint32_t)i * 16777619u; break;
|
|
case CV_64U: lut.at<uint64_t>(0, i) = ((uint64_t)i << 56) | (uint64_t)i; break;
|
|
case CV_64S: lut.at<int64_t>(0, i) = ((int64_t)i - 128) * 1000000000LL; break;
|
|
default: FAIL() << "Unexpected depth " << dst_depth;
|
|
}
|
|
}
|
|
|
|
cv::Mat src(16, 16, CV_8UC1);
|
|
for (int y = 0; y < 16; ++y)
|
|
for (int x = 0; x < 16; ++x)
|
|
src.at<uchar>(y, x) = (uchar)(y * 16 + x);
|
|
|
|
cv::Mat dst;
|
|
cv::LUT(src, lut, dst);
|
|
ASSERT_EQ(dst.size(), src.size());
|
|
ASSERT_EQ(dst.type(), CV_MAKETYPE(dst_depth, 1));
|
|
|
|
for (int y = 0; y < 16; ++y)
|
|
{
|
|
for (int x = 0; x < 16; ++x)
|
|
{
|
|
int idx = src.at<uchar>(y, x);
|
|
switch (dst_depth)
|
|
{
|
|
case CV_Bool:
|
|
EXPECT_EQ(lut.at<uchar>(0, idx), dst.at<uchar>(y, x)); break;
|
|
case CV_16BF:
|
|
EXPECT_EQ(lut.at<uint16_t>(0, idx), dst.at<uint16_t>(y, x)); break;
|
|
case CV_32U:
|
|
EXPECT_EQ(lut.at<uint32_t>(0, idx), dst.at<uint32_t>(y, x)); break;
|
|
case CV_64U:
|
|
EXPECT_EQ(lut.at<uint64_t>(0, idx), dst.at<uint64_t>(y, x)); break;
|
|
case CV_64S:
|
|
EXPECT_EQ(lut.at<int64_t>(0, idx), dst.at<int64_t>(y, x)); break;
|
|
default: FAIL() << "Unexpected depth " << dst_depth;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
LUTAcc,
|
|
LUTAccuracyNewDstTypes,
|
|
testing::Values(CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U)
|
|
);
|
|
|
|
CV_ENUM(LutIdxType, CV_8U, CV_8S, CV_16U, CV_16S)
|
|
CV_ENUM(LutMatType, CV_8U, CV_8S, CV_16U, CV_16S, CV_32S, CV_32F, CV_64F, CV_16F, CV_16BF, CV_Bool, CV_64U, CV_64S, CV_32U)
|
|
|
|
struct Core_LUT: public testing::TestWithParam< std::tuple<LutIdxType, LutMatType> >
|
|
{
|
|
template<typename Ti, typename T, int ch, bool same_cn>
|
|
cv::Mat referenceWithType(cv::Mat input, cv::Mat table)
|
|
{
|
|
cv::Mat ref(input.size(), CV_MAKE_TYPE(table.depth(), ch));
|
|
for (int i = 0; i < input.rows; i++)
|
|
{
|
|
for (int j = 0; j < input.cols; j++)
|
|
{
|
|
if(ch == 1)
|
|
{
|
|
ref.at<T>(i, j) = table.at<T>(input.at<Ti>(i, j));
|
|
}
|
|
else
|
|
{
|
|
Vec<T, ch> val;
|
|
for (int k = 0; k < ch; k++)
|
|
{
|
|
if (same_cn)
|
|
{
|
|
val[k] = table.at<Vec<T, ch>>(input.at<Vec<Ti, ch>>(i, j)[k])[k];
|
|
}
|
|
else
|
|
{
|
|
val[k] = table.at<T>(input.at<Vec<Ti, ch>>(i, j)[k]);
|
|
}
|
|
}
|
|
ref.at<Vec<T, ch>>(i, j) = val;
|
|
}
|
|
}
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
template<int ch = 1, bool same_cn = false>
|
|
cv::Mat reference(cv::Mat input, cv::Mat table)
|
|
{
|
|
cv::Mat ret = cv::Mat();
|
|
if ((input.depth() == CV_8U) || (input.depth() == CV_8S)) // Index type for LUT operation
|
|
{
|
|
switch(table.depth()) // Value type for LUT operation
|
|
{
|
|
case CV_8U: ret = referenceWithType<uint8_t, uint8_t, ch, same_cn>(input, table); break;
|
|
case CV_8S: ret = referenceWithType<uint8_t, int8_t, ch, same_cn>(input, table); break;
|
|
case CV_16U: ret = referenceWithType<uint8_t, uint16_t, ch, same_cn>(input, table); break;
|
|
case CV_16S: ret = referenceWithType<uint8_t, int16_t, ch, same_cn>(input, table); break;
|
|
case CV_32S: ret = referenceWithType<uint8_t, int32_t, ch, same_cn>(input, table); break;
|
|
case CV_32F: ret = referenceWithType<uint8_t, float, ch, same_cn>(input, table); break;
|
|
case CV_64F: ret = referenceWithType<uint8_t, double, ch, same_cn>(input, table); break;
|
|
case CV_16F: ret = referenceWithType<uint8_t, uint16_t, ch, same_cn>(input, table); break;
|
|
case CV_16BF: ret = referenceWithType<uint8_t, uint16_t, ch, same_cn>(input, table); break;
|
|
case CV_Bool: ret = referenceWithType<uint8_t, uint8_t, ch, same_cn>(input, table); break;
|
|
case CV_64U: ret = referenceWithType<uint8_t, uint64_t, ch, same_cn>(input, table); break;
|
|
case CV_64S: ret = referenceWithType<uint8_t, int64_t, ch, same_cn>(input, table); break;
|
|
case CV_32U: ret = referenceWithType<uint8_t, uint32_t, ch, same_cn>(input, table); break;
|
|
default: ret = cv::Mat(); break;
|
|
}
|
|
}
|
|
else if ((input.depth() == CV_16U) || (input.depth() == CV_16S))
|
|
{
|
|
switch(table.depth()) // Value type for LUT operation
|
|
{
|
|
case CV_8U: ret = referenceWithType<uint16_t, uint8_t, ch, same_cn>(input, table); break;
|
|
case CV_8S: ret = referenceWithType<uint16_t, int8_t, ch, same_cn>(input, table); break;
|
|
case CV_16U: ret = referenceWithType<uint16_t, uint16_t, ch, same_cn>(input, table); break;
|
|
case CV_16S: ret = referenceWithType<uint16_t, int16_t, ch, same_cn>(input, table); break;
|
|
case CV_32S: ret = referenceWithType<uint16_t, int32_t, ch, same_cn>(input, table); break;
|
|
case CV_32F: ret = referenceWithType<uint16_t, float, ch, same_cn>(input, table); break;
|
|
case CV_64F: ret = referenceWithType<uint16_t, double, ch, same_cn>(input, table); break;
|
|
case CV_16F: ret = referenceWithType<uint16_t, uint16_t, ch, same_cn>(input, table); break;
|
|
case CV_16BF: ret = referenceWithType<uint16_t, uint16_t, ch, same_cn>(input, table); break;
|
|
case CV_Bool: ret = referenceWithType<uint16_t, uint8_t, ch, same_cn>(input, table); break;
|
|
case CV_64U: ret = referenceWithType<uint16_t, uint64_t, ch, same_cn>(input, table); break;
|
|
case CV_64S: ret = referenceWithType<uint16_t, int64_t, ch, same_cn>(input, table); break;
|
|
case CV_32U: ret = referenceWithType<uint16_t, uint32_t, ch, same_cn>(input, table); break;
|
|
default: ret = cv::Mat(); break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
TEST_P(Core_LUT, accuracy)
|
|
{
|
|
int idx_type = get<0>(GetParam());
|
|
int value_type = get<1>(GetParam());
|
|
|
|
ASSERT_TRUE((idx_type == CV_8U) || (idx_type == CV_8S) || (idx_type == CV_16U ) || (idx_type == CV_16S));
|
|
const int tableSize = ((idx_type == CV_8U) || (idx_type == CV_8S)) ? 256: 65536;
|
|
|
|
cv::Mat input(117, 113, CV_MAKE_TYPE(idx_type, 1));
|
|
randu(input, getMinVal(idx_type), getMaxVal(idx_type));
|
|
|
|
cv::Mat table(1, tableSize, CV_MAKE_TYPE(value_type, 1));
|
|
randu(table, getMinVal(value_type), getMaxVal(value_type));
|
|
|
|
cv::Mat output;
|
|
ASSERT_NO_THROW(cv::LUT(input, table, output));
|
|
ASSERT_FALSE(output.empty());
|
|
|
|
cv::Mat gt = reference(input, table);
|
|
ASSERT_FALSE(gt.empty());
|
|
|
|
// Force convert to 8U as CV_Bool is not supported in cv::norm for now
|
|
// TODO: Remove conversion after cv::norm fix
|
|
if (value_type == CV_Bool)
|
|
{
|
|
output.convertTo(output, CV_8U);
|
|
gt.convertTo(gt, CV_8U);
|
|
}
|
|
|
|
ASSERT_EQ(0, cv::norm(output, gt, cv::NORM_INF));
|
|
}
|
|
|
|
TEST_P(Core_LUT, accuracy_multi)
|
|
{
|
|
int idx_type = get<0>(GetParam());
|
|
int value_type = get<1>(GetParam());
|
|
|
|
ASSERT_TRUE((idx_type == CV_8U) || (idx_type == CV_8S) || (idx_type == CV_16U) || (idx_type == CV_16S));
|
|
const int tableSize = ((idx_type == CV_8U) || (idx_type == CV_8S) ) ? 256: 65536;
|
|
|
|
cv::Mat input(117, 113, CV_MAKE_TYPE(idx_type, 3));
|
|
randu(input, getMinVal(idx_type), getMaxVal(idx_type));
|
|
|
|
cv::Mat table(1, tableSize, CV_MAKE_TYPE(value_type, 1));
|
|
randu(table, getMinVal(value_type), getMaxVal(value_type));
|
|
|
|
cv::Mat output;
|
|
ASSERT_NO_THROW(cv::LUT(input, table, output));
|
|
ASSERT_FALSE(output.empty());
|
|
|
|
cv::Mat gt = reference<3>(input, table);
|
|
ASSERT_FALSE(gt.empty());
|
|
|
|
// Force convert to 8U as CV_Bool is not supported in cv::norm for now
|
|
// TODO: Remove conversion after cv::norm fix
|
|
if (value_type == CV_Bool)
|
|
{
|
|
output.convertTo(output, CV_8U);
|
|
gt.convertTo(gt, CV_8U);
|
|
}
|
|
|
|
ASSERT_EQ(0, cv::norm(output, gt, cv::NORM_INF));
|
|
}
|
|
|
|
TEST_P(Core_LUT, accuracy_multi2)
|
|
{
|
|
int idx_type = get<0>(GetParam());
|
|
int value_type = get<1>(GetParam());
|
|
|
|
ASSERT_TRUE((idx_type == CV_8U) || (idx_type == CV_8S) || (idx_type == CV_16U) || (idx_type == CV_16S));
|
|
const int tableSize = ((idx_type == CV_8U) || (idx_type == CV_8S)) ? 256: 65536;
|
|
|
|
cv::Mat input(117, 113, CV_MAKE_TYPE(idx_type, 3));
|
|
randu(input, getMinVal(idx_type), getMaxVal(idx_type));
|
|
|
|
cv::Mat table(1, tableSize, CV_MAKE_TYPE(value_type, 3));
|
|
randu(table, getMinVal(value_type), getMaxVal(value_type));
|
|
|
|
cv::Mat output;
|
|
ASSERT_NO_THROW(cv::LUT(input, table, output));
|
|
ASSERT_FALSE(output.empty());
|
|
|
|
cv::Mat gt = reference<3, true>(input, table);
|
|
ASSERT_FALSE(gt.empty());
|
|
|
|
// Force convert to 8U as CV_Bool is not supported in cv::norm for now
|
|
// TODO: Remove conversion after cv::norm fix
|
|
if (value_type == CV_Bool)
|
|
{
|
|
output.convertTo(output, CV_8U);
|
|
gt.convertTo(gt, CV_8U);
|
|
}
|
|
|
|
ASSERT_EQ(0, cv::norm(output, gt, cv::NORM_INF));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(/**/, Core_LUT, testing::Combine( LutIdxType::all(), LutMatType::all()));
|
|
|
|
CV_ENUM(MaskType, CV_8U, CV_8S, CV_Bool)
|
|
typedef testing::TestWithParam<MaskType> Core_MaskTypeTest;
|
|
|
|
TEST_P(Core_MaskTypeTest, BasicArithm)
|
|
{
|
|
int mask_type = GetParam();
|
|
RNG& rng = theRNG();
|
|
const int MAX_DIM=3;
|
|
int sizes[MAX_DIM];
|
|
for( int iter = 0; iter < 100; iter++ )
|
|
{
|
|
int dims = rng.uniform(1, MAX_DIM+1);
|
|
int depth = rng.uniform(CV_8U, CV_64F+1);
|
|
int cn = rng.uniform(1, 6);
|
|
int type = CV_MAKETYPE(depth, cn);
|
|
int op = rng.uniform(0, depth < CV_32F ? 5 : 2); // don't run binary operations between floating-point values
|
|
int depth1 = op <= 1 ? CV_64F : depth;
|
|
for (int k = 0; k < MAX_DIM; k++)
|
|
{
|
|
sizes[k] = k < dims ? rng.uniform(1, 30) : 0;
|
|
}
|
|
|
|
Mat a(dims, sizes, type), a1;
|
|
Mat b(dims, sizes, type), b1;
|
|
Mat mask(dims, sizes, mask_type);
|
|
Mat mask1;
|
|
Mat c, d;
|
|
|
|
rng.fill(a, RNG::UNIFORM, 0, 100);
|
|
rng.fill(b, RNG::UNIFORM, 0, 100);
|
|
|
|
// [-2,2) range means that the each generated random number
|
|
// will be one of -2, -1, 0, 1. Saturated to [0,255], it will become
|
|
// 0, 0, 0, 1 => the mask will be filled by ~25%.
|
|
rng.fill(mask, RNG::UNIFORM, -2, 2);
|
|
|
|
a.convertTo(a1, depth1);
|
|
b.convertTo(b1, depth1);
|
|
// invert the mask
|
|
cv::compare(mask, 0, mask1, CMP_EQ);
|
|
a1.setTo(0, mask1);
|
|
b1.setTo(0, mask1);
|
|
|
|
if( op == 0 )
|
|
{
|
|
cv::add(a, b, c, mask);
|
|
cv::add(a1, b1, d);
|
|
}
|
|
else if( op == 1 )
|
|
{
|
|
cv::subtract(a, b, c, mask);
|
|
cv::subtract(a1, b1, d);
|
|
}
|
|
else if( op == 2 )
|
|
{
|
|
cv::bitwise_and(a, b, c, mask);
|
|
cv::bitwise_and(a1, b1, d);
|
|
}
|
|
else if( op == 3 )
|
|
{
|
|
cv::bitwise_or(a, b, c, mask);
|
|
cv::bitwise_or(a1, b1, d);
|
|
}
|
|
else if( op == 4 )
|
|
{
|
|
cv::bitwise_xor(a, b, c, mask);
|
|
cv::bitwise_xor(a1, b1, d);
|
|
}
|
|
Mat d1;
|
|
d.convertTo(d1, depth);
|
|
EXPECT_LE(cvtest::norm(c, d1, NORM_INF), DBL_EPSILON);
|
|
}
|
|
}
|
|
|
|
TEST_P(Core_MaskTypeTest, MinMaxIdx)
|
|
{
|
|
int mask_type = GetParam();
|
|
const int rows = 4;
|
|
const int cols = 3;
|
|
uchar mask_[rows*cols] = {
|
|
255, 255, 1,
|
|
255, 0, 255,
|
|
0, 1, 255,
|
|
0, 0, 255
|
|
};
|
|
uchar src_[rows*cols] = {
|
|
1, 1, 1,
|
|
1, 1, 1,
|
|
2, 1, 1,
|
|
2, 2, 1
|
|
};
|
|
Mat mask(Size(cols, rows), mask_type, mask_);
|
|
Mat src(Size(cols, rows), CV_8UC1, src_);
|
|
double minVal = -0.0, maxVal = -0.0;
|
|
int minIdx[2] = { -2, -2 }, maxIdx[2] = { -2, -2 };
|
|
cv::minMaxIdx(src, &minVal, &maxVal, minIdx, maxIdx, mask);
|
|
EXPECT_EQ(0, minIdx[0]);
|
|
EXPECT_EQ(0, minIdx[1]);
|
|
EXPECT_EQ(0, maxIdx[0]);
|
|
EXPECT_EQ(0, maxIdx[1]);
|
|
}
|
|
|
|
TEST_P(Core_MaskTypeTest, Norm)
|
|
{
|
|
int mask_type = GetParam();
|
|
int cn = 3;
|
|
Size sz(9, 4); // width < 16
|
|
Mat a(sz, CV_MAKE_TYPE(CV_16U, cn), Scalar::all(1));
|
|
Mat b(sz, CV_MAKE_TYPE(CV_16U, cn), Scalar::all(2));
|
|
uchar mask_[9*4] = {
|
|
255, 255, 255, 0, 1, 255, 0, 255, 0,
|
|
0, 255, 0, 0, 255, 255, 255, 255, 0,
|
|
0, 0, 0, 255, 0, 1, 0, 255, 255,
|
|
0, 0, 255, 0, 255, 255, 1, 0, 255
|
|
};
|
|
Mat mask(sz, mask_type, mask_);
|
|
|
|
EXPECT_EQ((double)9*4*cn, cv::norm(a, b, NORM_L1)); // without mask, IPP works well
|
|
EXPECT_EQ((double)20*cn, cv::norm(a, b, NORM_L1, mask));
|
|
}
|
|
|
|
TEST_P(Core_MaskTypeTest, Mean)
|
|
{
|
|
int mask_type = GetParam();
|
|
Size sz(9, 4);
|
|
Mat a(sz, CV_16UC1, Scalar::all(1));
|
|
uchar mask_[9*4] = {
|
|
255, 255, 255, 0, 1, 255, 0, 255, 0,
|
|
0, 255, 0, 0, 255, 255, 255, 255, 0,
|
|
0, 0, 0, 1, 0, 255, 0, 1, 255,
|
|
0, 0, 255, 0, 255, 255, 255, 0, 255
|
|
};
|
|
Mat mask(sz, mask_type, mask_);
|
|
a.setTo(2, mask);
|
|
|
|
Scalar result = cv::mean(a, mask);
|
|
EXPECT_NEAR(result[0], 2, 1e-6);
|
|
}
|
|
|
|
TEST_P(Core_MaskTypeTest, MeanStdDev)
|
|
{
|
|
int mask_type = GetParam();
|
|
Size sz(9, 4);
|
|
Mat a(sz, CV_16UC1, Scalar::all(1));
|
|
uchar mask_[9*4] = {
|
|
255, 255, 255, 0, 1, 255, 0, 255, 0,
|
|
0, 255, 0, 0, 255, 255, 255, 255, 0,
|
|
0, 0, 0, 1, 0, 255, 0, 1, 255,
|
|
0, 0, 255, 0, 255, 255, 255, 0, 255
|
|
};
|
|
Mat mask(sz, mask_type, mask_);
|
|
a.setTo(2, mask);
|
|
|
|
Scalar m, stddev;
|
|
cv::meanStdDev(a, m, stddev, mask);
|
|
|
|
EXPECT_NEAR(m[0], 2, 1e-6);
|
|
EXPECT_NEAR(stddev[0], 0, 1e-6);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(/**/, Core_MaskTypeTest, MaskType::all());
|
|
|
|
// Still fails in 5.x: https://github.com/opencv/opencv/issues/28557
|
|
TEST(Core_Arithm, DISABLED_mul_overflow_28557)
|
|
{
|
|
uint16_t data[] = {5000, 60000, 5000, 60000, 5000, 60000};
|
|
cv::Mat m(1, 6, CV_16U, data);
|
|
cv::Mat res = m.mul(m);
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
EXPECT_EQ(65535, res.at<uint16_t>(0, i));
|
|
}
|
|
}
|
|
|
|
}} // namespace
|