Merge branch 4.x

pull/25384/head
Alexander Smorkalov 8 months ago
commit 282c762ead
  1. 2
      doc/js_tutorials/js_gui/js_image_display/js_image_display.markdown
  2. 2
      doc/tutorials/introduction/env_reference/env_reference.markdown
  3. 20
      modules/calib/src/calibinit.cpp
  4. 86
      modules/core/include/opencv2/core.hpp
  5. 15
      modules/core/src/hal_replacement.hpp
  6. 2
      modules/core/src/matrix_transform.cpp
  7. 100
      modules/dnn/src/layers/cpu_kernels/conv_depthwise.simd.hpp
  8. 11
      modules/dnn/src/onnx/onnx_importer.cpp
  9. 7
      modules/dnn/src/tflite/tflite_importer.cpp
  10. 4
      modules/dnn/test/test_onnx_importer.cpp
  11. 10
      modules/dnn/test/test_tflite_importer.cpp
  12. 2
      modules/gapi/CMakeLists.txt
  13. 7
      modules/highgui/misc/java/src/java/highgui+HighGui.java
  14. 46
      modules/imgcodecs/include/opencv2/imgcodecs.hpp
  15. 18
      modules/imgcodecs/test/test_tiff.cpp
  16. 13
      modules/imgproc/include/opencv2/imgproc.hpp
  17. 30
      modules/imgproc/include/opencv2/imgproc/detail/legacy.hpp
  18. 5
      modules/imgproc/src/contours.cpp
  19. 354
      modules/imgproc/src/contours_approx.cpp
  20. 75
      modules/imgproc/src/contours_common.cpp
  21. 219
      modules/imgproc/src/contours_common.hpp
  22. 417
      modules/imgproc/src/contours_link.cpp
  23. 697
      modules/imgproc/src/contours_new.cpp
  24. 2
      modules/imgproc/src/phasecorr.cpp
  25. 3
      modules/imgproc/test/test_contours.cpp
  26. 605
      modules/imgproc/test/test_contours_new.cpp
  27. 172
      modules/imgproc/test/test_filter.cpp
  28. 54
      modules/imgproc/test/test_thresh.cpp
  29. 3
      modules/objdetect/src/qrcode.cpp
  30. 308
      modules/videoio/src/cap_v4l.cpp
  31. 30
      modules/videoio/test/test_v4l2.cpp
  32. 14
      samples/dnn/models.yml

@ -45,7 +45,7 @@ cv.cvtColor(dst, dst, cv.COLOR_***2RGBA);
Then, new an ImageData obj from dst:
@code{.js}
let imgData = new ImageData(new Uint8ClampedArray(dst.data, dst.cols, dst.rows);
let imgData = new ImageData(new Uint8ClampedArray(dst.data), dst.cols, dst.rows);
@endcode
Finally, display it:

@ -123,7 +123,7 @@ Some modules have multiple available backends, following variables allow choosin
| OPENCV_PARALLEL_PRIORITY_LIST | string, `,`-separated | | list of backends in priority order |
| OPENCV_UI_BACKEND | string | | choose highgui backend for window rendering (one of `GTK`, `GTK3`, `GTK2`, `QT`, `WIN32`) |
| OPENCV_UI_PRIORITY_${NAME} | num | | set highgui backend priority, default is 1000 |
| OPENCV_UI_PRIORITY_LIST | string, `,`-separated | | list of hioghgui backends in priority order |
| OPENCV_UI_PRIORITY_LIST | string, `,`-separated | | list of highgui backends in priority order |
| OPENCV_VIDEOIO_PRIORITY_${NAME} | num | | set videoio backend priority, default is 1000 |
| OPENCV_VIDEOIO_PRIORITY_LIST | string, `,`-separated | | list of videoio backends in priority order |

@ -1641,10 +1641,11 @@ void ChessBoardDetector::findQuadNeighbors()
continue;
float min_dist = FLT_MAX;
int closest_neighbor_idx = -1;
int closest_corner_idx = -1;
ChessBoardQuad *closest_quad = 0;
Point2f pt = cur_quad.corners[i]->pt;
Point2f pt = all_quads_pts[(idx << 2) + i];
// find the closest corner in all other quadrangles
std::vector<float> query = Mat(pt);
@ -1664,7 +1665,7 @@ void ChessBoardDetector::findQuadNeighbors()
if (q_k.neighbors[j])
continue;
const float dist = normL2Sqr<float>(pt - q_k.corners[j]->pt);
const float dist = normL2Sqr<float>(pt - all_quads_pts[neighbor_idx]);
if (dist < min_dist &&
dist <= cur_quad.edge_len * thresh_scale &&
dist <= q_k.edge_len * thresh_scale)
@ -1679,6 +1680,7 @@ void ChessBoardDetector::findQuadNeighbors()
DPRINTF("Incompatible edge lengths");
continue;
}
closest_neighbor_idx = neighbor_idx;
closest_corner_idx = j;
closest_quad = &q_k;
min_dist = dist;
@ -1686,7 +1688,7 @@ void ChessBoardDetector::findQuadNeighbors()
}
// we found a matching corner point?
if (closest_corner_idx >= 0 && min_dist < FLT_MAX)
if (closest_neighbor_idx >= 0 && closest_corner_idx >= 0 && min_dist < FLT_MAX)
{
CV_Assert(closest_quad);
@ -1698,6 +1700,7 @@ void ChessBoardDetector::findQuadNeighbors()
// This is necessary to support small squares where otherwise the wrong
// corner will get matched to closest_quad;
ChessBoardCorner& closest_corner = *closest_quad->corners[closest_corner_idx];
cv::Point2f closest_corner_pt = all_quads_pts[closest_neighbor_idx];
int j = 0;
for (; j < 4; j++)
@ -1705,7 +1708,7 @@ void ChessBoardDetector::findQuadNeighbors()
if (cur_quad.neighbors[j] == closest_quad)
break;
if (normL2Sqr<float>(closest_corner.pt - cur_quad.corners[j]->pt) < min_dist)
if (normL2Sqr<float>(closest_corner_pt - all_quads_pts[(idx << 2) + j]) < min_dist)
break;
}
if (j < 4)
@ -1720,9 +1723,8 @@ void ChessBoardDetector::findQuadNeighbors()
if (j < 4)
continue;
// check whether the closest corner to closest_corner
// is different from cur_quad->corners[i]->pt
query = Mat(closest_corner.pt);
// check whether the closest corner to closest_corner is different from pt
query = Mat(closest_corner_pt);
radius = min_dist + 1;
neighbors_count = all_quads_pts_index.radiusSearch(query, neighbors_indices, neighbors_dists, radius, search_params);
@ -1740,14 +1742,14 @@ void ChessBoardDetector::findQuadNeighbors()
CV_DbgAssert(q);
if (!q->neighbors[k])
{
if (normL2Sqr<float>(closest_corner.pt - q->corners[k]->pt) < min_dist)
if (normL2Sqr<float>(closest_corner_pt - all_quads_pts[neighbor_idx]) < min_dist)
break;
}
}
if (neighbor_idx_idx < neighbors_count)
continue;
closest_corner.pt = (pt + closest_corner.pt) * 0.5f;
closest_corner.pt = (pt + closest_corner_pt) * 0.5f;
// We've found one more corner - remember it
cur_quad.count++;

@ -248,7 +248,7 @@ CV_EXPORTS void swap( UMat& a, UMat& b );
The function computes and returns the coordinate of a donor pixel corresponding to the specified
extrapolated pixel when using the specified extrapolation border mode. For example, if you use
cv::BORDER_WRAP mode in the horizontal direction, cv::BORDER_REFLECT_101 in the vertical direction and
want to compute value of the "virtual" pixel Point(-5, 100) in a floating-point image img , it
want to compute value of the "virtual" pixel Point(-5, 100) in a floating-point image img, it
looks like:
@code{.cpp}
float val = img.at<float>(borderInterpolate(100, img.rows, cv::BORDER_REFLECT_101),
@ -259,7 +259,7 @@ copyMakeBorder.
@param p 0-based coordinate of the extrapolated pixel along one of the axes, likely \<0 or \>= len
@param len Length of the array along the corresponding axis.
@param borderType Border type, one of the #BorderTypes, except for #BORDER_TRANSPARENT and
#BORDER_ISOLATED . When borderType==#BORDER_CONSTANT , the function always returns -1, regardless
#BORDER_ISOLATED. When borderType==#BORDER_CONSTANT, the function always returns -1, regardless
of p and len.
@sa copyMakeBorder
@ -586,9 +586,19 @@ CV_EXPORTS_AS(sumElems) Scalar sum(InputArray src);
/** @brief Checks for the presence of at least one non-zero array element.
The function returns whether there are non-zero elements in src
@note CV_16F/CV_16BF/CV_Bool/CV_64U/CV_64S/CV_32U are not supported for src.
The function do not work with multi-channel arrays. If you need to check non-zero array
elements across all the channels, use Mat::reshape first to reinterpret the array as
single-channel. Or you may extract the particular channel using either extractImageCOI, or
mixChannels, or split.
@note
- CV_16F/CV_16BF/CV_Bool/CV_64U/CV_64S/CV_32U are not supported for src.
- If the location of non-zero array elements is important, @ref findNonZero is helpful.
- If the count of non-zero array elements is important, @ref countNonZero is helpful.
@param src single-channel array.
@sa mean, meanStdDev, norm, minMaxLoc, calcCovarMatrix
@sa findNonZero, countNonZero
*/
CV_EXPORTS_W bool hasNonZero( InputArray src );
@ -596,9 +606,19 @@ CV_EXPORTS_W bool hasNonZero( InputArray src );
The function returns the number of non-zero elements in src :
\f[\sum _{I: \; \texttt{src} (I) \ne0 } 1\f]
@note CV_16F/CV_16BF/CV_Bool/CV_64U/CV_64S/CV_32U are not supported for src.
The function do not work with multi-channel arrays. If you need to count non-zero array
elements across all the channels, use Mat::reshape first to reinterpret the array as
single-channel. Or you may extract the particular channel using either extractImageCOI, or
mixChannels, or split.
@note
- CV_16F/CV_16BF/CV_Bool/CV_64U/CV_64S/CV_32U are not supported for src.
- If only whether there are non-zero elements is important, @ref hasNonZero is helpful.
- If the location of non-zero array elements is important, @ref findNonZero is helpful.
@param src single-channel array.
@sa mean, meanStdDev, norm, minMaxLoc, calcCovarMatrix
@sa findNonZero, hasNonZero
*/
CV_EXPORTS_W int countNonZero( InputArray src );
@ -625,9 +645,19 @@ or
// access pixel coordinates
Point pnt = locations[i];
@endcode
@note CV_16F/CV_16BF/CV_Bool/CV_64U/CV_64S/CV_32U are not supported for src.
The function do not work with multi-channel arrays. If you need to find non-zero
elements across all the channels, use Mat::reshape first to reinterpret the array as
single-channel. Or you may extract the particular channel using either extractImageCOI, or
mixChannels, or split.
@note
- CV_16F/CV_16BF/CV_Bool/CV_64U/CV_64S/CV_32U are not supported for src.
- If only count of non-zero array elements is important, @ref countNonZero is helpful.
- If only whether there are non-zero elements is important, @ref hasNonZero is helpful.
@param src single-channel array
@param idx the output array, type of cv::Mat or std::vector<Point>, corresponding to non-zero indices in the input
@sa countNonZero, hasNonZero
*/
CV_EXPORTS_W void findNonZero( InputArray src, OutputArray idx );
@ -834,8 +864,8 @@ array region.
The function do not work with multi-channel arrays. If you need to find minimum or maximum
elements across all the channels, use Mat::reshape first to reinterpret the array as
single-channel. Or you may extract the particular channel using either extractImageCOI , or
mixChannels , or split .
single-channel. Or you may extract the particular channel using either extractImageCOI, or
mixChannels, or split.
@note CV_16F/CV_16BF/CV_Bool/CV_64U/CV_64S/CV_32U are not supported for src.
@param src input single-channel array.
@param minVal pointer to the returned minimum value; NULL is used if not required.
@ -889,8 +919,8 @@ The function cv::minMaxIdx finds the minimum and maximum element values and thei
extremums are searched across the whole array or, if mask is not an empty array, in the specified
array region. The function does not work with multi-channel arrays. If you need to find minimum or
maximum elements across all the channels, use Mat::reshape first to reinterpret the array as
single-channel. Or you may extract the particular channel using either extractImageCOI , or
mixChannels , or split . In case of a sparse matrix, the minimum is found among non-zero elements
single-channel. Or you may extract the particular channel using either extractImageCOI, or
mixChannels, or split. In case of a sparse matrix, the minimum is found among non-zero elements
only.
@note When minIdx is not NULL, it must have at least 2 elements (as well as maxIdx), even if src is
a single-row or single-column matrix. In OpenCV (following MATLAB) each array has at least 2
@ -927,8 +957,8 @@ CV_EXPORTS void minMaxLoc(const SparseMat& a, double* minVal,
The function #reduce reduces the matrix to a vector by treating the matrix rows/columns as a set of
1D vectors and performing the specified operation on the vectors until a single row/column is
obtained. For example, the function can be used to compute horizontal and vertical projections of a
raster image. In case of #REDUCE_MAX and #REDUCE_MIN , the output image should have the same type as the source one.
In case of #REDUCE_SUM, #REDUCE_SUM2 and #REDUCE_AVG , the output may have a larger element bit-depth to preserve accuracy.
raster image. In case of #REDUCE_MAX and #REDUCE_MIN, the output image should have the same type as the source one.
In case of #REDUCE_SUM, #REDUCE_SUM2 and #REDUCE_AVG, the output may have a larger element bit-depth to preserve accuracy.
And multi-channel arrays are also supported in these two reduction modes.
The following code demonstrates its usage for a single channel matrix.
@ -982,7 +1012,7 @@ CV_EXPORTS_W void merge(InputArrayOfArrays mv, OutputArray dst);
The function cv::split splits a multi-channel array into separate single-channel arrays:
\f[\texttt{mv} [c](I) = \texttt{src} (I)_c\f]
If you need to extract a single channel or do some other sophisticated channel permutation, use
mixChannels .
mixChannels.
The following example demonstrates how to split a 3-channel matrix into 3 single channel matrices.
@snippet snippets/core_split.cpp example
@ -1123,7 +1153,7 @@ The example scenarios of using the function are the following:
flipping around the x-axis and positive value (for example, 1) means
flipping around y-axis. Negative value (for example, -1) means flipping
around both axes.
@sa transpose , repeat , completeSymm
@sa transpose, repeat, completeSymm
*/
CV_EXPORTS_W void flip(InputArray src, OutputArray dst, int flipCode);
@ -1155,7 +1185,7 @@ The function cv::rotate rotates the array in one of three different ways:
@param dst output array of the same type as src. The size is the same with ROTATE_180,
and the rows and cols are switched for ROTATE_90_CLOCKWISE and ROTATE_90_COUNTERCLOCKWISE.
@param rotateCode an enum to specify how to rotate the array; see the enum #RotateFlags
@sa transpose , repeat , completeSymm, flip, RotateFlags
@sa transpose, repeat, completeSymm, flip, RotateFlags
*/
CV_EXPORTS_W void rotate(InputArray src, OutputArray dst, int rotateCode);
@ -1589,7 +1619,7 @@ converts denormalized values to zeros on output. Special values (NaN,
Inf) are not handled.
@param src input array.
@param dst output array of the same size and type as src.
@sa log , cartToPolar , polarToCart , phase , pow , sqrt , magnitude
@sa log, cartToPolar, polarToCart, phase, pow, sqrt, magnitude
*/
CV_EXPORTS_W void exp(InputArray src, OutputArray dst);
@ -1742,7 +1772,7 @@ should have the same type as src1 and src2.
@param dst output matrix; it has the proper size and the same type as
input matrices.
@param flags operation flags (cv::GemmFlags)
@sa mulTransposed , transform
@sa mulTransposed, transform
*/
CV_EXPORTS_W void gemm(InputArray src1, InputArray src2, double alpha,
InputArray src3, double beta, OutputArray dst, int flags = 0);
@ -1752,7 +1782,7 @@ CV_EXPORTS_W void gemm(InputArray src1, InputArray src2, double alpha,
The function cv::mulTransposed calculates the product of src and its
transposition:
\f[\texttt{dst} = \texttt{scale} ( \texttt{src} - \texttt{delta} )^T ( \texttt{src} - \texttt{delta} )\f]
if aTa=true , and
if aTa=true, and
\f[\texttt{dst} = \texttt{scale} ( \texttt{src} - \texttt{delta} ) ( \texttt{src} - \texttt{delta} )^T\f]
otherwise. The function is used to calculate the covariance matrix. With
zero delta, it can be used as a faster substitute for general matrix
@ -1765,7 +1795,7 @@ description below.
@param delta Optional delta matrix subtracted from src before the
multiplication. When the matrix is empty ( delta=noArray() ), it is
assumed to be zero, that is, nothing is subtracted. If it has the same
size as src , it is simply subtracted. Otherwise, it is "repeated" (see
size as src, it is simply subtracted. Otherwise, it is "repeated" (see
repeat ) to cover the full src and then subtracted. Type of the delta
matrix, when it is not empty, must be the same as the type of created
output matrix. See the dtype parameter description below.
@ -2039,7 +2069,7 @@ in the descending order.
@param eigenvectors output matrix of eigenvectors; it has the same size and type as src; the
eigenvectors are stored as subsequent matrix rows, in the same order as the corresponding
eigenvalues.
@sa eigenNonSymmetric, completeSymm , PCA
@sa eigenNonSymmetric, completeSymm, PCA
*/
CV_EXPORTS_W bool eigen(InputArray src, OutputArray eigenvalues,
OutputArray eigenvectors = noArray());
@ -2179,7 +2209,7 @@ So, the function chooses an operation mode depending on the flags and size of th
If #DFT_SCALE is set, the scaling is done after the transformation.
Unlike dct , the function supports arrays of arbitrary size. But only those arrays are processed
Unlike dct, the function supports arrays of arbitrary size. But only those arrays are processed
efficiently, whose sizes can be factorized in a product of small prime numbers (2, 3, and 5 in the
current implementation). Such an efficient DFT size can be calculated using the getOptimalDFTSize
method.
@ -2262,8 +2292,8 @@ nonzeroRows rows of the input array (#DFT_INVERSE is not set) or only the first
output array (#DFT_INVERSE is set) contain non-zeros, thus, the function can handle the rest of the
rows more efficiently and save some time; this technique is very useful for calculating array
cross-correlation or convolution using DFT.
@sa dct , getOptimalDFTSize , mulSpectrums, filter2D , matchTemplate , flip , cartToPolar ,
magnitude , phase
@sa dct, getOptimalDFTSize, mulSpectrums, filter2D, matchTemplate, flip, cartToPolar,
magnitude, phase
*/
CV_EXPORTS_W void dft(InputArray src, OutputArray dst, int flags = 0, int nonzeroRows = 0);
@ -2300,9 +2330,9 @@ floating-point array:
\f[X = \left (C^{(N)} \right )^T \cdot X \cdot C^{(N)}\f]
The function chooses the mode of operation by looking at the flags and size of the input array:
- If (flags & #DCT_INVERSE) == 0 , the function does a forward 1D or 2D transform. Otherwise, it
- If (flags & #DCT_INVERSE) == 0, the function does a forward 1D or 2D transform. Otherwise, it
is an inverse 1D or 2D transform.
- If (flags & #DCT_ROWS) != 0 , the function performs a 1D transform of each row.
- If (flags & #DCT_ROWS) != 0, the function performs a 1D transform of each row.
- If the array is a single column or a single row, the function performs a 1D transform.
- If none of the above is true, the function performs a 2D transform.
@ -2318,7 +2348,7 @@ of a vector of size N/2 . Thus, the optimal DCT size N1 \>= N can be calculated
@param src input floating-point array.
@param dst output array of the same size and type as src .
@param flags transformation flags as a combination of cv::DftFlags (DCT_*)
@sa dft , getOptimalDFTSize , idct
@sa dft, getOptimalDFTSize, idct
*/
CV_EXPORTS_W void dct(InputArray src, OutputArray dst, int flags = 0);
@ -2337,7 +2367,7 @@ CV_EXPORTS_W void idct(InputArray src, OutputArray dst, int flags = 0);
The function cv::mulSpectrums performs the per-element multiplication of the two CCS-packed or complex
matrices that are results of a real or complex Fourier transform.
The function, together with dft and idft , may be used to calculate convolution (pass conjB=false )
The function, together with dft and idft, may be used to calculate convolution (pass conjB=false )
or correlation (pass conjB=true ) of two arrays rapidly. When the arrays are complex, they are
simply multiplied (per element) with an optional conjugation of the second-array elements. When the
arrays are real, they are assumed to be CCS-packed (see dft for details).
@ -2371,7 +2401,7 @@ While the function cannot be used directly to estimate the optimal vector size f
(since the current DCT implementation supports only even-size vectors), it can be easily processed
as getOptimalDFTSize((vecsize+1)/2)\*2.
@param vecsize vector size.
@sa dft , dct , idft , idct , mulSpectrums
@sa dft, dct, idft, idct, mulSpectrums
*/
CV_EXPORTS_W int getOptimalDFTSize(int vecsize);
@ -2923,7 +2953,7 @@ public:
The methods transform the state using the MWC algorithm and return the
next random number. The first form is equivalent to RNG::next . The
second form returns the random number modulo N , which means that the
second form returns the random number modulo N, which means that the
result is in the range [0, N) .
*/
unsigned operator ()();

@ -932,7 +932,6 @@ inline int hal_ni_flip(int src_type, const uchar* src_data, size_t src_step, int
#define cv_hal_flip hal_ni_flip
//! @endcond
/**
@brief rotate90
@param src_type source and destination image type
@ -955,6 +954,20 @@ inline int hal_ni_rotate90(int src_type, const uchar* src_data, size_t src_step,
#define cv_hal_rotate90 hal_ni_rotate90
//! @endcond
/**
@brief Transpose2d
@param src_data,src_step Source image
@param dst_data,dst_step Destination image
@param src_width,src_height Source image dimensions
@param element_size Size of an element in bytes
*/
inline int hal_ni_transpose2d(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, int src_width,
int src_height, int element_size) { return CV_HAL_ERROR_NOT_IMPLEMENTED; }
//! @cond IGNORED
#define cv_hal_transpose2d hal_ni_transpose2d
//! @endcond
//! @}

@ -269,6 +269,8 @@ void transpose( InputArray _src, OutputArray _dst )
return;
}
CALL_HAL(transpose2d, cv_hal_transpose2d, src.data, src.step, dst.data, dst.step, src.cols, src.rows, esz);
CV_IPP_RUN_FAST(ipp_transpose(src, dst))
if( dst.data == src.data )

@ -209,34 +209,6 @@ void fastDepthwiseConv( const float* wptr,
#if !defined(CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY) && CV_RVV
/*
Example for load_deinterleave:
input: ptr[16] = {1,2,3, ... ,14,15,16}
output: a = {1, 3, 5, 7, 9, 11, 13, 15}
output: b = {2, 4, 6, 8,10, 12, 14, 16}
*/
static inline void vfloat32m2_load_deinterleave(const float* ptr, vfloat32m2_t& a, vfloat32m2_t& b, int vl)
{
vuint64m4_t mask = vmv_v_x_u64m4(1,vl*2);
vuint32m4_t mask_re = vreinterpret_v_u64m4_u32m4(mask);
vbool8_t mask0 = vmseq_vx_u32m4_b8 (mask_re, 1, vl*2);
vbool8_t mask1 = vmseq_vx_u32m4_b8 (mask_re, 0, vl*2);
vfloat32m4_t tempa = vundefined_f32m4(), tempb = vundefined_f32m4();
vfloat32m4_t vw = vle32_v_f32m4(ptr, vl*2);
tempa = vcompress_vm_f32m4(mask0, tempa, vw, vl*2);
tempb = vcompress_vm_f32m4(mask1, tempb, vw, vl*2);
/* The following instructions have not to be supported by the GNU toolchain.
So we temporarily use store and load instead.
// a = vlmul_trunc_v_f32m4_f32m2(tempa);
// b = vlmul_trunc_v_f32m4_f32m2(tempb);
*/
cv::AutoBuffer<float> cvBuffer(sizeof(float)*vl*2);
float* buffer = (float*)cvBuffer.data();
vse32_v_f32m4(buffer, tempa, vl);
a = vle32_v_f32m2(buffer, vl);
vse32_v_f32m4(buffer, tempb, vl);
b = vle32_v_f32m2(buffer, vl);
}
void fastDepthwiseConv( const float* wptr,
int kernel_h, int kernel_w,
@ -292,64 +264,40 @@ void fastDepthwiseConv( const float* wptr,
if( stride_w == 1 )
for( ; out_j < outW1; out_j += vl, avl -= vl)
{
vl = vsetvl_e32m2(avl);
vl = vsetvl_e32m8(avl);
int in_j = out_j * stride_w - pad_l;
vfloat32m2_t v00 = vle32_v_f32m2(imgptr0 + in_j, vl),
v01 = vle32_v_f32m2(imgptr0 + in_j + dilation_w, vl),
v02 = vle32_v_f32m2(imgptr0 + in_j + dilation_w*2, vl),
v10 = vle32_v_f32m2(imgptr1 + in_j, vl),
v11 = vle32_v_f32m2(imgptr1 + in_j + dilation_w, vl),
v12 = vle32_v_f32m2(imgptr1 + in_j + dilation_w*2, vl),
v20 = vle32_v_f32m2(imgptr2 + in_j, vl),
v21 = vle32_v_f32m2(imgptr2 + in_j + dilation_w, vl),
v22 = vle32_v_f32m2(imgptr2 + in_j + dilation_w*2, vl);
vfloat32m2_t vout0 = vfmul_vf_f32m2(v00, w00, vl);
vfloat32m2_t vout1 = vfmul_vf_f32m2(v01, w01, vl);
vfloat32m2_t vout2 = vfmul_vf_f32m2(v02, w02, vl);
vout0 = vfadd_vf_f32m2(vout0, bias, vl);
vout0 = vfmacc_vf_f32m2(vout0, w10, v10, vl);
vout1 = vfmacc_vf_f32m2(vout1, w11, v11, vl);
vout2 = vfmacc_vf_f32m2(vout2, w12, v12, vl);
vout0 = vfmacc_vf_f32m2(vout0, w20, v20, vl);
vout1 = vfmacc_vf_f32m2(vout1, w21, v21, vl);
vout2 = vfmacc_vf_f32m2(vout2, w22, v22, vl);
vout0 = vfadd_vv_f32m2(vfadd_vv_f32m2(vout0, vout1, vl), vout2, vl);
vfloat32m8_t vout0 = vfmacc_vf_f32m8(vfmv_v_f_f32m8(bias, vl), w00, vle32_v_f32m8(imgptr0 + in_j, vl), vl);
vout0 = vfmacc_vf_f32m8(vout0, w01, vle32_v_f32m8(imgptr0 + in_j + dilation_w, vl), vl);
vout0 = vfmacc_vf_f32m8(vout0, w02, vle32_v_f32m8(imgptr0 + in_j + dilation_w*2, vl), vl);
vout0 = vfmacc_vf_f32m8(vout0, w10, vle32_v_f32m8(imgptr1 + in_j, vl),vl);
vout0 = vfmacc_vf_f32m8(vout0, w11, vle32_v_f32m8(imgptr1 + in_j + dilation_w, vl),vl);
vout0 = vfmacc_vf_f32m8(vout0, w12, vle32_v_f32m8(imgptr1 + in_j + dilation_w*2, vl),vl);
vout0 = vfmacc_vf_f32m8(vout0, w20, vle32_v_f32m8(imgptr2 + in_j, vl), vl);
vout0 = vfmacc_vf_f32m8(vout0, w21, vle32_v_f32m8(imgptr2 + in_j + dilation_w, vl), vl);
vout0 = vfmacc_vf_f32m8(vout0, w22, vle32_v_f32m8(imgptr2 + in_j + dilation_w*2, vl), vl);
if (relu)
{
vbool16_t m = vmfgt_vf_f32m2_b16(vout0, 0, vl);
vout0 = vmerge_vvm_f32m2(m, vfmul_vf_f32m2(vout0, relu_coeff, vl), vout0, vl);
vbool4_t m = vmfgt_vf_f32m8_b4(vout0, 0, vl);
vout0 = vmerge_vvm_f32m8(m, vfmul_vf_f32m8(vout0, relu_coeff, vl), vout0, vl);
}
vse32_v_f32m2(outptr + out_j, vout0, vl);
vse32_v_f32m8(outptr + out_j, vout0, vl);
}
else //stride_w == 2 && dilation_w == 1
for( ; out_j < outW1; out_j += vl, avl -= vl)
{
vl = vsetvl_e32m2(avl);
int in_j = out_j * stride_w - pad_l;
vfloat32m2_t v00, v01, v02, v10, v11, v12, v20, v21, v22, unused;
vfloat32m2_load_deinterleave(imgptr0 + in_j, v00, v01, vl);
vfloat32m2_load_deinterleave(imgptr0 + in_j + 2, v02, unused, vl);
vfloat32m2_load_deinterleave(imgptr1 + in_j, v10, v11, vl);
vfloat32m2_load_deinterleave(imgptr1 + in_j + 2, v12, unused, vl);
vfloat32m2_load_deinterleave(imgptr2 + in_j, v20, v21, vl);
vfloat32m2_load_deinterleave(imgptr2 + in_j + 2, v22, unused, vl);
vfloat32m2_t vout0 = vfmul_vf_f32m2(v00, w00, vl);
vfloat32m2_t vout1 = vfmul_vf_f32m2(v01, w01, vl);
vfloat32m2_t vout2 = vfmul_vf_f32m2(v02, w02, vl);
vout0 = vfadd_vf_f32m2(vout0, bias, vl);
vout0 = vfmacc_vf_f32m2(vout0, w10, v10, vl);
vout1 = vfmacc_vf_f32m2(vout1, w11, v11, vl);
vout2 = vfmacc_vf_f32m2(vout2, w12, v12, vl);
vout0 = vfmacc_vf_f32m2(vout0, w20, v20, vl);
vout1 = vfmacc_vf_f32m2(vout1, w21, v21, vl);
vout2 = vfmacc_vf_f32m2(vout2, w22, v22, vl);
vfloat32m2_t vout0 = vfmacc_vf_f32m2(vfmv_v_f_f32m2(bias, vl), w00, vlse32_v_f32m2(imgptr0+in_j , 8, vl), vl);
vfloat32m2_t vout1 = vfmul_vf_f32m2(vlse32_v_f32m2(imgptr0+in_j+1, 8, vl), w01, vl);
vfloat32m2_t vout2 = vfmul_vf_f32m2(vlse32_v_f32m2(imgptr0+in_j+2, 8, vl), w02, vl);
vout0 = vfmacc_vf_f32m2(vout0, w10, vlse32_v_f32m2(imgptr1+in_j , 8, vl), vl);
vout1 = vfmacc_vf_f32m2(vout1, w11, vlse32_v_f32m2(imgptr1+in_j+1, 8, vl), vl);
vout2 = vfmacc_vf_f32m2(vout2, w12, vlse32_v_f32m2(imgptr1+in_j+2, 8, vl), vl);
vout0 = vfmacc_vf_f32m2(vout0, w20, vlse32_v_f32m2(imgptr2+in_j , 8, vl), vl);
vout1 = vfmacc_vf_f32m2(vout1, w21, vlse32_v_f32m2(imgptr2+in_j+1, 8, vl), vl);
vout2 = vfmacc_vf_f32m2(vout2, w22, vlse32_v_f32m2(imgptr2+in_j+2, 8, vl), vl);
vout0 = vfadd_vv_f32m2(vfadd_vv_f32m2(vout0, vout1, vl), vout2, vl);
if (relu)

@ -92,7 +92,8 @@ class ONNXImporter
void addConstant(const std::string& name, const Mat& blob);
void addLayer(LayerParams& layerParams,
const opencv_onnx::NodeProto& node_proto);
const opencv_onnx::NodeProto& node_proto,
int num_inputs = std::numeric_limits<int>::max());
void setParamsDtype(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
void lstm_extractConsts(LayerParams& layerParams, const opencv_onnx::NodeProto& lstm_proto, size_t idx, int* blobShape_, int size);
@ -629,7 +630,8 @@ ONNXImporter::TensorInfo ONNXImporter::getBlobExtraInfo(const std::string& input
}
void ONNXImporter::addLayer(LayerParams& layerParams,
const opencv_onnx::NodeProto& node_proto)
const opencv_onnx::NodeProto& node_proto,
int num_inputs)
{
int depth = layerParams.get<int>("depth", CV_32F);
int id = dstNet.addLayer(layerParams.name, layerParams.type, depth, layerParams);
@ -644,7 +646,8 @@ void ONNXImporter::addLayer(LayerParams& layerParams,
std::vector<MatShape> layerInpShapes, layerOutShapes, layerInternalShapes;
int inpNum = 0;
for (int j = 0; j < node_proto.input_size(); j++)
num_inputs = std::min(node_proto.input_size(), num_inputs);
for (int j = 0; j < num_inputs; j++)
{
const std::string& input_name = node_proto.input(j);
IterLayerId_t layerId = layer_id.find(input_name);
@ -1818,7 +1821,7 @@ void ONNXImporter::parseClip(LayerParams& layerParams, const opencv_onnx::NodePr
layerParams.set("min_value", layerParams.get<float>("min", min_value));
layerParams.set("max_value", layerParams.get<float>("max", max_value));
addLayer(layerParams, node_proto);
addLayer(layerParams, node_proto, 1);
}
void ONNXImporter::parseLeakyRelu(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)

@ -300,6 +300,10 @@ void TFLiteImporter::addLayer(LayerParams& layerParams, const Operator& op) {
Mat blob = allTensors[idx];
layerParams.blobs.push_back(blob.u ? blob : blob.clone()); // some tensors are owned by OpenCV
}
} else {
for (auto& blob : layerParams.blobs) {
CV_Assert(blob.u);
}
}
int dtype = CV_32F;
@ -830,9 +834,6 @@ void TFLiteImporter::parseFullyConnected(const Operator& op, const std::string&
auto options = op.builtin_options_as_FullyConnectedOptions();
CV_Assert(options);
int idx = op.inputs()->Get(1);
Mat weights = allTensors[idx];
layerParams.blobs.resize(1, weights);
layerParams.set("transB", true);
layerParams.set("constB", true);
addLayer(layerParams, op);

@ -3119,6 +3119,10 @@ TEST_P(Test_ONNX_layers, MatMulAddFusion) {
testONNXModels("biased_matmul", npy, l1, lInf);
}
TEST_P(Test_ONNX_layers, ClipDivSharedConstant) {
testONNXModels("clip_div_shared_constant");
}
INSTANTIATE_TEST_CASE_P(/**/, Test_ONNX_nets, dnnBackendsAndTargets());
}} // namespace

@ -58,7 +58,13 @@ void Test_TFLite::testModel(Net& net, const std::string& modelName, const Mat& i
ASSERT_EQ(outs.size(), outNames.size());
for (int i = 0; i < outNames.size(); ++i) {
Mat ref = blobFromNPY(findDataFile(format("dnn/tflite/%s_out_%s.npy", modelName.c_str(), outNames[i].c_str())));
normAssert(ref.reshape(1, 1), outs[i].reshape(1, 1), outNames[i].c_str(), l1, lInf);
// A workaround solution for the following cases due to inconsistent shape definitions.
// The details please see: https://github.com/opencv/opencv/pull/25297#issuecomment-2039081369
if (modelName == "face_landmark" || modelName == "selfie_segmentation") {
ref = ref.reshape(1, 1);
outs[i] = outs[i].reshape(1, 1);
}
normAssert(ref, outs[i], outNames[i].c_str(), l1, lInf);
}
}
@ -240,8 +246,6 @@ TEST_P(Test_TFLite, split) {
}
TEST_P(Test_TFLite, fully_connected) {
if (backend == DNN_BACKEND_CUDA)
applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA, CV_TEST_TAG_DNN_SKIP_CUDA_FP16);
if (backend == DNN_BACKEND_VKCOM)
applyTestTag(CV_TEST_TAG_DNN_SKIP_VULKAN);
testLayer("fully_connected");

@ -41,7 +41,7 @@ if(MSVC)
# and IE deprecated code warning C4996
ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4503 /wd4996)
endif()
if(MSVC_VERSION LESS 1920) # MSVS 2015/2017
if((MSVC_VERSION LESS 1920) OR ARM OR AARCH64) # MSVS 2015/2017 on x86 and ARM
ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4702) # 'unreachable code'
endif()
endif()

@ -62,14 +62,9 @@ public final class HighGui {
if (m.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
int bufferSize = m.channels() * m.cols() * m.rows();
byte[] b = new byte[bufferSize];
m.get(0, 0, b); // get all the pixels
BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);
final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(b, 0, targetPixels, 0, b.length);
m.get(0, 0, targetPixels);
return image;
}

@ -103,9 +103,9 @@ enum ImwriteFlags {
IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values
IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI
IMWRITE_TIFF_YDPI = 258,//!< For TIFF, use to specify the Y direction DPI
IMWRITE_TIFF_COMPRESSION = 259,//!< For TIFF, use to specify the image compression scheme. See libtiff for integer constants corresponding to compression formats. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default.
IMWRITE_TIFF_COMPRESSION = 259,//!< For TIFF, use to specify the image compression scheme. See cv::ImwriteTiffCompressionFlags. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default.
IMWRITE_TIFF_ROWSPERSTRIP = 278,//!< For TIFF, use to specify the number of rows per strip.
IMWRITE_TIFF_PREDICTOR = 317,//!< For TIFF, use to specify predictor.
IMWRITE_TIFF_PREDICTOR = 317,//!< For TIFF, use to specify predictor. See cv::ImwriteTiffPredictorFlags.
IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95.
IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8.
@ -120,6 +120,48 @@ enum ImwriteJPEGSamplingFactorParams {
IMWRITE_JPEG_SAMPLING_FACTOR_444 = 0x111111 //!< 1x1,1x1,1x1(No subsampling)
};
enum ImwriteTiffCompressionFlags {
IMWRITE_TIFF_COMPRESSION_NONE = 1, //!< dump mode
IMWRITE_TIFF_COMPRESSION_CCITTRLE = 2, //!< CCITT modified Huffman RLE
IMWRITE_TIFF_COMPRESSION_CCITTFAX3 = 3, //!< CCITT Group 3 fax encoding
IMWRITE_TIFF_COMPRESSION_CCITT_T4 = 3, //!< CCITT T.4 (TIFF 6 name)
IMWRITE_TIFF_COMPRESSION_CCITTFAX4 = 4, //!< CCITT Group 4 fax encoding
IMWRITE_TIFF_COMPRESSION_CCITT_T6 = 4, //!< CCITT T.6 (TIFF 6 name)
IMWRITE_TIFF_COMPRESSION_LZW = 5, //!< Lempel-Ziv & Welch
IMWRITE_TIFF_COMPRESSION_OJPEG = 6, //!< !6.0 JPEG
IMWRITE_TIFF_COMPRESSION_JPEG = 7, //!< %JPEG DCT compression
IMWRITE_TIFF_COMPRESSION_T85 = 9, //!< !TIFF/FX T.85 JBIG compression
IMWRITE_TIFF_COMPRESSION_T43 = 10, //!< !TIFF/FX T.43 colour by layered JBIG compression
IMWRITE_TIFF_COMPRESSION_NEXT = 32766, //!< NeXT 2-bit RLE
IMWRITE_TIFF_COMPRESSION_CCITTRLEW = 32771, //!< #1 w/ word alignment
IMWRITE_TIFF_COMPRESSION_PACKBITS = 32773, //!< Macintosh RLE
IMWRITE_TIFF_COMPRESSION_THUNDERSCAN = 32809, //!< ThunderScan RLE
IMWRITE_TIFF_COMPRESSION_IT8CTPAD = 32895, //!< IT8 CT w/padding
IMWRITE_TIFF_COMPRESSION_IT8LW = 32896, //!< IT8 Linework RLE
IMWRITE_TIFF_COMPRESSION_IT8MP = 32897, //!< IT8 Monochrome picture
IMWRITE_TIFF_COMPRESSION_IT8BL = 32898, //!< IT8 Binary line art
IMWRITE_TIFF_COMPRESSION_PIXARFILM = 32908, //!< Pixar companded 10bit LZW
IMWRITE_TIFF_COMPRESSION_PIXARLOG = 32909, //!< Pixar companded 11bit ZIP
IMWRITE_TIFF_COMPRESSION_DEFLATE = 32946, //!< Deflate compression, legacy tag
IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE = 8, //!< Deflate compression, as recognized by Adobe
IMWRITE_TIFF_COMPRESSION_DCS = 32947, //!< Kodak DCS encoding
IMWRITE_TIFF_COMPRESSION_JBIG = 34661, //!< ISO JBIG
IMWRITE_TIFF_COMPRESSION_SGILOG = 34676, //!< SGI Log Luminance RLE
IMWRITE_TIFF_COMPRESSION_SGILOG24 = 34677, //!< SGI Log 24-bit packed
IMWRITE_TIFF_COMPRESSION_JP2000 = 34712, //!< Leadtools JPEG2000
IMWRITE_TIFF_COMPRESSION_LERC = 34887, //!< ESRI Lerc codec: https://github.com/Esri/lerc
IMWRITE_TIFF_COMPRESSION_LZMA = 34925, //!< LZMA2
IMWRITE_TIFF_COMPRESSION_ZSTD = 50000, //!< ZSTD: WARNING not registered in Adobe-maintained registry
IMWRITE_TIFF_COMPRESSION_WEBP = 50001, //!< WEBP: WARNING not registered in Adobe-maintained registry
IMWRITE_TIFF_COMPRESSION_JXL = 50002 //!< JPEGXL: WARNING not registered in Adobe-maintained registry
};
enum ImwriteTiffPredictorFlags {
IMWRITE_TIFF_PREDICTOR_NONE = 1, //!< no prediction scheme used
IMWRITE_TIFF_PREDICTOR_HORIZONTAL = 2, //!< horizontal differencing
IMWRITE_TIFF_PREDICTOR_FLOATINGPOINT = 3 //!< floating point predictor
};
enum ImwriteEXRTypeFlags {
/*IMWRITE_EXR_TYPE_UNIT = 0, //!< not supported */

@ -9,11 +9,6 @@ namespace opencv_test { namespace {
#ifdef HAVE_TIFF
// these defines are used to resolve conflict between tiff.h and opencv2/core/types_c.h
#define uint64 uint64_hack_
#define int64 int64_hack_
#include "tiff.h"
#ifdef __ANDROID__
// Test disabled as it uses a lot of memory.
// It is killed with SIGKILL by out of memory killer.
@ -767,7 +762,7 @@ TEST(Imgcodecs_Tiff, readWrite_32FC3_RAW)
std::vector<int> params;
params.push_back(IMWRITE_TIFF_COMPRESSION);
params.push_back(COMPRESSION_NONE);
params.push_back(IMWRITE_TIFF_COMPRESSION_NONE);
ASSERT_TRUE(cv::imwrite(filenameOutput, img, params));
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
@ -816,8 +811,9 @@ TEST(Imgcodecs_Tiff, readWrite_predictor)
cv::Mat mat(10, 16, CV_8UC1, (void*)sample_data);
int methods[] = {
COMPRESSION_NONE, COMPRESSION_LZW,
COMPRESSION_PACKBITS, COMPRESSION_DEFLATE, COMPRESSION_ADOBE_DEFLATE
IMWRITE_TIFF_COMPRESSION_NONE, IMWRITE_TIFF_COMPRESSION_LZW,
IMWRITE_TIFF_COMPRESSION_PACKBITS, IMWRITE_TIFF_COMPRESSION_DEFLATE,
IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE
};
for (size_t i = 0; i < sizeof(methods) / sizeof(int); i++)
{
@ -827,7 +823,7 @@ TEST(Imgcodecs_Tiff, readWrite_predictor)
params.push_back(IMWRITE_TIFF_COMPRESSION);
params.push_back(methods[i]);
params.push_back(IMWRITE_TIFF_PREDICTOR);
params.push_back(PREDICTOR_HORIZONTAL);
params.push_back(IMWRITE_TIFF_PREDICTOR_HORIZONTAL);
EXPECT_NO_THROW(cv::imwrite(out, mat, params));
@ -863,7 +859,7 @@ TEST_P(Imgcodecs_Tiff_Types, readWrite_alltypes)
{
std::vector<int> params;
params.push_back(IMWRITE_TIFF_COMPRESSION);
params.push_back(COMPRESSION_LZW);
params.push_back(IMWRITE_TIFF_COMPRESSION_LZW);
ASSERT_NO_THROW(cv::imencode(".tiff", src, bufLZW, params));
Mat dstLZW;
@ -878,7 +874,7 @@ TEST_P(Imgcodecs_Tiff_Types, readWrite_alltypes)
{
std::vector<int> params;
params.push_back(IMWRITE_TIFF_COMPRESSION);
params.push_back(COMPRESSION_NONE);
params.push_back(IMWRITE_TIFF_COMPRESSION_NONE);
ASSERT_NO_THROW(cv::imencode(".tiff", src, bufRAW, params));
Mat dstRAW;

@ -4061,6 +4061,19 @@ A program using pyramid scaling, Canny, contours and contour simplification to f
squares in the input image.
*/
//! @brief Find contours using link runs algorithm
//!
//! This function implements an algorithm different from cv::findContours:
//! - doesn't allocate temporary image internally, thus it has reduced memory consumption
//! - supports CV_8UC1 images only
//! - outputs 2-level hierarhy only (RETR_CCOMP mode)
//! - doesn't support approximation change other than CHAIN_APPROX_SIMPLE
//! In all other aspects this function is compatible with cv::findContours.
CV_EXPORTS_W void findContoursLinkRuns(InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy);
//! @overload
CV_EXPORTS_W void findContoursLinkRuns(InputArray image, OutputArrayOfArrays contours);
/** @brief Approximates a polygonal curve(s) with the specified precision.
The function cv::approxPolyDP approximates a curve or a polygon with another curve/polygon with less

@ -0,0 +1,30 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_IMGPROC_DETAIL_LEGACY_HPP
#define OPENCV_IMGPROC_DETAIL_LEGACY_HPP
#include "opencv2/imgproc.hpp"
namespace cv {
#ifdef __OPENCV_BUILD
CV_EXPORTS void findContours_legacy(InputArray _image,
OutputArrayOfArrays _contours,
OutputArray _hierarchy,
int mode,
int method,
Point offset = Point());
CV_EXPORTS void findContours_legacy(InputArray image,
OutputArrayOfArrays contours,
int mode,
int method,
Point offset = Point());
#endif
} // namespace cv
#endif // OPENCV_IMGPROC_DETAIL_LEGACY_HPP

@ -42,6 +42,7 @@
#include "opencv2/core/hal/intrin.hpp"
#include "opencv2/core/types_c.h"
#include "opencv2/core/core_c.h"
#include "opencv2/imgproc/detail/legacy.hpp"
using namespace cv;
@ -1964,7 +1965,7 @@ cvFindContours_Impl( void* img, CvMemStorage* storage,
return count;
}
void cv::findContours( InputArray _image, OutputArrayOfArrays _contours,
void cv::findContours_legacy( InputArray _image, OutputArrayOfArrays _contours,
OutputArray _hierarchy, int mode, int method, Point offset )
{
CV_INSTRUMENT_REGION();
@ -2029,7 +2030,7 @@ void cv::findContours( InputArray _image, OutputArrayOfArrays _contours,
}
}
void cv::findContours( InputArray _image, OutputArrayOfArrays _contours,
void cv::findContours_legacy( InputArray _image, OutputArrayOfArrays _contours,
int mode, int method, Point offset)
{
CV_INSTRUMENT_REGION();

@ -0,0 +1,354 @@
// 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 "opencv2/core/base.hpp"
#include "opencv2/core/types.hpp"
#include "opencv2/imgproc.hpp"
#include "contours_common.hpp"
#include <vector>
using namespace std;
using namespace cv;
namespace {
struct ApproxItem
{
Point pt;
size_t k; // support region
int s; // 1-curvature
bool removed;
ApproxItem() : k(0), s(0), removed(false) {}
ApproxItem(const Point& pt_, int s_) : pt(pt_), k(0), s(s_), removed(false) {}
};
static const schar abs_diff[16] = {1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1};
static const Point chainCodeDeltas[8] =
{{1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}};
// Pass 0.
// Restores all the digital curve points from the chain code.
// Removes the points (from the resultant polygon)
// that have zero 1-curvature
static vector<ApproxItem> pass_0(const vector<schar>& chain, Point pt, bool isApprox, bool isFull)
{
vector<ApproxItem> res;
const size_t len = chain.size();
res.reserve(len / 2);
for (size_t i = 0; i < len; ++i)
{
const schar prev = (i == 0) ? chain[len - 1] : chain[i - 1];
const schar cur = chain[i];
const schar s = abs_diff[cur - prev + 7];
if ((!isApprox && (isFull || s != 0)) || isApprox)
{
res.push_back(ApproxItem(pt, s));
if (s == 0)
(res.end() - 1)->removed = true;
}
pt += chainCodeDeltas[cur];
}
return res;
}
static vector<Point> gatherPoints(const vector<ApproxItem>& ares)
{
vector<Point> res;
res.reserve(ares.size() / 2);
for (const ApproxItem& item : ares)
{
if (item.removed)
continue;
res.push_back(item.pt);
}
return res;
}
static size_t calc_support(const vector<ApproxItem>& ares, size_t i)
{
const size_t len = ares.size();
/* determine support region */
int d_num = 0;
int l = 0;
size_t k = 1;
for (;; k++)
{
CV_Assert(k <= len);
/* calc indices */
const size_t i1 = (i >= k) ? (i - k) : (len - k + i);
const size_t i2 = (i + k < len) ? (i + k) : (i + k - len);
const int dx = ares[i2].pt.x - ares[i1].pt.x;
const int dy = ares[i2].pt.y - ares[i1].pt.y;
/* distance between p_(i - k) and p_(i + k) */
const int lk = dx * dx + dy * dy;
/* distance between p_i and the line (p_(i-k), p_(i+k)) */
const int dk_num =
(ares[i].pt.x - ares[i1].pt.x) * dy - (ares[i].pt.y - ares[i1].pt.y) * dx;
union
{
int i;
float f;
} d;
d.f = (float)(((double)d_num) * lk - ((double)dk_num) * l);
if (k > 1 && (l >= lk || ((d_num > 0 && d.i <= 0) || (d_num < 0 && d.i >= 0))))
break;
d_num = dk_num;
l = lk;
}
return k - 1;
}
static int calc_cosine(const vector<ApproxItem>& ares, size_t i)
{
const size_t k = ares[i].k;
size_t j;
int s;
const size_t len = ares.size();
/* calc k-cosine curvature */
for (j = k, s = 0; j > 0; j--)
{
const size_t i1 = (i >= j) ? (i - j) : (len - j + i);
const size_t i2 = (i + j < len) ? (i + j) : (i + j - len);
const int dx1 = ares[i1].pt.x - ares[i].pt.x;
const int dy1 = ares[i1].pt.y - ares[i].pt.y;
const int dx2 = ares[i2].pt.x - ares[i].pt.x;
const int dy2 = ares[i2].pt.y - ares[i].pt.y;
if ((dx1 | dy1) == 0 || (dx2 | dy2) == 0)
break;
double temp_num = dx1 * dx2 + dy1 * dy2;
temp_num = (float)(temp_num / sqrt(((double)dx1 * dx1 + (double)dy1 * dy1) *
((double)dx2 * dx2 + (double)dy2 * dy2)));
Cv32suf sk;
sk.f = (float)(temp_num + 1.1);
CV_Assert(0 <= sk.f && sk.f <= 2.2);
if (j < k && sk.i <= s)
break;
s = sk.i;
}
return s;
}
static bool calc_nms_cleanup(const vector<ApproxItem>& ares, size_t i)
{
const size_t k2 = ares[i].k >> 1;
const int s = ares[i].s;
const size_t len = ares.size();
size_t j;
for (j = 1; j <= k2; j++)
{
const size_t i1 = (i >= j) ? (i - j) : (len - j + i);
const size_t i2 = (i + j < len) ? (i + j) : (i + j - len);
if (ares[i1].s > s || ares[i2].s > s)
break;
}
return j <= k2;
}
static bool calc_dominance(const vector<ApproxItem>& ares, size_t i)
{
const size_t len = ares.size();
CV_Assert(len > 0);
const size_t i1 = (i >= 1) ? (i - 1) : (len - 1 + i);
const size_t i2 = (i + 1 < len) ? (i + 1) : (i + 1 - len);
return ares[i].s <= ares[i1].s || ares[i].s <= ares[i2].s;
}
inline size_t get_next_idx(const vector<ApproxItem>& ares, const size_t start)
{
const size_t len = ares.size();
size_t res = start + 1;
for (; res < len; ++res)
{
if (!ares[res].removed)
break;
}
return res;
}
inline void clear_until(vector<ApproxItem>& ares, const size_t start, const size_t finish)
{
const size_t len = ares.size();
for (size_t i = start + 1; i < finish && i < len; ++i)
{
ares[i].removed = true;
}
}
static bool calc_new_start(vector<ApproxItem>& ares, size_t& res)
{
const size_t len = ares.size();
CV_Assert(len > 0);
size_t i1;
// remove all previous items from the beginning
for (i1 = 1; i1 < len && ares[i1].s != 0; i1++)
{
ares[i1 - 1].s = 0;
}
if (i1 == len)
{
// all points survived - skip to the end
return false;
}
i1--;
size_t i2;
// remove all following items from the end
for (i2 = len - 2; i2 > 0 && ares[i2].s != 0; i2--)
{
clear_until(ares, i2, len);
ares[i2 + 1].s = 0;
}
i2++;
// only two points left
if (i1 == 0 && i2 == len - 1)
{
// find first non-removed element from the start
i1 = get_next_idx(ares, 0);
// append first item to the end
ares.push_back(ares[0]);
(ares.end() - 1)->removed = false;
}
res = i1;
return true;
}
static void pass_cleanup(vector<ApproxItem>& ares, size_t start_idx)
{
int count = 1;
const size_t len = ares.size();
size_t first = start_idx;
for (size_t i = start_idx, prev = i; i < len; ++i)
{
ApproxItem& item = ares[i];
if (item.removed)
continue;
size_t next_idx = get_next_idx(ares, i);
if (next_idx == len || next_idx - i != 1)
{
if (count >= 2)
{
if (count == 2)
{
const int s1 = ares[prev].s;
const int s2 = ares[i].s;
if (s1 > s2 || (s1 == s2 && ares[prev].k <= ares[i].k))
/* remove second */
clear_until(ares, get_next_idx(ares, prev), get_next_idx(ares, i));
else
/* remove first */
clear_until(ares, first, i);
}
else
{
first = get_next_idx(ares, first);
clear_until(ares, first, i);
}
}
clear_until(ares, first, i);
first = i;
count = 1;
}
else
{
++count;
}
prev = i;
}
}
} // namespace
vector<Point> cv::approximateChainTC89(vector<schar> chain, const Point& origin, const int method)
{
if (chain.size() == 0)
{
return vector<Point>({origin});
}
const bool isApprox = method == CHAIN_APPROX_TC89_L1 || method == CHAIN_APPROX_TC89_KCOS;
ApproxItem root;
vector<ApproxItem> ares = pass_0(chain, origin, isApprox, method == CHAIN_APPROX_NONE);
if (isApprox)
{
CV_DbgAssert(ares.size() < (size_t)numeric_limits<int>::max());
// Pass 1.
// Determines support region for all the remained points */
for (size_t i = 0; i < ares.size(); ++i)
{
ApproxItem& item = ares[i];
if (item.removed)
continue;
item.k = calc_support(ares, i);
if (method == CHAIN_APPROX_TC89_KCOS)
item.s = calc_cosine(ares, i);
}
// Pass 2.
// Performs non-maxima suppression
for (size_t i = 0; i < ares.size(); ++i)
{
ApproxItem& item = ares[i];
if (calc_nms_cleanup(ares, i))
{
item.s = 0; // "clear"
item.removed = true;
}
}
// Pass 3.
// Removes non-dominant points with 1-length support region */
for (size_t i = 0; i < ares.size(); ++i)
{
ApproxItem& item = ares[i];
if (item.removed)
continue;
if (item.k == 1 && calc_dominance(ares, i))
{
item.s = 0;
item.removed = true;
}
}
if (method == cv::CHAIN_APPROX_TC89_L1)
{
// Pass 4.
// Cleans remained couples of points
bool skip = false;
size_t new_start_idx = 0;
const size_t len = ares.size();
if (ares[0].s != 0 && ares[len - 1].s != 0)
{
if (!calc_new_start(ares, new_start_idx))
{
skip = true;
}
}
if (!skip)
{
pass_cleanup(ares, new_start_idx);
}
}
}
return gatherPoints(ares);
}

@ -0,0 +1,75 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include "contours_common.hpp"
#include <map>
#include <limits>
using namespace std;
using namespace cv;
void cv::contourTreeToResults(CTree& tree,
int res_type,
OutputArrayOfArrays& _contours,
OutputArray& _hierarchy)
{
// check if there are no results
if (tree.isEmpty() || (tree.elem(0).body.isEmpty() && (tree.elem(0).first_child == -1)))
{
_contours.clear();
return;
}
// mapping for indexes (original -> resulting)
map<int, int> index_mapping;
index_mapping[-1] = -1;
index_mapping[0] = -1;
CV_Assert(tree.size() < (size_t)numeric_limits<int>::max());
const int total = (int)tree.size() - 1;
_contours.create(total, 1, 0, -1, true);
{
int i = 0;
CIterator it(tree);
while (!it.isDone())
{
const CNode& elem = it.getNext_s();
CV_Assert(elem.self() != -1);
if (elem.self() == 0)
continue;
index_mapping[elem.self()] = i;
CV_Assert(elem.body.size() < (size_t)numeric_limits<int>::max());
const int sz = (int)elem.body.size();
_contours.create(sz, 1, res_type, i, true);
if (sz > 0)
{
Mat cmat = _contours.getMat(i);
CV_Assert(cmat.isContinuous());
elem.body.copyTo(cmat.data);
}
++i;
}
}
if (_hierarchy.needed())
{
_hierarchy.create(1, total, CV_32SC4, -1, true);
Mat h_mat = _hierarchy.getMat();
int i = 0;
CIterator it(tree);
while (!it.isDone())
{
const CNode& elem = it.getNext_s();
if (elem.self() == 0)
continue;
Vec4i& h_vec = h_mat.at<Vec4i>(i);
h_vec = Vec4i(index_mapping.at(elem.next),
index_mapping.at(elem.prev),
index_mapping.at(elem.first_child),
index_mapping.at(elem.parent));
++i;
}
}
}

@ -0,0 +1,219 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#ifndef OPENCV_CONTOURS_COMMON_HPP
#define OPENCV_CONTOURS_COMMON_HPP
#include "precomp.hpp"
#include <stack>
namespace cv {
static const schar MAX_SIZE = 16;
static const cv::Point chainCodeDeltas[8] =
{{1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}};
static inline int getDelta(schar s, size_t step)
{
CV_DbgAssert(s >= 0 && s < 16);
const cv::Point res = chainCodeDeltas[s % 8];
return res.x + res.y * (int)step;
}
inline schar clamp_direction(schar dir)
{
return std::min(dir, (schar)15);
}
template <typename T>
class TreeNode
{
private:
int self_;
public:
// tree hierarchy (parent - children)
int parent;
int first_child;
// 1st linked list - bidirectional - sibling children
int prev;
int next;
// 2nd linked list - unidirectional - not related to 1st list
int ctable_next;
T body;
public:
TreeNode(int self) :
self_(self), parent(-1), first_child(-1), prev(-1), next(-1), ctable_next(-1)
{
CV_Assert(self >= 0);
}
int self() const
{
return self_;
}
};
template <typename T>
class Tree
{
private:
std::vector<TreeNode<T>> nodes;
public:
TreeNode<T>& newElem()
{
const size_t idx = nodes.size();
CV_DbgAssert(idx < (size_t)std::numeric_limits<int>::max());
nodes.push_back(TreeNode<T>((int)idx));
return nodes[idx];
}
TreeNode<T>& elem(int idx)
{
CV_DbgAssert(idx >= 0 && (size_t)idx < nodes.size());
return nodes[(size_t)idx];
}
const TreeNode<T>& elem(int idx) const
{
CV_DbgAssert(idx >= 0 && (size_t)idx < nodes.size());
return nodes[(size_t)idx];
}
int lastSibling(int e) const
{
if (e != -1)
{
while (true)
{
const TreeNode<T>& cur_elem = elem(e);
if (cur_elem.next == -1)
break;
e = cur_elem.next;
}
}
return e;
}
void addSiblingAfter(int prev, int idx)
{
TreeNode<T>& prev_item = nodes[prev];
TreeNode<T>& child = nodes[idx];
child.parent = prev_item.parent;
if (prev_item.next != -1)
{
nodes[prev_item.next].prev = idx;
child.next = prev_item.next;
}
child.prev = prev;
prev_item.next = idx;
}
void addChild(int parent_idx, int child_idx)
{
TreeNode<T>& parent = nodes[parent_idx];
TreeNode<T>& child = nodes[child_idx];
if (parent.first_child != -1)
{
TreeNode<T>& fchild_ = nodes[parent.first_child];
fchild_.prev = child_idx;
child.next = parent.first_child;
}
parent.first_child = child_idx;
child.parent = parent_idx;
child.prev = -1;
}
bool isEmpty() const
{
return nodes.size() == 0;
}
size_t size() const
{
return nodes.size();
}
};
template <typename T>
class TreeIterator
{
public:
TreeIterator(Tree<T>& tree_) : tree(tree_)
{
CV_Assert(!tree.isEmpty());
levels.push(0);
}
bool isDone() const
{
return levels.empty();
}
const TreeNode<T>& getNext_s()
{
int idx = levels.top();
levels.pop();
const TreeNode<T>& res = tree.elem(idx);
int cur = tree.lastSibling(res.first_child);
while (cur != -1)
{
levels.push(cur);
cur = tree.elem(cur).prev;
}
return res;
}
private:
std::stack<int> levels;
Tree<T>& tree;
};
//==============================================================================
class Contour
{
public:
cv::Rect brect;
cv::Point origin;
std::vector<cv::Point> pts;
std::vector<schar> codes;
bool isHole;
bool isChain;
Contour() : isHole(false), isChain(false) {}
void updateBoundingRect() {}
bool isEmpty() const
{
return pts.size() == 0 && codes.size() == 0;
}
size_t size() const
{
return isChain ? codes.size() : pts.size();
}
void copyTo(void* data) const
{
// NOTE: Mat::copyTo doesn't work because it creates new Mat object
// instead of reusing existing vector data
if (isChain)
{
memcpy(data, &codes[0], codes.size() * sizeof(codes[0]));
}
else
{
memcpy(data, &pts[0], pts.size() * sizeof(pts[0]));
}
}
};
typedef TreeNode<Contour> CNode;
typedef Tree<Contour> CTree;
typedef TreeIterator<Contour> CIterator;
void contourTreeToResults(CTree& tree,
int res_type,
cv::OutputArrayOfArrays& _contours,
cv::OutputArray& _hierarchy);
std::vector<Point>
approximateChainTC89(std::vector<schar> chain, const Point& origin, const int method);
} // namespace cv
#endif // OPENCV_CONTOURS_COMMON_HPP

@ -0,0 +1,417 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "precomp.hpp"
#include "contours_common.hpp"
#include "opencv2/core/hal/intrin.hpp"
using namespace cv;
using namespace std;
//==============================================================================
namespace {
inline static int findStartContourPoint(uchar* src_data, Size img_size, int j)
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
v_uint8 v_zero = vx_setzero_u8();
for (; j <= img_size.width - VTraits<v_uint8>::vlanes(); j += VTraits<v_uint8>::vlanes())
{
v_uint8 vmask = (v_ne(vx_load((uchar*)(src_data + j)), v_zero));
if (v_check_any(vmask))
{
j += v_scan_forward(vmask);
return j;
}
}
#endif
for (; j < img_size.width && !src_data[j]; ++j)
;
return j;
}
inline static int findEndContourPoint(uchar* src_data, Size img_size, int j)
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
if (j < img_size.width && !src_data[j])
{
return j;
}
else
{
v_uint8 v_zero = vx_setzero_u8();
for (; j <= img_size.width - VTraits<v_uint8>::vlanes(); j += VTraits<v_uint8>::vlanes())
{
v_uint8 vmask = (v_eq(vx_load((uchar*)(src_data + j)), v_zero));
if (v_check_any(vmask))
{
j += v_scan_forward(vmask);
return j;
}
}
}
#endif
for (; j < img_size.width && src_data[j]; ++j)
;
return j;
}
//==============================================================================
struct LinkRunPoint
{
int link;
int next;
Point pt;
LinkRunPoint() : link(-1), next(-1) {}
LinkRunPoint(const Point& pt_) : link(-1), next(-1), pt(pt_) {}
};
typedef LinkRunPoint LRP;
//==============================================================================
class LinkRunner
{
public:
enum LinkConnectionDirection
{
ICV_SINGLE = 0,
ICV_CONNECTING_ABOVE = 1,
ICV_CONNECTING_BELOW = -1,
};
CTree tree;
vector<LRP> rns;
vector<int> ext_rns;
vector<int> int_rns;
public:
LinkRunner()
{
tree.newElem();
rns.reserve(100);
}
void process(Mat& image);
void convertLinks(int& first, int& prev, bool isHole);
void establishLinks(int& prev_point,
int upper_run,
int lower_run,
const int upper_total,
const int lower_total);
};
void LinkRunner::convertLinks(int& first, int& prev, bool isHole)
{
const vector<int>& contours = isHole ? int_rns : ext_rns;
int count = 0;
for (int j = 0; j < (int)contours.size(); j++, count++)
{
int start = contours[j];
int cur = start;
if (rns[cur].link == -1)
continue;
CNode& node = tree.newElem();
node.body.isHole = isHole;
do
{
node.body.pts.push_back(rns[cur].pt);
int p_temp = cur;
cur = rns[cur].link;
rns[p_temp].link = -1;
}
while (cur != start);
if (first == 0)
{
tree.addChild(0, node.self());
prev = first = node.self();
}
else
{
tree.addSiblingAfter(prev, node.self());
prev = node.self();
}
}
}
void LinkRunner::establishLinks(int& prev_point,
int upper_run,
int lower_run,
const int upper_total,
const int lower_total)
{
int k, n;
int connect_flag = ICV_SINGLE;
for (k = 0, n = 0; k < upper_total / 2 && n < lower_total / 2;)
{
switch (connect_flag)
{
case ICV_SINGLE:
if (rns[rns[upper_run].next].pt.x < rns[rns[lower_run].next].pt.x)
{
if (rns[rns[upper_run].next].pt.x >= rns[lower_run].pt.x - 1)
{
rns[lower_run].link = upper_run;
connect_flag = ICV_CONNECTING_ABOVE;
prev_point = rns[upper_run].next;
}
else
rns[rns[upper_run].next].link = upper_run;
k++;
upper_run = rns[rns[upper_run].next].next;
}
else
{
if (rns[upper_run].pt.x <= rns[rns[lower_run].next].pt.x + 1)
{
rns[lower_run].link = upper_run;
connect_flag = ICV_CONNECTING_BELOW;
prev_point = rns[lower_run].next;
}
else
{
rns[lower_run].link = rns[lower_run].next;
// First point of contour
ext_rns.push_back(lower_run);
}
n++;
lower_run = rns[rns[lower_run].next].next;
}
break;
case ICV_CONNECTING_ABOVE:
if (rns[upper_run].pt.x > rns[rns[lower_run].next].pt.x + 1)
{
rns[prev_point].link = rns[lower_run].next;
connect_flag = ICV_SINGLE;
n++;
lower_run = rns[rns[lower_run].next].next;
}
else
{
rns[prev_point].link = upper_run;
if (rns[rns[upper_run].next].pt.x < rns[rns[lower_run].next].pt.x)
{
k++;
prev_point = rns[upper_run].next;
upper_run = rns[rns[upper_run].next].next;
}
else
{
connect_flag = ICV_CONNECTING_BELOW;
prev_point = rns[lower_run].next;
n++;
lower_run = rns[rns[lower_run].next].next;
}
}
break;
case ICV_CONNECTING_BELOW:
if (rns[lower_run].pt.x > rns[rns[upper_run].next].pt.x + 1)
{
rns[rns[upper_run].next].link = prev_point;
connect_flag = ICV_SINGLE;
k++;
upper_run = rns[rns[upper_run].next].next;
}
else
{
// First point of contour
int_rns.push_back(lower_run);
rns[lower_run].link = prev_point;
if (rns[rns[lower_run].next].pt.x < rns[rns[upper_run].next].pt.x)
{
n++;
prev_point = rns[lower_run].next;
lower_run = rns[rns[lower_run].next].next;
}
else
{
connect_flag = ICV_CONNECTING_ABOVE;
k++;
prev_point = rns[upper_run].next;
upper_run = rns[rns[upper_run].next].next;
}
}
break;
}
} // k, n
for (; n < lower_total / 2; n++)
{
if (connect_flag != ICV_SINGLE)
{
rns[prev_point].link = rns[lower_run].next;
connect_flag = ICV_SINGLE;
lower_run = rns[rns[lower_run].next].next;
continue;
}
rns[rns[lower_run].next] = rns[rns[lower_run].next];
rns[lower_run].link = rns[lower_run].next;
// First point of contour
ext_rns.push_back(lower_run);
lower_run = rns[rns[lower_run].next].next;
}
for (; k < upper_total / 2; k++)
{
if (connect_flag != ICV_SINGLE)
{
rns[rns[upper_run].next].link = prev_point;
connect_flag = ICV_SINGLE;
upper_run = rns[rns[upper_run].next].next;
continue;
}
rns[rns[upper_run].next] = rns[rns[upper_run].next];
rns[rns[upper_run].next].link = upper_run;
upper_run = rns[rns[upper_run].next].next;
}
}
void LinkRunner::process(Mat& image)
{
const Size sz = image.size();
int j;
int lower_total;
int upper_total;
int all_total;
Point cur_point;
rns.reserve(sz.height); // optimization, assuming some contours exist
// First line. None of runs is binded
rns.push_back(LRP());
int upper_line = (int)rns.size() - 1;
int cur = upper_line;
for (j = 0; j < sz.width;)
{
j = findStartContourPoint(image.ptr<uchar>(), sz, j);
if (j == sz.width)
break;
cur_point.x = j;
rns.push_back(LRP(cur_point));
rns[cur].next = (int)rns.size() - 1;
cur = rns[cur].next;
j = findEndContourPoint(image.ptr<uchar>(), sz, j + 1);
cur_point.x = j - 1;
rns.push_back(LRP(cur_point));
rns[cur].next = (int)rns.size() - 1;
rns[cur].link = rns[cur].next;
// First point of contour
ext_rns.push_back(cur);
cur = rns[cur].next;
}
upper_line = rns[upper_line].next;
upper_total = (int)rns.size() - 1; // runs->total - 1;
int last_elem = cur;
rns[cur].next = -1;
int prev_point = -1;
int lower_line = -1;
for (int i = 1; i < sz.height; i++)
{
// Find runs in next line
cur_point.y = i;
all_total = (int)rns.size(); // runs->total;
for (j = 0; j < sz.width;)
{
j = findStartContourPoint(image.ptr<uchar>(i), sz, j);
if (j == sz.width)
break;
cur_point.x = j;
rns.push_back(LRP(cur_point));
rns[cur].next = (int)rns.size() - 1;
cur = rns[cur].next;
j = findEndContourPoint(image.ptr<uchar>(i), sz, j + 1);
cur_point.x = j - 1;
rns.push_back(LRP(cur_point));
cur = rns[cur].next = (int)rns.size() - 1;
} // j
lower_line = rns[last_elem].next;
lower_total = (int)rns.size() - all_total; // runs->total - all_total;
last_elem = cur;
rns[cur].next = -1;
CV_DbgAssert(rns.size() < (size_t)numeric_limits<int>::max());
// Find links between runs of lower_line and upper_line
establishLinks(prev_point, upper_line, lower_line, upper_total, lower_total);
upper_line = lower_line;
upper_total = lower_total;
} // i
// the last line of image
int upper_run = upper_line;
for (int k = 0; k < upper_total / 2; k++)
{
rns[rns[upper_run].next].link = upper_run;
upper_run = rns[rns[upper_run].next].next;
}
int first = 0;
int prev = 0;
convertLinks(first, prev, false);
convertLinks(first, prev, true);
}
} // namespace
//==============================================================================
void cv::findContoursLinkRuns(InputArray _image,
OutputArrayOfArrays _contours,
OutputArray _hierarchy)
{
CV_INSTRUMENT_REGION();
CV_CheckType(_image.type(),
_image.type() == CV_8UC1 || _image.type() == CV_8SC1,
"Bad input image type, must be CV_8UC1 or CV_8SC1");
// Sanity check: output must be of type vector<vector<Point>>
CV_Assert(_contours.kind() == _InputArray::STD_VECTOR_VECTOR ||
_contours.kind() == _InputArray::STD_VECTOR_MAT ||
_contours.kind() == _InputArray::STD_VECTOR_UMAT);
if (!_contours.empty())
CV_CheckTypeEQ(_contours.type(), CV_32SC2, "Contours must have type CV_32SC2");
if (_hierarchy.needed())
_hierarchy.clear();
Mat image = _image.getMat();
LinkRunner runner;
runner.process(image);
contourTreeToResults(runner.tree, CV_32SC2, _contours, _hierarchy);
}
void cv::findContoursLinkRuns(InputArray _image, OutputArrayOfArrays _contours)
{
CV_INSTRUMENT_REGION();
findContoursLinkRuns(_image, _contours, noArray());
}

@ -0,0 +1,697 @@
// 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 "opencv2/imgproc.hpp"
#include "precomp.hpp"
#include "opencv2/core/hal/intrin.hpp"
#include "opencv2/core/check.hpp"
#include "opencv2/core/utils/logger.hpp"
#include <iostream>
#include <array>
#include <limits>
#include <map>
#include "contours_common.hpp"
using namespace std;
using namespace cv;
//==============================================================================
namespace {
template <typename T>
struct Trait
{
};
static const schar MASK8_RIGHT = '\x80'; // 1000 0000
static const schar MASK8_NEW = '\x02'; // 0000 0010 (+2)
static const schar MASK8_FLAGS = '\xFE'; // 1111 1110 (-2)
static const schar MASK8_BLACK = '\x01'; // 0000 0001 - black pixel
static const schar MASK8_LVAL = '\x7F'; // 0111 1111 (for table)
template <>
struct Trait<schar>
{
static inline bool checkValue(const schar* elem, const schar*)
{
return *elem != 0;
}
static inline bool isVal(const schar* elem, const schar*)
{
return *elem == MASK8_BLACK;
}
static inline bool isRight(const schar* elem, const schar*)
{
return (*elem & MASK8_RIGHT) != 0;
}
static inline void setRightFlag(schar* elem, const schar*, schar nbd)
{
*elem = nbd | MASK8_RIGHT;
}
static inline void setNewFlag(schar* elem, const schar*, schar nbd)
{
*elem = nbd;
}
};
static const int MASK_RIGHT = 0x80000000; // 100..000
static const int MASK_NEW = 0x40000000; // 010..000
static const int MASK_FLAGS = 0xC0000000; // right + new
static const int MASK_VAL = 0x3FFFFFFF; // ~flags - pixel label
template <>
struct Trait<int>
{
static inline bool checkValue(const int* elem, const int* elem0)
{
return (*elem & MASK_VAL) == (*elem0 & MASK_VAL);
}
static inline bool isVal(const int* elem, const int* elem0)
{
return *elem == (*elem0 & MASK_VAL);
}
static inline bool isRight(const int* elem, const int* elem0)
{
return (*elem & MASK_RIGHT) == (*elem0 & MASK8_RIGHT);
}
static inline void setRightFlag(int* elem, const int* elem0, int)
{
*elem = (*elem0 & MASK_VAL) | MASK_NEW | MASK_RIGHT;
}
static inline void setNewFlag(int* elem, const int* elem0, int)
{
*elem = (*elem0 & MASK_VAL) | MASK_NEW;
}
};
} // namespace
//==============================================================================
namespace {
template <typename T>
static bool icvTraceContour(Mat& image, const Point& start, const Point& end, bool isHole)
{
const T* stop_ptr = image.ptr<T>(end.y, end.x);
const size_t step = image.step1();
const T *i0 = image.ptr<T>(start.y, start.x), *i1, *i3, *i4 = NULL;
const schar s_end = isHole ? 0 : 4;
schar s = s_end;
do
{
s = (s - 1) & 7;
i1 = i0 + getDelta(s, step);
}
while (!Trait<T>::checkValue(i1, i0) && s != s_end);
i3 = i0;
// check single pixel domain
if (s != s_end)
{
// follow border
for (;;)
{
CV_Assert(i3 != NULL);
s = clamp_direction(s);
while (s < MAX_SIZE - 1)
{
++s;
i4 = i3 + getDelta(s, step);
CV_Assert(i4 != NULL);
if (Trait<T>::checkValue(i4, i0))
break;
}
if (i3 == stop_ptr)
{
if (!Trait<T>::isRight(i3, i0))
{
// it's the only contour
return true;
}
// check if this is the last contour
// encountered during a raster scan
const T* i5;
schar t = s;
while (true)
{
t = (t - 1) & 7;
i5 = i3 + getDelta(t, step);
if (*i5 != 0)
break;
if (t == 0)
return true;
}
}
if ((i4 == i0 && i3 == i1))
break;
i3 = i4;
s = (s + 4) & 7;
} // end of border following loop
}
else
{
return i3 == stop_ptr;
}
return false;
}
template <typename T>
static void icvFetchContourEx(Mat& image,
const Point& start,
T nbd,
Contour& res_contour,
const bool isDirect)
{
const size_t step = image.step1();
T *i0 = image.ptr<T>(start.y, start.x), *i1, *i3, *i4 = NULL;
Point pt = res_contour.origin;
cv::Rect rect(pt.x, pt.y, pt.x, pt.y);
schar s_end = res_contour.isHole ? 0 : 4;
schar s = s_end;
do
{
s = (s - 1) & 7;
i1 = i0 + getDelta(s, step);
}
while (!Trait<T>::checkValue(i1, i0) && s != s_end);
if (s == s_end)
{
Trait<T>::setRightFlag(i0, i0, nbd);
if (!res_contour.isChain)
{
res_contour.pts.push_back(pt);
}
}
else
{
i3 = i0;
schar prev_s = s ^ 4;
// follow border
for (;;)
{
s_end = s;
s = clamp_direction(s);
while (s < MAX_SIZE - 1)
{
++s;
i4 = i3 + getDelta(s, step);
CV_Assert(i4 != NULL);
if (Trait<T>::checkValue(i4, i0))
break;
}
s &= 7;
// check "right" bound
if ((unsigned)(s - 1) < (unsigned)s_end)
{
Trait<T>::setRightFlag(i3, i0, nbd);
}
else if (Trait<T>::isVal(i3, i0))
{
Trait<T>::setNewFlag(i3, i0, nbd);
}
if (res_contour.isChain)
{
res_contour.codes.push_back(s);
}
else if (s != prev_s || isDirect)
{
res_contour.pts.push_back(pt);
}
if (s != prev_s)
{
// update bounds
if (pt.x < rect.x)
rect.x = pt.x;
else if (pt.x > rect.width)
rect.width = pt.x;
if (pt.y < rect.y)
rect.y = pt.y;
else if (pt.y > rect.height)
rect.height = pt.y;
}
prev_s = s;
pt += chainCodeDeltas[s];
if (i4 == i0 && i3 == i1)
break;
i3 = i4;
s = (s + 4) & 7;
}
}
rect.width -= rect.x - 1;
rect.height -= rect.y - 1;
res_contour.brect = rect;
}
} // namespace
//==============================================================================
//
// Raster->Chain Tree (Suzuki algorithms)
//
// Structure that is used for sequential retrieving contours from the image.
// It supports both hierarchical and plane variants of Suzuki algorithm.
struct ContourScanner_
{
Mat image;
Point offset; // ROI offset: coordinates, added to each contour point
Point pt; // current scanner position
Point lnbd; // position of the last met contour
schar nbd; // current mark val
int approx_method1; // approx method when tracing
int approx_method2; // final approx method
int mode;
CTree tree;
array<int, 128> ctable;
public:
ContourScanner_() {}
~ContourScanner_() {}
inline bool isInt() const
{
return (this->mode == RETR_FLOODFILL);
}
inline bool isSimple() const
{
return (this->mode == RETR_EXTERNAL || this->mode == RETR_LIST);
}
CNode& makeContour(schar& nbd_, const bool is_hole, const int x, const int y);
bool contourScan(const int prev, int& p, Point& last_pos, const int x, const int y);
int findFirstBoundingContour(const Point& last_pos, const int y, const int lval, int par);
int findNextX(int x, int y, int& prev, int& p);
bool findNext();
static shared_ptr<ContourScanner_> create(Mat img, int mode, int method, Point offset);
}; // class ContourScanner_
typedef shared_ptr<ContourScanner_> ContourScanner;
shared_ptr<ContourScanner_> ContourScanner_::create(Mat img, int mode, int method, Point offset)
{
if (mode == RETR_CCOMP && img.type() == CV_32SC1)
mode = RETR_FLOODFILL;
if (mode == RETR_FLOODFILL)
CV_CheckTypeEQ(img.type(), CV_32SC1, "RETR_FLOODFILL mode supports only CV_32SC1 images");
else
CV_CheckTypeEQ(img.type(),
CV_8UC1,
"Modes other than RETR_FLOODFILL and RETR_CCOMP support only CV_8UC1 "
"images");
CV_Check(mode,
mode == RETR_EXTERNAL || mode == RETR_LIST || mode == RETR_CCOMP ||
mode == RETR_TREE || mode == RETR_FLOODFILL,
"Wrong extraction mode");
CV_Check(method,
method == 0 || method == CHAIN_APPROX_NONE || method == CHAIN_APPROX_SIMPLE ||
method == CHAIN_APPROX_TC89_L1 || method == CHAIN_APPROX_TC89_KCOS,
"Wrong approximation method");
Size size = img.size();
CV_Assert(size.height >= 1);
shared_ptr<ContourScanner_> scanner = make_shared<ContourScanner_>();
scanner->image = img;
scanner->mode = mode;
scanner->offset = offset;
scanner->pt = Point(1, 1);
scanner->lnbd = Point(0, 1);
scanner->nbd = 2;
CNode& root = scanner->tree.newElem();
CV_Assert(root.self() == 0);
root.body.isHole = true;
root.body.brect = Rect(Point(0, 0), size);
scanner->ctable.fill(-1);
scanner->approx_method2 = scanner->approx_method1 = method;
if (method == CHAIN_APPROX_TC89_L1 || method == CHAIN_APPROX_TC89_KCOS)
scanner->approx_method1 = CHAIN_CODE;
return scanner;
}
CNode& ContourScanner_::makeContour(schar& nbd_, const bool is_hole, const int x, const int y)
{
const bool isChain = (this->approx_method1 == CHAIN_CODE); // TODO: get rid of old constant
const bool isDirect = (this->approx_method1 == CHAIN_APPROX_NONE);
const Point start_pt(x - (is_hole ? 1 : 0), y);
CNode& res = tree.newElem();
if (isChain)
res.body.codes.reserve(200);
else
res.body.pts.reserve(200);
res.body.isHole = is_hole;
res.body.isChain = isChain;
res.body.origin = start_pt + offset;
if (isSimple())
{
icvFetchContourEx<schar>(this->image, start_pt, MASK8_NEW, res.body, isDirect);
}
else
{
schar lval;
if (isInt())
{
const int start_val = this->image.at<int>(start_pt);
lval = start_val & MASK8_LVAL;
icvFetchContourEx<int>(this->image, start_pt, 0, res.body, isDirect);
}
else
{
lval = nbd_;
// change nbd
nbd_ = (nbd_ + 1) & MASK8_LVAL;
if (nbd_ == 0)
nbd_ = MASK8_BLACK | MASK8_NEW;
icvFetchContourEx<schar>(this->image, start_pt, lval, res.body, isDirect);
}
res.body.brect.x -= this->offset.x;
res.body.brect.y -= this->offset.y;
res.ctable_next = this->ctable[lval];
this->ctable[lval] = res.self();
}
const Point prev_origin = res.body.origin;
res.body.origin = start_pt;
if (this->approx_method1 != this->approx_method2)
{
CV_Assert(res.body.isChain);
res.body.pts = approximateChainTC89(res.body.codes, prev_origin, this->approx_method2);
res.body.isChain = false;
}
return res;
}
bool ContourScanner_::contourScan(const int prev, int& p, Point& last_pos, const int x, const int y)
{
bool is_hole = false;
/* if not external contour */
if (isInt())
{
if (!(((prev & MASK_FLAGS) != 0 || prev == 0) && (p & MASK_FLAGS) == 0))
{
if ((prev & MASK_FLAGS) != 0 || ((p & MASK_FLAGS) != 0))
return false;
if (prev & MASK_FLAGS)
{
last_pos.x = x - 1;
}
is_hole = true;
}
}
else
{
if (!(prev == 0 && p == 1))
{
if (p != 0 || prev < 1)
return false;
if (prev & MASK8_FLAGS)
{
last_pos.x = x - 1;
}
is_hole = true;
}
}
if (mode == RETR_EXTERNAL && (is_hole || this->image.at<schar>(last_pos) > 0))
{
return false;
}
/* find contour parent */
int main_parent = -1;
if (isSimple() || (!is_hole && (mode == RETR_CCOMP || mode == RETR_FLOODFILL)) ||
last_pos.x <= 0)
{
main_parent = 0;
}
else
{
int lval;
if (isInt())
lval = this->image.at<int>(last_pos.y, last_pos.x) & MASK8_LVAL;
else
lval = this->image.at<schar>(last_pos.y, last_pos.x) & MASK8_LVAL;
main_parent = findFirstBoundingContour(last_pos, y, lval, main_parent);
// if current contour is a hole and previous contour is a hole or
// current contour is external and previous contour is external then
// the parent of the contour is the parent of the previous contour else
// the parent is the previous contour itself.
{
CNode& main_parent_elem = tree.elem(main_parent);
if (main_parent_elem.body.isHole == is_hole)
{
if (main_parent_elem.parent != -1)
{
main_parent = main_parent_elem.parent;
}
else
{
main_parent = 0;
}
}
}
// hole flag of the parent must differ from the flag of the contour
{
CNode& main_parent_elem = tree.elem(main_parent);
CV_Assert(main_parent_elem.body.isHole != is_hole);
}
}
last_pos.x = x - (is_hole ? 1 : 0);
schar nbd_ = this->nbd;
CNode& new_contour = makeContour(nbd_, is_hole, x, y);
if (new_contour.parent == -1)
{
tree.addChild(main_parent, new_contour.self());
}
this->pt.x = !isInt() ? (x + 1) : (x + 1 - (is_hole ? 1 : 0));
this->pt.y = y;
this->nbd = nbd_;
return true;
}
int ContourScanner_::findFirstBoundingContour(const Point& last_pos,
const int y,
const int lval,
int par)
{
const Point end_point(last_pos.x, y);
int res = par;
int cur = ctable[lval];
while (cur != -1)
{
CNode& cur_elem = tree.elem(cur);
if (((last_pos.x - cur_elem.body.brect.x) < cur_elem.body.brect.width) &&
((last_pos.y - cur_elem.body.brect.y) < cur_elem.body.brect.height))
{
if (res != -1)
{
CNode& res_elem = tree.elem(res);
const Point origin = res_elem.body.origin;
const bool isHole = res_elem.body.isHole;
if (isInt())
{
if (icvTraceContour<int>(this->image, origin, end_point, isHole))
break;
}
else
{
if (icvTraceContour<schar>(this->image, origin, end_point, isHole))
break;
}
}
res = cur;
}
cur = cur_elem.ctable_next;
}
return res;
}
int ContourScanner_::findNextX(int x, int y, int& prev, int& p)
{
const int width = this->image.size().width - 1;
if (isInt())
{
for (; x < width &&
((p = this->image.at<int>(y, x)) == prev || (p & MASK_VAL) == (prev & MASK_VAL));
x++)
prev = p;
}
else
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
if ((p = this->image.at<schar>(y, x)) != prev)
{
return x;
}
else
{
v_uint8 v_prev = vx_setall_u8((uchar)prev);
for (; x <= width - VTraits<v_uint8>::vlanes(); x += VTraits<v_uint8>::vlanes())
{
v_uint8 vmask = (v_ne(vx_load(this->image.ptr<uchar>(y, x)), v_prev));
if (v_check_any(vmask))
{
x += v_scan_forward(vmask);
p = this->image.at<schar>(y, x);
return x;
}
}
}
#endif
for (; x < width && (p = this->image.at<schar>(y, x)) == prev; x++)
;
}
return x;
}
bool ContourScanner_::findNext()
{
int x = this->pt.x;
int y = this->pt.y;
int width = this->image.size().width - 1;
int height = this->image.size().height - 1;
Point last_pos = this->lnbd;
int prev = isInt() ? this->image.at<int>(y, x - 1) : this->image.at<schar>(y, x - 1);
for (; y < height; y++)
{
int p = 0;
for (; x < width; x++)
{
x = findNextX(x, y, prev, p);
if (x >= width)
break;
if (contourScan(prev, p, last_pos, x, y))
{
this->lnbd = last_pos;
return true;
}
else
{
prev = p;
if ((isInt() && (prev & MASK_FLAGS)) || (!isInt() && (prev & MASK8_FLAGS)))
{
last_pos.x = x;
}
}
}
last_pos = Point(0, y + 1);
x = 1;
prev = 0;
}
return false;
}
//==============================================================================
void cv::findContours(InputArray _image,
OutputArrayOfArrays _contours,
OutputArray _hierarchy,
int mode,
int method,
Point offset)
{
CV_INSTRUMENT_REGION();
// TODO: remove this block in future
if (method == 5 /*CV_LINK_RUNS*/)
{
CV_LOG_ONCE_WARNING(NULL,
"LINK_RUNS mode has been extracted to separate function: "
"cv::findContoursLinkRuns. "
"Calling through cv::findContours will be removed in future.");
CV_CheckTrue(!_hierarchy.needed() || mode == RETR_CCOMP,
"LINK_RUNS mode supports only simplified hierarchy output (mode=RETR_CCOMP)");
findContoursLinkRuns(_image, _contours, _hierarchy);
return;
}
// TODO: need enum value, need way to return contour starting points with chain codes
if (method == 0 /*CV_CHAIN_CODE*/)
{
CV_LOG_ONCE_WARNING(NULL,
"Chain code output is an experimental feature and might change in "
"future!");
}
// Sanity check: output must be of type vector<vector<Point>>
CV_Assert((_contours.kind() == _InputArray::STD_VECTOR_VECTOR) ||
(_contours.kind() == _InputArray::STD_VECTOR_MAT) ||
(_contours.kind() == _InputArray::STD_VECTOR_UMAT));
const int res_type = (method == 0 /*CV_CHAIN_CODE*/) ? CV_8SC1 : CV_32SC2;
if (!_contours.empty())
{
CV_CheckTypeEQ(_contours.type(),
res_type,
"Contours must have type CV_8SC1 (chain code) or CV_32SC2 (other methods)");
}
if (_hierarchy.needed())
_hierarchy.clear();
// preprocess
Mat image;
copyMakeBorder(_image, image, 1, 1, 1, 1, BORDER_CONSTANT | BORDER_ISOLATED, Scalar(0));
if (image.type() != CV_32SC1)
threshold(image, image, 0, 1, THRESH_BINARY);
// find contours
ContourScanner scanner = ContourScanner_::create(image, mode, method, offset + Point(-1, -1));
while (scanner->findNext())
{
}
contourTreeToResults(scanner->tree, res_type, _contours, _hierarchy);
}
void cv::findContours(InputArray _image,
OutputArrayOfArrays _contours,
int mode,
int method,
Point offset)
{
CV_INSTRUMENT_REGION();
findContours(_image, _contours, noArray(), mode, method, offset);
}

@ -613,7 +613,7 @@ void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type)
AutoBuffer<double> _wc(cols);
double* const wc = _wc.data();
double coeff0 = 2.0 * CV_PI / (double)(cols - 1), coeff1 = 2.0f * CV_PI / (double)(rows - 1);
double coeff0 = 2.0 * CV_PI / (double)(cols - 1), coeff1 = 2.0 * CV_PI / (double)(rows - 1);
for(int j = 0; j < cols; j++)
wc[j] = 0.5 * (1.0 - cos(coeff0 * j));

@ -93,7 +93,6 @@ TEST(Imgproc_FindContours, hilbert)
dilate(img, img, Mat());
vector<vector<Point> > contours;
findContours(img, contours, noArray(), RETR_LIST, CHAIN_APPROX_SIMPLE);
printf("ncontours = %d, contour[0].npoints=%d\n", (int)contours.size(), (int)contours[0].size());
img.setTo(Scalar::all(0));
drawContours(img, contours, 0, Scalar::all(255), 1);
@ -164,10 +163,12 @@ TEST(Imgproc_FindContours, regression_4363_shared_nbd)
if (found)
{
ASSERT_EQ(contours.size(), hierarchy.size());
EXPECT_LT(hierarchy[index][3], 0) << "Desired result: (7,9) has no parent - Actual result: parent of (7,9) is another contour. index = " << index;
}
}
TEST(Imgproc_PointPolygonTest, regression_10222)
{
vector<Point> contour;

@ -0,0 +1,605 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
#include "opencv2/ts/ocl_test.hpp"
#include "opencv2/imgproc/detail/legacy.hpp"
#define CHECK_OLD 1
namespace opencv_test { namespace {
// debug function
template <typename T>
inline static void print_pts(const T& c)
{
for (const auto& one_pt : c)
{
cout << one_pt << " ";
}
cout << endl;
}
// debug function
template <typename T>
inline static void print_pts_2(vector<T>& cs)
{
int cnt = 0;
cout << "Contours:" << endl;
for (const auto& one_c : cs)
{
cout << cnt++ << " : ";
print_pts(one_c);
}
};
// draw 1-2 px blob with orientation defined by 'kind'
template <typename T>
inline static void drawSmallContour(Mat& img, Point pt, int kind, int color_)
{
const T color = static_cast<T>(color_);
img.at<T>(pt) = color;
switch (kind)
{
case 1: img.at<T>(pt + Point(1, 0)) = color; break;
case 2: img.at<T>(pt + Point(1, -1)) = color; break;
case 3: img.at<T>(pt + Point(0, -1)) = color; break;
case 4: img.at<T>(pt + Point(-1, -1)) = color; break;
case 5: img.at<T>(pt + Point(-1, 0)) = color; break;
case 6: img.at<T>(pt + Point(-1, 1)) = color; break;
case 7: img.at<T>(pt + Point(0, 1)) = color; break;
case 8: img.at<T>(pt + Point(1, 1)) = color; break;
default: break;
}
}
inline static void drawContours(Mat& img,
const vector<vector<Point>>& contours,
const Scalar& color = Scalar::all(255))
{
for (const auto& contour : contours)
{
for (size_t n = 0, end = contour.size(); n < end; ++n)
{
size_t m = n + 1;
if (n == end - 1)
m = 0;
line(img, contour[m], contour[n], color, 1, LINE_8);
}
}
}
//==================================================================================================
// Test parameters - mode + method
typedef testing::TestWithParam<tuple<int, int>> Imgproc_FindContours_Modes1;
// Draw random rectangle and find contours
//
TEST_P(Imgproc_FindContours_Modes1, rectangle)
{
const int mode = get<0>(GetParam());
const int method = get<1>(GetParam());
const size_t ITER = 100;
RNG rng = TS::ptr()->get_rng();
for (size_t i = 0; i < ITER; ++i)
{
SCOPED_TRACE(cv::format("i=%zu", i));
const Size sz(rng.uniform(640, 1920), rng.uniform(480, 1080));
Mat img(sz, CV_8UC1, Scalar::all(0));
Mat img32s(sz, CV_32SC1, Scalar::all(0));
const Rect r(Point(rng.uniform(1, sz.width / 2 - 1), rng.uniform(1, sz.height / 2)),
Point(rng.uniform(sz.width / 2 - 1, sz.width - 1),
rng.uniform(sz.height / 2 - 1, sz.height - 1)));
rectangle(img, r, Scalar::all(255));
rectangle(img32s, r, Scalar::all(255), FILLED);
const vector<Point> ext_ref {r.tl(),
r.tl() + Point(0, r.height - 1),
r.br() + Point(-1, -1),
r.tl() + Point(r.width - 1, 0)};
const vector<Point> int_ref {ext_ref[0] + Point(0, 1),
ext_ref[0] + Point(1, 0),
ext_ref[3] + Point(-1, 0),
ext_ref[3] + Point(0, 1),
ext_ref[2] + Point(0, -1),
ext_ref[2] + Point(-1, 0),
ext_ref[1] + Point(1, 0),
ext_ref[1] + Point(0, -1)};
const size_t ext_perimeter = r.width * 2 + r.height * 2;
const size_t int_perimeter = ext_perimeter - 4;
vector<vector<Point>> contours;
vector<vector<schar>> chains;
vector<Vec4i> hierarchy;
// run functionn
if (mode == RETR_FLOODFILL)
if (method == 0)
findContours(img32s, chains, hierarchy, mode, method);
else
findContours(img32s, contours, hierarchy, mode, method);
else if (method == 0)
findContours(img, chains, hierarchy, mode, method);
else
findContours(img, contours, hierarchy, mode, method);
// verify results
if (mode == RETR_EXTERNAL)
{
if (method == 0)
{
ASSERT_EQ(1U, chains.size());
}
else
{
ASSERT_EQ(1U, contours.size());
if (method == CHAIN_APPROX_NONE)
{
EXPECT_EQ(int_perimeter, contours[0].size());
}
else if (method == CHAIN_APPROX_SIMPLE)
{
EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[0]), 0);
}
}
}
else
{
if (method == 0)
{
ASSERT_EQ(2U, chains.size());
}
else
{
ASSERT_EQ(2U, contours.size());
if (mode == RETR_LIST)
{
if (method == CHAIN_APPROX_NONE)
{
EXPECT_EQ(int_perimeter - 4, contours[0].size());
EXPECT_EQ(int_perimeter, contours[1].size());
}
else if (method == CHAIN_APPROX_SIMPLE)
{
EXPECT_MAT_NEAR(Mat(int_ref), Mat(contours[0]), 0);
EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[1]), 0);
}
}
else if (mode == RETR_CCOMP || mode == RETR_TREE)
{
if (method == CHAIN_APPROX_NONE)
{
EXPECT_EQ(int_perimeter, contours[0].size());
EXPECT_EQ(int_perimeter - 4, contours[1].size());
}
else if (method == CHAIN_APPROX_SIMPLE)
{
EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[0]), 0);
EXPECT_MAT_NEAR(Mat(int_ref), Mat(contours[1]), 0);
}
}
else if (mode == RETR_FLOODFILL)
{
if (method == CHAIN_APPROX_NONE)
{
EXPECT_EQ(int_perimeter + 4, contours[0].size());
}
else if (method == CHAIN_APPROX_SIMPLE)
{
EXPECT_EQ(int_ref.size(), contours[0].size());
EXPECT_MAT_NEAR(Mat(ext_ref), Mat(contours[1]), 0);
}
}
}
}
#if CHECK_OLD
if (method != 0) // old doesn't support chain codes
{
if (mode != RETR_FLOODFILL)
{
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours.size(), contours_o.size());
for (size_t j = 0; j < contours.size(); ++j)
{
SCOPED_TRACE(format("contour %zu", j));
EXPECT_MAT_NEAR(Mat(contours[j]), Mat(contours_o[j]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
}
else
{
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img32s, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours.size(), contours_o.size());
for (size_t j = 0; j < contours.size(); ++j)
{
SCOPED_TRACE(format("contour %zu", j));
EXPECT_MAT_NEAR(Mat(contours[j]), Mat(contours_o[j]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
}
}
#endif
}
}
// Draw many small 1-2px blobs and find contours
//
TEST_P(Imgproc_FindContours_Modes1, small)
{
const int mode = get<0>(GetParam());
const int method = get<1>(GetParam());
const size_t DIM = 1000;
const Size sz(DIM, DIM);
const int num = (DIM / 10) * (DIM / 10); // number of 10x10 squares
Mat img(sz, CV_8UC1, Scalar::all(0));
Mat img32s(sz, CV_32SC1, Scalar::all(0));
vector<Point> pts;
int extra_contours_32s = 0;
for (int j = 0; j < num; ++j)
{
const int kind = j % 9;
Point pt {(j % 100) * 10 + 4, (j / 100) * 10 + 4};
drawSmallContour<uchar>(img, pt, kind, 255);
drawSmallContour<int>(img32s, pt, kind, j + 1);
pts.push_back(pt);
// NOTE: for some reason these small diagonal contours (NW, SE)
// result in 2 external contours for FLOODFILL mode
if (kind == 8 || kind == 4)
++extra_contours_32s;
}
{
vector<vector<Point>> contours;
vector<vector<schar>> chains;
vector<Vec4i> hierarchy;
if (mode == RETR_FLOODFILL)
{
if (method == 0)
{
findContours(img32s, chains, hierarchy, mode, method);
ASSERT_EQ(pts.size() * 2 + extra_contours_32s, chains.size());
}
else
{
findContours(img32s, contours, hierarchy, mode, method);
ASSERT_EQ(pts.size() * 2 + extra_contours_32s, contours.size());
#if CHECK_OLD
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img32s, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours.size(), contours_o.size());
for (size_t i = 0; i < contours.size(); ++i)
{
SCOPED_TRACE(format("contour %zu", i));
EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
#endif
}
}
else
{
if (method == 0)
{
findContours(img, chains, hierarchy, mode, method);
ASSERT_EQ(pts.size(), chains.size());
}
else
{
findContours(img, contours, hierarchy, mode, method);
ASSERT_EQ(pts.size(), contours.size());
#if CHECK_OLD
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours.size(), contours_o.size());
for (size_t i = 0; i < contours.size(); ++i)
{
SCOPED_TRACE(format("contour %zu", i));
EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
#endif
}
}
}
}
// Draw many nested rectangles and find contours
//
TEST_P(Imgproc_FindContours_Modes1, deep)
{
const int mode = get<0>(GetParam());
const int method = get<1>(GetParam());
const size_t DIM = 1000;
const Size sz(DIM, DIM);
const size_t NUM = 249U;
Mat img(sz, CV_8UC1, Scalar::all(0));
Mat img32s(sz, CV_32SC1, Scalar::all(0));
Rect rect(1, 1, 998, 998);
for (size_t i = 0; i < NUM; ++i)
{
rectangle(img, rect, Scalar::all(255));
rectangle(img32s, rect, Scalar::all((double)i + 1), FILLED);
rect.x += 2;
rect.y += 2;
rect.width -= 4;
rect.height -= 4;
}
{
vector<vector<Point>> contours {{{0, 0}, {1, 1}}};
vector<vector<schar>> chains {{1, 2, 3}};
vector<Vec4i> hierarchy;
if (mode == RETR_FLOODFILL)
{
if (method == 0)
{
findContours(img32s, chains, hierarchy, mode, method);
ASSERT_EQ(2 * NUM, chains.size());
}
else
{
findContours(img32s, contours, hierarchy, mode, method);
ASSERT_EQ(2 * NUM, contours.size());
#if CHECK_OLD
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img32s, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours.size(), contours_o.size());
for (size_t i = 0; i < contours.size(); ++i)
{
SCOPED_TRACE(format("contour %zu", i));
EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
#endif
}
}
else
{
const size_t expected_count = (mode == RETR_EXTERNAL) ? 1U : 2 * NUM;
if (method == 0)
{
findContours(img, chains, hierarchy, mode, method);
ASSERT_EQ(expected_count, chains.size());
}
else
{
findContours(img, contours, hierarchy, mode, method);
ASSERT_EQ(expected_count, contours.size());
#if CHECK_OLD
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours.size(), contours_o.size());
for (size_t i = 0; i < contours.size(); ++i)
{
SCOPED_TRACE(format("contour %zu", i));
EXPECT_MAT_NEAR(Mat(contours[i]), Mat(contours_o[i]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy), Mat(hierarchy_o), 0);
#endif
}
}
}
}
INSTANTIATE_TEST_CASE_P(
,
Imgproc_FindContours_Modes1,
testing::Combine(
testing::Values(RETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE, RETR_FLOODFILL),
testing::Values(0,
CHAIN_APPROX_NONE,
CHAIN_APPROX_SIMPLE,
CHAIN_APPROX_TC89_L1,
CHAIN_APPROX_TC89_KCOS)));
//==================================================================================================
typedef testing::TestWithParam<tuple<int, int>> Imgproc_FindContours_Modes2;
// Very approximate backport of an old accuracy test
//
TEST_P(Imgproc_FindContours_Modes2, new_accuracy)
{
const int mode = get<0>(GetParam());
const int method = get<1>(GetParam());
RNG& rng = TS::ptr()->get_rng();
const int blob_count = rng.uniform(1, 10);
const Size sz(rng.uniform(640, 1920), rng.uniform(480, 1080));
const int blob_sz = 50;
// prepare image
Mat img(sz, CV_8UC1, Scalar::all(0));
vector<RotatedRect> rects;
for (int i = 0; i < blob_count; ++i)
{
const Point2f center((float)rng.uniform(blob_sz, sz.width - blob_sz),
(float)rng.uniform(blob_sz, sz.height - blob_sz));
const Size2f rsize((float)rng.uniform(1, blob_sz), (float)rng.uniform(1, blob_sz));
RotatedRect rect(center, rsize, rng.uniform(0.f, 180.f));
rects.push_back(rect);
ellipse(img, rect, Scalar::all(100), FILLED);
}
// draw contours manually
Mat cont_img(sz, CV_8UC1, Scalar::all(0));
for (int y = 1; y < sz.height - 1; ++y)
{
for (int x = 1; x < sz.width - 1; ++x)
{
if (img.at<uchar>(y, x) != 0 &&
((img.at<uchar>(y - 1, x) == 0) || (img.at<uchar>(y + 1, x) == 0) ||
(img.at<uchar>(y, x + 1) == 0) || (img.at<uchar>(y, x - 1) == 0)))
{
cont_img.at<uchar>(y, x) = 255;
}
}
}
// find contours
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(img, contours, hierarchy, mode, method);
// 0 < contours <= rects
EXPECT_GT(contours.size(), 0U);
EXPECT_GE(rects.size(), contours.size());
// draw contours
Mat res_img(sz, CV_8UC1, Scalar::all(0));
drawContours(res_img, contours);
// compare resulting drawn contours with manually drawn contours
const double diff1 = cvtest::norm(cont_img, res_img, NORM_L1) / 255;
if (method == CHAIN_APPROX_NONE || method == CHAIN_APPROX_SIMPLE)
{
EXPECT_EQ(0., diff1);
}
#if CHECK_OLD
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours(img, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours_o.size(), contours.size());
for (size_t i = 0; i < contours_o.size(); ++i)
{
SCOPED_TRACE(format("contour = %zu", i));
EXPECT_MAT_NEAR(Mat(contours_o[i]), Mat(contours[i]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy_o), Mat(hierarchy), 0);
#endif
}
TEST_P(Imgproc_FindContours_Modes2, approx)
{
const int mode = get<0>(GetParam());
const int method = get<1>(GetParam());
const Size sz {500, 500};
Mat img = Mat::zeros(sz, CV_8UC1);
for (int c = 0; c < 4; ++c)
{
if (c != 0)
{
// noise + filter + threshold
RNG& rng = TS::ptr()->get_rng();
cvtest::randUni(rng, img, 0, 255);
Mat fimg;
boxFilter(img, fimg, CV_8U, Size(5, 5));
Mat timg;
const int level = 44 + c * 42;
// 'level' goes through:
// 86 - some black speckles on white
// 128 - 50/50 black/white
// 170 - some white speckles on black
cv::threshold(fimg, timg, level, 255, THRESH_BINARY);
}
else
{
// circle with cut
const Point center {250, 250};
const int r {20};
const Point cut {r, r};
circle(img, center, r, Scalar(255), FILLED);
rectangle(img, center, center + cut, Scalar(0), FILLED);
}
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(img, contours, hierarchy, mode, method);
#if CHECK_OLD
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img, contours_o, hierarchy_o, mode, method);
ASSERT_EQ(contours_o.size(), contours.size());
for (size_t i = 0; i < contours_o.size(); ++i)
{
SCOPED_TRACE(format("c = %d, contour = %zu", c, i));
EXPECT_MAT_NEAR(Mat(contours_o[i]), Mat(contours[i]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy_o), Mat(hierarchy), 0);
#endif
// TODO: check something
}
}
// TODO: offset test
// no RETR_FLOODFILL - no CV_32S input images
INSTANTIATE_TEST_CASE_P(
,
Imgproc_FindContours_Modes2,
testing::Combine(testing::Values(RETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE),
testing::Values(CHAIN_APPROX_NONE,
CHAIN_APPROX_SIMPLE,
CHAIN_APPROX_TC89_L1,
CHAIN_APPROX_TC89_KCOS)));
TEST(Imgproc_FindContours, link_runs)
{
const Size sz {500, 500};
Mat img = Mat::zeros(sz, CV_8UC1);
// noise + filter + threshold
RNG& rng = TS::ptr()->get_rng();
cvtest::randUni(rng, img, 0, 255);
Mat fimg;
boxFilter(img, fimg, CV_8U, Size(5, 5));
const int level = 135;
cv::threshold(fimg, img, level, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContoursLinkRuns(img, contours, hierarchy);
if (cvtest::debugLevel >= 10)
{
print_pts_2(contours);
Mat res = Mat::zeros(sz, CV_8UC1);
drawContours(res, contours);
imshow("res", res);
imshow("img", img);
waitKey(0);
}
#if CHECK_OLD
vector<vector<Point>> contours_o;
vector<Vec4i> hierarchy_o;
findContours_legacy(img, contours_o, hierarchy_o, 0, 5); // CV_LINK_RUNS method
ASSERT_EQ(contours_o.size(), contours.size());
for (size_t i = 0; i < contours_o.size(); ++i)
{
SCOPED_TRACE(format("contour = %zu", i));
EXPECT_MAT_NEAR(Mat(contours_o[i]), Mat(contours[i]), 0);
}
EXPECT_MAT_NEAR(Mat(hierarchy_o), Mat(hierarchy), 0);
#endif
}
}} // namespace opencv_test

@ -1105,4 +1105,176 @@ TEST(Imgproc, morphologyEx_small_input_22893)
ASSERT_EQ(0, cvtest::norm(result, gold, NORM_INF));
}
TEST(Imgproc_sepFilter2D, identity)
{
std::vector<uint8_t> kernelX{0, 0, 0, 1, 0, 0, 0};
std::vector<uint8_t> kernelY{0, 0, 1, 0, 0};
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::sepFilter2D(input, result, input.depth(), kernelX, kernelY);
EXPECT_EQ(0, cv::norm(result, input, NORM_INF));
}
TEST(Imgproc_sepFilter2D, shift)
{
std::vector<float> kernelX{1, 0, 0};
std::vector<float> kernelY{0, 0, 1};
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::sepFilter2D(input, result, input.depth(), kernelX, kernelY);
int W = input.cols;
int H = input.rows;
Mat inputCrop = input(Range(1, H), Range(0, W - 1));
Mat resultCrop = result(Range(0, H - 1), Range(1, W));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
// Checking borders. Should be BORDER_REFLECT_101
inputCrop = input(Range(H - 2, H - 1), Range(0, W - 1));
resultCrop = result(Range(H - 1, H), Range(1, W));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
inputCrop = input(Range(1, H), Range(1, 2));
resultCrop = result(Range(0, H - 1), Range(0, 1));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
inputCrop = input(Range(H - 2, H - 1), Range(1, 2));
resultCrop = result(Range(H - 1, H), Range(0, 1));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
}
TEST(Imgproc_sepFilter2D, zeroPadding)
{
std::vector<int> kernelX{1, 0, 0};
std::vector<int> kernelY{0, 0, 1};
Point anchor(-1, -1);
double delta = 0;
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::sepFilter2D(input, result, input.depth(), kernelX, kernelY, anchor, delta, BORDER_CONSTANT);
int W = input.cols;
int H = input.rows;
Mat inputCrop = input(Range(1, H), Range(0, W - 1));
Mat resultCrop = result(Range(0, H - 1), Range(1, W));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
// Checking borders
resultCrop = result(Range(H - 1, H), Range(0, W));
EXPECT_EQ(0, cv::norm(resultCrop, NORM_INF));
resultCrop = result(Range(0, H), Range(0, 1));
EXPECT_EQ(0, cv::norm(resultCrop, NORM_INF));
}
TEST(Imgproc_sepFilter2D, anchor)
{
std::vector<float> kernelX{0, 1, 0};
std::vector<float> kernelY{0, 1, 0};
Point anchor(2, 0);
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::sepFilter2D(input, result, input.depth(), kernelX, kernelY, anchor);
int W = input.cols;
int H = input.rows;
Mat inputCrop = input(Range(1, H), Range(0, W - 1));
Mat resultCrop = result(Range(0, H - 1), Range(1, W));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
// Checking borders. Should be BORDER_REFLECT_101
inputCrop = input(Range(H - 2, H - 1), Range(0, W - 1));
resultCrop = result(Range(H - 1, H), Range(1, W));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
inputCrop = input(Range(1, H), Range(1, 2));
resultCrop = result(Range(0, H - 1), Range(0, 1));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
inputCrop = input(Range(H - 2, H - 1), Range(1, 2));
resultCrop = result(Range(H - 1, H), Range(0, 1));
EXPECT_EQ(0, cv::norm(resultCrop, inputCrop, NORM_INF));
}
TEST(Imgproc_sepFilter2D, delta)
{
std::vector<float> kernelX{0, 0.5, 0};
std::vector<float> kernelY{0, 1, 0};
Point anchor(1, 1);
double delta = 5;
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::sepFilter2D(input, result, input.depth(), kernelX, kernelY, anchor, delta);
Mat gt = input / 2 + delta;
EXPECT_EQ(0, cv::norm(result, gt, NORM_INF));
}
typedef testing::TestWithParam<int> Imgproc_sepFilter2D_outTypes;
TEST_P(Imgproc_sepFilter2D_outTypes, simple)
{
int outputType = GetParam();
std::vector<float> kernelX{0, 0.5, 0};
std::vector<float> kernelY{0, 0.5, 0};
Point anchor(1, 1);
double delta = 5;
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::sepFilter2D(input, result, outputType, kernelX, kernelY, anchor, delta);
input.convertTo(input, outputType);
Mat gt = input / 4 + delta;
EXPECT_EQ(0, cv::norm(result, gt, NORM_INF));
}
INSTANTIATE_TEST_CASE_P(/**/, Imgproc_sepFilter2D_outTypes,
testing::Values(CV_16S, CV_32F, CV_64F),
);
typedef testing::TestWithParam<int> Imgproc_sepFilter2D_types;
TEST_P(Imgproc_sepFilter2D_types, simple)
{
int outputType = GetParam();
std::vector<float> kernelX{0, 0.5, 0};
std::vector<float> kernelY{0, 0.5, 0};
Point anchor(1, 1);
double delta = 5;
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
input.convertTo(input, outputType);
Mat result;
cv::sepFilter2D(input, result, outputType, kernelX, kernelY, anchor, delta);
Mat gt = input / 4 + delta;
EXPECT_EQ(0, cv::norm(result, gt, NORM_INF));
}
INSTANTIATE_TEST_CASE_P(/**/, Imgproc_sepFilter2D_types,
testing::Values(CV_16S, CV_32F, CV_64F),
);
}} // namespace

@ -96,4 +96,58 @@ TEST(Imgproc_Threshold, regression_THRESH_TOZERO_IPP_21258_Max)
EXPECT_EQ(0, cv::norm(result, NORM_INF));
}
TEST(Imgproc_AdaptiveThreshold, mean)
{
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::adaptiveThreshold(input, result, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, 8);
const string gt_path = cvtest::findDataFile("../cv/imgproc/adaptive_threshold1.png");
Mat gt = imread(gt_path, IMREAD_GRAYSCALE);
EXPECT_EQ(0, cv::norm(result, gt, NORM_INF));
}
TEST(Imgproc_AdaptiveThreshold, mean_inv)
{
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::adaptiveThreshold(input, result, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 15, 8);
const string gt_path = cvtest::findDataFile("../cv/imgproc/adaptive_threshold1.png");
Mat gt = imread(gt_path, IMREAD_GRAYSCALE);
gt = Mat(gt.rows, gt.cols, CV_8UC1, cv::Scalar(255)) - gt;
EXPECT_EQ(0, cv::norm(result, gt, NORM_INF));
}
TEST(Imgproc_AdaptiveThreshold, gauss)
{
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::adaptiveThreshold(input, result, 200, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 21, -5);
const string gt_path = cvtest::findDataFile("../cv/imgproc/adaptive_threshold2.png");
Mat gt = imread(gt_path, IMREAD_GRAYSCALE);
EXPECT_EQ(0, cv::norm(result, gt, NORM_INF));
}
TEST(Imgproc_AdaptiveThreshold, gauss_inv)
{
const string input_path = cvtest::findDataFile("../cv/shared/baboon.png");
Mat input = imread(input_path, IMREAD_GRAYSCALE);
Mat result;
cv::adaptiveThreshold(input, result, 200, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 21, -5);
const string gt_path = cvtest::findDataFile("../cv/imgproc/adaptive_threshold2.png");
Mat gt = imread(gt_path, IMREAD_GRAYSCALE);
gt = Mat(gt.rows, gt.cols, CV_8UC1, cv::Scalar(200)) - gt;
EXPECT_EQ(0, cv::norm(result, gt, NORM_INF));
}
}} // namespace

@ -2937,7 +2937,8 @@ QRDecode::QRDecode(bool _useAlignmentMarkers):
useAlignmentMarkers(_useAlignmentMarkers),
version(0),
version_size(0),
test_perspective_size(0.f)
test_perspective_size(0.f),
mode(QRCodeEncoder::EncodeMode::MODE_AUTO)
{}
std::string ImplContour::decode(InputArray in, InputArray points, OutputArray straight_qrcode) const {

@ -1321,262 +1321,6 @@ yuv411p_to_rgb24(int width, int height,
}
}
/*
* BAYER2RGB24 ROUTINE TAKEN FROM:
*
* Sonix SN9C10x based webcam basic I/F routines
* Takafumi Mizuno <taka-qce@ls-a.jp>
*
*/
static void bayer2rgb24(long int WIDTH, long int HEIGHT, unsigned char *src, unsigned char *dst)
{
long int i;
unsigned char *rawpt, *scanpt;
long int size;
rawpt = src;
scanpt = dst;
size = WIDTH*HEIGHT;
for ( i = 0; i < size; i++ ) {
if ( (i/WIDTH) % 2 == 0 ) {
if ( (i % 2) == 0 ) {
/* B */
if ( (i > WIDTH) && ((i % WIDTH) > 0) ) {
*scanpt++ = (*(rawpt-WIDTH-1)+*(rawpt-WIDTH+1)+
*(rawpt+WIDTH-1)+*(rawpt+WIDTH+1))/4; /* R */
*scanpt++ = (*(rawpt-1)+*(rawpt+1)+
*(rawpt+WIDTH)+*(rawpt-WIDTH))/4; /* G */
*scanpt++ = *rawpt; /* B */
} else {
/* first line or left column */
*scanpt++ = *(rawpt+WIDTH+1); /* R */
*scanpt++ = (*(rawpt+1)+*(rawpt+WIDTH))/2; /* G */
*scanpt++ = *rawpt; /* B */
}
} else {
/* (B)G */
if ( (i > WIDTH) && ((i % WIDTH) < (WIDTH-1)) ) {
*scanpt++ = (*(rawpt+WIDTH)+*(rawpt-WIDTH))/2; /* R */
*scanpt++ = *rawpt; /* G */
*scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; /* B */
} else {
/* first line or right column */
*scanpt++ = *(rawpt+WIDTH); /* R */
*scanpt++ = *rawpt; /* G */
*scanpt++ = *(rawpt-1); /* B */
}
}
} else {
if ( (i % 2) == 0 ) {
/* G(R) */
if ( (i < (WIDTH*(HEIGHT-1))) && ((i % WIDTH) > 0) ) {
*scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; /* R */
*scanpt++ = *rawpt; /* G */
*scanpt++ = (*(rawpt+WIDTH)+*(rawpt-WIDTH))/2; /* B */
} else {
/* bottom line or left column */
*scanpt++ = *(rawpt+1); /* R */
*scanpt++ = *rawpt; /* G */
*scanpt++ = *(rawpt-WIDTH); /* B */
}
} else {
/* R */
if ( i < (WIDTH*(HEIGHT-1)) && ((i % WIDTH) < (WIDTH-1)) ) {
*scanpt++ = *rawpt; /* R */
*scanpt++ = (*(rawpt-1)+*(rawpt+1)+
*(rawpt-WIDTH)+*(rawpt+WIDTH))/4; /* G */
*scanpt++ = (*(rawpt-WIDTH-1)+*(rawpt-WIDTH+1)+
*(rawpt+WIDTH-1)+*(rawpt+WIDTH+1))/4; /* B */
} else {
/* bottom line or right column */
*scanpt++ = *rawpt; /* R */
*scanpt++ = (*(rawpt-1)+*(rawpt-WIDTH))/2; /* G */
*scanpt++ = *(rawpt-WIDTH-1); /* B */
}
}
}
rawpt++;
}
}
// SGBRG to RGB24
// for some reason, red and blue needs to be swapped
// at least for 046d:092f Logitech, Inc. QuickCam Express Plus to work
//see: http://www.siliconimaging.com/RGB%20Bayer.htm
//and 4.6 at http://tldp.org/HOWTO/html_single/libdc1394-HOWTO/
static void sgbrg2rgb24(long int WIDTH, long int HEIGHT, unsigned char *src, unsigned char *dst)
{
long int i;
unsigned char *rawpt, *scanpt;
long int size;
rawpt = src;
scanpt = dst;
size = WIDTH*HEIGHT;
for ( i = 0; i < size; i++ )
{
if ( (i/WIDTH) % 2 == 0 ) //even row
{
if ( (i % 2) == 0 ) //even pixel
{
if ( (i > WIDTH) && ((i % WIDTH) > 0) )
{
*scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = (*(rawpt-WIDTH) + *(rawpt+WIDTH))/2; /* B */
} else
{
/* first line or left column */
*scanpt++ = *(rawpt+1); /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = *(rawpt+WIDTH); /* B */
}
} else //odd pixel
{
if ( (i > WIDTH) && ((i % WIDTH) < (WIDTH-1)) )
{
*scanpt++ = *(rawpt); /* R */
*scanpt++ = (*(rawpt-1)+*(rawpt+1)+*(rawpt-WIDTH)+*(rawpt+WIDTH))/4; /* G */
*scanpt++ = (*(rawpt-WIDTH-1) + *(rawpt-WIDTH+1) + *(rawpt+WIDTH-1) + *(rawpt+WIDTH+1))/4; /* B */
} else
{
/* first line or right column */
*scanpt++ = *(rawpt); /* R */
*scanpt++ = (*(rawpt-1)+*(rawpt+WIDTH))/2; /* G */
*scanpt++ = *(rawpt+WIDTH-1); /* B */
}
}
} else
{ //odd row
if ( (i % 2) == 0 ) //even pixel
{
if ( (i < (WIDTH*(HEIGHT-1))) && ((i % WIDTH) > 0) )
{
*scanpt++ = (*(rawpt-WIDTH-1)+*(rawpt-WIDTH+1)+*(rawpt+WIDTH-1)+*(rawpt+WIDTH+1))/4; /* R */
*scanpt++ = (*(rawpt-1)+*(rawpt+1)+*(rawpt-WIDTH)+*(rawpt+WIDTH))/4; /* G */
*scanpt++ = *(rawpt); /* B */
} else
{
/* bottom line or left column */
*scanpt++ = *(rawpt-WIDTH+1); /* R */
*scanpt++ = (*(rawpt+1)+*(rawpt-WIDTH))/2; /* G */
*scanpt++ = *(rawpt); /* B */
}
} else
{ //odd pixel
if ( i < (WIDTH*(HEIGHT-1)) && ((i % WIDTH) < (WIDTH-1)) )
{
*scanpt++ = (*(rawpt-WIDTH)+*(rawpt+WIDTH))/2; /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; /* B */
} else
{
/* bottom line or right column */
*scanpt++ = (*(rawpt-WIDTH)); /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = (*(rawpt-1)); /* B */
}
}
}
rawpt++;
}
}
// SGRBG to RGB24
static void sgrbg2rgb24(long int WIDTH, long int HEIGHT, unsigned char *src, unsigned char *dst)
{
long int i;
unsigned char *rawpt, *scanpt;
long int size;
rawpt = src;
scanpt = dst;
size = WIDTH*HEIGHT;
for ( i = 0; i < size; i++ )
{
if ( (i/WIDTH) % 2 == 0 ) //even row
{
if ( (i % 2) == 0 ) //even pixel
{
if ( (i > WIDTH) && ((i % WIDTH) > 0) )
{
*scanpt++ = (*(rawpt-WIDTH) + *(rawpt+WIDTH))/2; /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; /* B */
} else
{
/* first line or left column */
*scanpt++ = *(rawpt+WIDTH); /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = *(rawpt+1); /* B */
}
} else //odd pixel
{
if ( (i > WIDTH) && ((i % WIDTH) < (WIDTH-1)) )
{
*scanpt++ = (*(rawpt-WIDTH-1) + *(rawpt-WIDTH+1) +
*(rawpt+WIDTH-1) + *(rawpt+WIDTH+1)) / 4; /* R */
*scanpt++ = (*(rawpt-1) + *(rawpt+1) +
*(rawpt-WIDTH) + *(rawpt+WIDTH)) / 4; /* G */
*scanpt++ = *(rawpt); /* B */
} else
{
/* first line or right column */
*scanpt++ = *(rawpt+WIDTH-1); /* R */
*scanpt++ = (*(rawpt-1)+*(rawpt+WIDTH))/2; /* G */
*scanpt++ = *(rawpt); /* B */
}
}
} else
{ //odd row
if ( (i % 2) == 0 ) //even pixel
{
if ( (i < (WIDTH*(HEIGHT-1))) && ((i % WIDTH) > 0) )
{
*scanpt++ = *(rawpt); /* R */
*scanpt++ = (*(rawpt-1) + *(rawpt+1)+
*(rawpt-WIDTH) + *(rawpt+WIDTH)) / 4; /* G */
*scanpt++ = (*(rawpt-WIDTH-1) + *(rawpt-WIDTH+1) +
*(rawpt+WIDTH-1) + *(rawpt+WIDTH+1)) / 4; /* B */
} else
{
/* bottom line or left column */
*scanpt++ = *(rawpt); /* R */
*scanpt++ = (*(rawpt+1)+*(rawpt-WIDTH))/2; /* G */
*scanpt++ = *(rawpt-WIDTH+1); /* B */
}
} else
{ //odd pixel
if ( i < (WIDTH*(HEIGHT-1)) && ((i % WIDTH) < (WIDTH-1)) )
{
*scanpt++ = (*(rawpt-1)+*(rawpt+1))/2; /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = (*(rawpt-WIDTH)+*(rawpt+WIDTH))/2; /* B */
} else
{
/* bottom line or right column */
*scanpt++ = (*(rawpt-1)); /* R */
*scanpt++ = *(rawpt); /* G */
*scanpt++ = (*(rawpt-WIDTH)); /* B */
}
}
}
rawpt++;
}
}
#define CLAMP(x) ((x)<0?0:((x)>255)?255:(x))
typedef struct {
@ -1778,28 +1522,6 @@ void CvCaptureCAM_V4L::convertToRgb(const Buffer &currentBuffer)
yuv411p_to_rgb24(imageSize.width, imageSize.height,
start, (unsigned char*)frame.imageData);
return;
case V4L2_PIX_FMT_SBGGR8:
bayer2rgb24(imageSize.width, imageSize.height,
start, (unsigned char*)frame.imageData);
return;
case V4L2_PIX_FMT_SN9C10X:
sonix_decompress_init();
sonix_decompress(imageSize.width, imageSize.height,
start, (unsigned char*)buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start);
bayer2rgb24(imageSize.width, imageSize.height,
(unsigned char*)buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start,
(unsigned char*)frame.imageData);
return;
case V4L2_PIX_FMT_SGBRG8:
sgbrg2rgb24(imageSize.width, imageSize.height,
start, (unsigned char*)frame.imageData);
return;
case V4L2_PIX_FMT_SGRBG8:
sgrbg2rgb24(imageSize.width, imageSize.height,
start, (unsigned char*)frame.imageData);
return;
default:
break;
}
@ -1872,6 +1594,36 @@ void CvCaptureCAM_V4L::convertToRgb(const Buffer &currentBuffer)
cv::cvtColor(temp, destination, COLOR_GRAY2BGR);
return;
}
case V4L2_PIX_FMT_SN9C10X:
{
sonix_decompress_init();
sonix_decompress(imageSize.width, imageSize.height,
start, (unsigned char*)buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start);
cv::Mat cv_buf(imageSize, CV_8UC1, buffers[MAX_V4L_BUFFERS].memories[MEMORY_RGB].start);
cv::cvtColor(cv_buf, destination, COLOR_BayerRG2BGR);
return;
}
case V4L2_PIX_FMT_SRGGB8:
{
cv::cvtColor(cv::Mat(imageSize, CV_8UC1, start), destination, COLOR_BayerBG2BGR);
return;
}
case V4L2_PIX_FMT_SBGGR8:
{
cv::cvtColor(cv::Mat(imageSize, CV_8UC1, start), destination, COLOR_BayerRG2BGR);
return;
}
case V4L2_PIX_FMT_SGBRG8:
{
cv::cvtColor(cv::Mat(imageSize, CV_8UC1, start), destination, COLOR_BayerGR2BGR);
return;
}
case V4L2_PIX_FMT_SGRBG8:
{
cv::cvtColor(cv::Mat(imageSize, CV_8UC1, start), destination, COLOR_BayerGB2BGR);
return;
}
case V4L2_PIX_FMT_GREY:
cv::cvtColor(cv::Mat(imageSize, CV_8UC1, start), destination, COLOR_GRAY2BGR);
break;

@ -17,6 +17,8 @@
#ifdef HAVE_CAMV4L2
// #define DUMP_CAMERA_FRAME
#include "test_precomp.hpp"
#include <opencv2/core/utils/configuration.private.hpp>
#include <linux/videodev2.h>
@ -70,6 +72,7 @@ TEST_P(videoio_v4l2, formats)
const string device = devs[0];
const Size sz(640, 480);
const Format_Channels_Depth params = GetParam();
const Size esz(sz.width * params.mul_width, sz.height * params.mul_height);
{
// Case with RAW output
@ -83,7 +86,17 @@ TEST_P(videoio_v4l2, formats)
Mat img;
EXPECT_TRUE(cap.grab());
EXPECT_TRUE(cap.retrieve(img));
EXPECT_EQ(Size(sz.width * params.mul_width, sz.height * params.mul_height), img.size());
if (params.pixel_format == V4L2_PIX_FMT_SRGGB8 ||
params.pixel_format == V4L2_PIX_FMT_SBGGR8 ||
params.pixel_format == V4L2_PIX_FMT_SGBRG8 ||
params.pixel_format == V4L2_PIX_FMT_SGRBG8)
{
EXPECT_EQ((size_t)esz.area(), img.total());
}
else
{
EXPECT_EQ(esz, img.size());
}
EXPECT_EQ(params.channels, img.channels());
EXPECT_EQ(params.depth, img.depth());
}
@ -102,6 +115,13 @@ TEST_P(videoio_v4l2, formats)
EXPECT_EQ(sz, img.size());
EXPECT_EQ(3, img.channels());
EXPECT_EQ(CV_8U, img.depth());
#ifdef DUMP_CAMERA_FRAME
std::string img_name = "frame_" + fourccToString(params.pixel_format);
// V4L2 flag for big-endian formats
if(params.pixel_format & (1 << 31))
img_name += "-BE";
cv::imwrite(img_name + ".png", img);
#endif
}
}
}
@ -116,9 +136,11 @@ vector<Format_Channels_Depth> all_params = {
// { V4L2_PIX_FMT_JPEG, 1, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_YUYV, 2, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_UYVY, 2, CV_8U, 1.f, 1.f },
// { V4L2_PIX_FMT_SBGGR8, 1, CV_8U, 1.f, 1.f },
// { V4L2_PIX_FMT_SN9C10X, 3, CV_8U, 1.f, 1.f },
// { V4L2_PIX_FMT_SGBRG8, 1, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_SN9C10X, 3, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_SRGGB8, 1, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_SBGGR8, 1, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_SGBRG8, 1, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_SGRBG8, 1, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_RGB24, 3, CV_8U, 1.f, 1.f },
{ V4L2_PIX_FMT_Y16, 1, CV_16U, 1.f, 1.f },
{ V4L2_PIX_FMT_Y16_BE, 1, CV_16U, 1.f, 1.f },

@ -62,7 +62,19 @@ yolov8n:
background_label_id: 0
sample: "yolo_detector"
yolov8m:
load_info:
url: "https://github.com/CVHub520/X-AnyLabeling/releases/download/v0.1.0/yolov8m.onnx"
sha1: "656ffeb4f3b067bc30df956728b5f9c61a4cb090"
model: "yolov8m.onnx"
mean: 0.0
scale: 0.00392
width: 640
height: 640
rgb: true
classes: "object_detection_classes_yolo.txt"
background_label_id: 0
sample: "yolo_detector"
# YOLO4 object detection family from Darknet (https://github.com/AlexeyAB/darknet)
# YOLO object detection family from Darknet (https://pjreddie.com/darknet/yolo/)

Loading…
Cancel
Save