1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-31 08:13:04 +04:00

Merge branch 4.x

This commit is contained in:
Alexander Smorkalov
2026-05-19 16:23:55 +03:00
160 changed files with 9385 additions and 2093 deletions
+7 -1
View File
@@ -697,7 +697,7 @@ __CV_ENUM_FLAGS_BITWISE_XOR_EQ (EnumType, EnumType)
#endif
/****************************************************************************************\
* Thread sanitizer *
* Sanitizers *
\****************************************************************************************/
#ifndef CV_THREAD_SANITIZER
# if defined(__has_feature)
@@ -707,6 +707,12 @@ __CV_ENUM_FLAGS_BITWISE_XOR_EQ (EnumType, EnumType)
# endif
#endif
#if defined(__clang__) || defined(__GNUC__)
#define CV_DISABLE_UBSAN __attribute__((no_sanitize("undefined")))
#else
#define CV_DISABLE_UBSAN
#endif
/****************************************************************************************\
* exchange-add operation for atomic operations on reference counters *
\****************************************************************************************/
@@ -361,6 +361,7 @@ CV_INLINE int cvRound( int value )
}
/** @overload */
CV_DISABLE_UBSAN
CV_INLINE int cvFloor( float value )
{
#if defined CV__FASTMATH_ENABLE_GCC_MATH_BUILTINS || \
+9 -2
View File
@@ -680,6 +680,13 @@ public:
double angle = 30, a = cos(angle*CV_PI/180), b = sin(angle*CV_PI/180);
Mat R = (Mat_<double>(2,2) << a, -b, b, a);
\endcode
\deprecated Use constructors with std::initializer_list instead:
\code
Mat_<int> m1({1, 2, 3, 4}); // 4x1 Mat
Mat_<uchar> m2({2, 3}, {1, 2, 3, 4, 5, 6}); // 2x3 Mat
Mat_<double> R({2, 2}, {a, -b, b, a}); // from example
*/
template<typename _Tp> class MatCommaInitializer_
{
@@ -1261,7 +1268,7 @@ public:
/** @overload
*/
template<typename _Tp> explicit Mat(const MatCommaInitializer_<_Tp>& commaInitializer);
template<typename _Tp> CV_DEPRECATED_EXTERNAL explicit Mat(const MatCommaInitializer_<_Tp>& commaInitializer);
//! download data from GpuMat
explicit Mat(const cuda::GpuMat& m);
@@ -2618,7 +2625,7 @@ public:
template<int m, int n> explicit Mat_(const Matx<typename DataType<_Tp>::channel_type, m, n>& mtx, bool copyData=true);
explicit Mat_(const Point_<typename DataType<_Tp>::channel_type>& pt, bool copyData=true);
explicit Mat_(const Point3_<typename DataType<_Tp>::channel_type>& pt, bool copyData=true);
explicit Mat_(const MatCommaInitializer_<_Tp>& commaInitializer);
CV_DEPRECATED_EXTERNAL explicit Mat_(const MatCommaInitializer_<_Tp>& commaInitializer);
Mat_(std::initializer_list<_Tp> values);
explicit Mat_(const std::initializer_list<int> sizes, const std::initializer_list<_Tp> values);
@@ -3207,7 +3207,7 @@ MatCommaInitializer_<_Tp>::operator Mat_<_Tp>() const
}
template<typename _Tp, typename T2> static inline
template<typename _Tp, typename T2> CV_DEPRECATED_EXTERNAL static inline
MatCommaInitializer_<_Tp> operator << (const Mat_<_Tp>& m, T2 val)
{
MatCommaInitializer_<_Tp> commaInitializer((Mat_<_Tp>*)&m);
@@ -115,7 +115,7 @@ public:
int idx;
};
template<typename _Tp, typename _T2, int m, int n> static inline
template<typename _Tp, typename _T2, int m, int n> CV_DEPRECATED_EXTERNAL static inline
MatxCommaInitializer<_Tp, m, n> operator << (const Matx<_Tp, m, n>& mtx, _T2 val)
{
MatxCommaInitializer<_Tp, m, n> commaInitializer((Matx<_Tp, m, n>*)&mtx);
@@ -738,7 +738,7 @@ public:
Vec<_Tp, m> operator *() const;
};
template<typename _Tp, typename _T2, int cn> static inline
template<typename _Tp, typename _T2, int cn> CV_DEPRECATED_EXTERNAL static inline
VecCommaInitializer<_Tp, cn> operator << (const Vec<_Tp, cn>& vec, _T2 val)
{
VecCommaInitializer<_Tp, cn> commaInitializer((Vec<_Tp, cn>*)&vec);
@@ -307,8 +307,12 @@ public:
before opening the file.
@param filename Name of the file to open or the text string to read the data from.
Extension of the file (.xml, .yml/.yaml or .json) determines its format (XML, YAML or JSON
respectively). Also you can append .gz to work with compressed files, for example myHugeMatrix.xml.gz. If both
FileStorage::WRITE and FileStorage::MEMORY flags are specified, source is used just to specify
respectively). Also you can append .gz to work with compressed files, for example myHugeMatrix.xml.gz.
You can also specify a compression level from 0 to 9 by appending it to the extension
(e.g. ".gz0" for no compression, ".gz9" for high compression).
The last digit will be truncated internally to write/read.
(e.g. If "a.xml.gz9" is specified, "a.xml.gz" is used for the actual file name.)
If both FileStorage::WRITE and FileStorage::MEMORY flags are specified, source is used just to specify
the output file format (e.g. mydata.xml, .yml etc.). A file name can also contain parameters.
You can use this format, "*?base64" (e.g. "file.json?base64" (case sensitive)), as an alternative to
FileStorage::BASE64 flag.
@@ -1245,6 +1245,26 @@ _Tp Point_<_Tp>::dot(const Point_& pt) const
return saturate_cast<_Tp>(x*pt.x + y*pt.y);
}
template<> inline
int Point_<int>::dot(const Point_<int>& pt) const
{
const int64_t xx = (int64_t)x * pt.x;
const int64_t yy = (int64_t)y * pt.y;
// detect int64 overflow before adding
if (yy > 0 && xx > INT64_MAX - yy)
{
return INT32_MAX;
}
if (yy < 0 && xx < INT64_MIN - yy)
{
return INT32_MIN;
}
const int64_t v = xx + yy;
return cv::saturate_cast<int>(v);
}
template<typename _Tp> inline
double Point_<_Tp>::ddot(const Point_& pt) const
{
@@ -1490,6 +1510,50 @@ _Tp Point3_<_Tp>::dot(const Point3_& pt) const
return saturate_cast<_Tp>(x*pt.x + y*pt.y + z*pt.z);
}
template<> inline
int Point3_<int>::dot(const Point3_<int>& pt) const
{
const int64_t xx = (int64_t)x * pt.x;
const int64_t yy = (int64_t)y * pt.y;
const int64_t zz = (int64_t)z * pt.z;
// Sort the three products so that lo <= mid <= hi.
// We add in the order (lo + hi) first, then add mid.
// This minimizes the absolute value of the intermediate sum,
// reducing the chance of int64 overflow during addition.
int64_t lo = xx, mid = yy, hi = zz;
if (lo > mid) std::swap(lo, mid);
if (mid > hi) std::swap(mid, hi);
if (lo > mid) std::swap(lo, mid);
// Step 1: lo + hi
// If lo + hi overflows int64, the final result must saturate to INT32_MAX/INT32_MIN.
// The middle value (mid) cannot bring the result back into the int32 range,
// so we can return immediately without considering mid.
if (hi > 0 && lo > INT64_MAX - hi)
{
return INT32_MAX;
}
if (hi < 0 && lo < INT64_MIN - hi)
{
return INT32_MIN;
}
int64_t sum = lo + hi;
// Step 2: sum + mid
if (mid > 0 && sum > INT64_MAX - mid)
{
return INT32_MAX;
}
if (mid < 0 && sum < INT64_MIN - mid)
{
return INT32_MIN;
}
sum += mid;
return cv::saturate_cast<int>(sum);
}
template<typename _Tp> inline
double Point3_<_Tp>::ddot(const Point3_& pt) const
{
+33 -1
View File
@@ -102,11 +102,18 @@ template<typename _Tp, size_t fixed_size = 1024/sizeof(_Tp)+8> class AutoBuffer
{
public:
typedef _Tp value_type;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef const _Tp* const_iterator;
typedef _Tp* iterator;
typedef const _Tp& const_reference;
typedef _Tp& reference;
//! the default constructor
AutoBuffer();
//! constructor taking the real buffer size
explicit AutoBuffer(size_t _size);
AutoBuffer(size_t _size, const _Tp& value);
//! the copy constructor
AutoBuffer(const AutoBuffer<_Tp, fixed_size>& buf);
@@ -140,7 +147,25 @@ public:
//! returns a reference to the element at specified location. No bounds checking is performed in Release builds.
inline const _Tp& operator[] (size_t i) const { CV_DbgCheckLT(i, sz, "out of range"); return ptr[i]; }
#endif
public:
inline iterator begin() { return data(); }
inline const_iterator begin() const { return data(); }
inline const_iterator cbegin() const { return begin(); }
inline iterator end() { return data()+size(); }
inline const_iterator end() const { return data()+size(); }
inline const_iterator cend() const { return end(); }
public:
inline bool empty() const { return !size(); }
inline void clear() {resize(0);}
inline const_reference front() const { return (*this)[0] ;}
inline reference front() { return (*this)[0] ;}
inline const_reference back() const { CV_DbgCheckGT(sz, (size_t)0, "out of range"); return (*this)[size()-1] ;}
inline reference back() { CV_DbgCheckGT(sz, (size_t)0, "out of range"); return (*this)[size()-1] ;}
public:
inline void push_back( const _Tp& value ) {resize(size()+1); back() = value;}
inline void push_back( _Tp&& value ) {resize(size()+1); back() = std::move(value);}
inline void emplace_back( _Tp&& value ) {push_back(value);}
inline void pop_back() {CV_DbgCheckGT(sz, (size_t)0, "out of range"); resize(size()-1);}
protected:
//! pointer to the real buffer, can point to buf if the buffer is small enough
_Tp* ptr;
@@ -1054,6 +1079,13 @@ AutoBuffer<_Tp, fixed_size>::AutoBuffer(size_t _size)
allocate(_size);
}
template<typename _Tp, size_t fixed_size> inline
AutoBuffer<_Tp, fixed_size>::AutoBuffer(size_t _size, const _Tp& value)
:AutoBuffer<_Tp, fixed_size>(_size)
{
std::fill(begin(), end(), value);
}
template<typename _Tp, size_t fixed_size> inline
AutoBuffer<_Tp, fixed_size>::AutoBuffer(const AutoBuffer<_Tp, fixed_size>& abuf )
{
+1 -1
View File
@@ -103,7 +103,7 @@ static bool ocl_math_op(InputArray _src1, InputArray _src2, OutputArray _dst, in
Fast cube root by Ken Turkowski
(http://www.worldserver.com/turk/computergraphics/papers.html)
\* ************************************************************************** */
float cubeRoot( float value )
CV_DISABLE_UBSAN float cubeRoot( float value )
{
CV_INSTRUMENT_REGION();
+537 -27
View File
@@ -1160,6 +1160,535 @@ struct NormDiffL2_SIMD<double, double> {
#endif
template <typename T, typename ST>
struct MaskedNormInf_SIMD {
inline ST operator() (const T* src, const uchar* mask, int len, int cn) const {
ST s = 0;
if (cn == 1) {
for (int i = 0; i < len; i++) {
if (mask[i]) {
s = std::max(s, (ST)cv_abs(src[i]));
}
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const T* elem = src + i * cn;
int k = 0;
#if CV_ENABLE_UNROLLED
for (; k <= cn - 4; k += 4) {
s = std::max(s, (ST)cv_abs(elem[k]));
s = std::max(s, (ST)cv_abs(elem[k + 1]));
s = std::max(s, (ST)cv_abs(elem[k + 2]));
s = std::max(s, (ST)cv_abs(elem[k + 3]));
}
#endif
for (; k < cn; k++) {
s = std::max(s, (ST)cv_abs(elem[k]));
}
}
}
}
return s;
}
};
template <typename T, typename ST>
struct MaskedNormL1_SIMD {
inline ST operator() (const T* src, const uchar* mask, int len, int cn) const {
ST s = 0;
if (cn == 1) {
for (int i = 0; i < len; i++) {
if (mask[i]) {
s += (ST)cv_abs(src[i]);
}
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const T* elem = src + i * cn;
int k = 0;
#if CV_ENABLE_UNROLLED
for (; k <= cn - 4; k += 4) {
s += (ST)cv_abs(elem[k]);
s += (ST)cv_abs(elem[k + 1]);
s += (ST)cv_abs(elem[k + 2]);
s += (ST)cv_abs(elem[k + 3]);
}
#endif
for (; k < cn; k++) {
s += (ST)cv_abs(elem[k]);
}
}
}
}
return s;
}
};
template <typename T, typename ST>
struct MaskedNormL2_SIMD {
inline ST operator() (const T* src, const uchar* mask, int len, int cn) const {
ST s = 0;
if (cn == 1) {
int i = 0;
#if CV_ENABLE_UNROLLED
for (; i <= len - 4; i += 4) {
if (mask[i]) { T v0 = src[i]; s += (ST)v0 * v0; }
if (mask[i + 1]) { T v1 = src[i + 1]; s += (ST)v1 * v1; }
if (mask[i + 2]) { T v2 = src[i + 2]; s += (ST)v2 * v2; }
if (mask[i + 3]) { T v3 = src[i + 3]; s += (ST)v3 * v3; }
}
#endif
for (; i < len; i++) {
if (mask[i]) {
T v = src[i];
s += (ST)v * v;
}
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const T* elem = src + i * cn;
int k = 0;
#if CV_ENABLE_UNROLLED
for (; k <= cn - 4; k += 4) {
T v0 = elem[k]; s += (ST)v0 * v0;
T v1 = elem[k + 1]; s += (ST)v1 * v1;
T v2 = elem[k + 2]; s += (ST)v2 * v2;
T v3 = elem[k + 3]; s += (ST)v3 * v3;
}
#endif
for (; k < cn; k++) {
T v = elem[k];
s += (ST)v * v;
}
}
}
}
return s;
}
};
template <>
struct MaskedNormInf_SIMD<float, float> {
inline float operator()(const float* src, const uchar* mask, int len, int cn) const {
float result = 0.0f;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_float32>::vlanes();
v_float32 acc = vx_setzero_f32();
for (; i <= len - vstep; i += vstep) {
v_uint32 m = v_reinterpret_as_u32(vx_load_expand(mask + i));
v_uint32 cmp = v_gt(m, vx_setzero_u32());
v_float32 s = vx_load(src + i);
s = v_abs(s);
s = v_reinterpret_as_f32(v_and(v_reinterpret_as_u32(s), cmp));
acc = v_max(acc, s);
}
result = v_reduce_max(acc);
for (; i < len; i++) {
if (mask[i])
result = std::max(result, std::abs(src[i]));
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const float* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_float32>::vlanes();
v_float32 acc = vx_setzero_f32();
for (; k <= cn - vstep; k += vstep) {
v_float32 s = vx_load(elem + k);
acc = v_max(acc, v_abs(s));
}
result = std::max(result, v_reduce_max(acc));
for (; k < cn; k++)
result = std::max(result, std::abs(elem[k]));
}
}
}
return result;
}
};
#if CV_SIMD_64F
template <>
struct MaskedNormL1_SIMD<float, double> {
inline double operator()(const float* src, const uchar* mask, int len, int cn) const {
double result = 0.0;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_float32>::vlanes();
v_float64 acc = vx_setzero_f64();
for (; i <= len - vstep; i += vstep) {
v_uint32 cmp = v_gt(vx_load_expand_q(mask + i), vx_setzero_u32());
v_float32 s = v_reinterpret_as_f32(v_and(v_reinterpret_as_u32(v_abs(vx_load(src + i))), cmp));
acc = v_add(acc, v_cvt_f64(s));
acc = v_add(acc, v_cvt_f64_high(s));
}
result = v_reduce_sum(acc);
for (; i < len; i++) {
if (mask[i])
result += std::abs(src[i]);
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const float* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_float32>::vlanes();
v_float64 acc = vx_setzero_f64();
for (; k <= cn - vstep; k += vstep) {
v_float32 s = v_abs(vx_load(elem + k));
acc = v_add(acc, v_cvt_f64(s));
acc = v_add(acc, v_cvt_f64_high(s));
}
result += v_reduce_sum(acc);
for (; k < cn; k++)
result += std::abs(elem[k]);
}
}
}
return result;
}
};
template <>
struct MaskedNormL2_SIMD<float, double> {
inline double operator()(const float* src, const uchar* mask, int len, int cn) const {
double result = 0.0;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_float32>::vlanes();
v_float32 facc = vx_setzero_f32();
v_float64 dacc = vx_setzero_f64();
int flush = 0;
for (; i <= len - vstep; i += vstep, flush += vstep) {
if (flush >= 64) {
dacc = v_add(dacc, v_cvt_f64(facc));
dacc = v_add(dacc, v_cvt_f64_high(facc));
facc = vx_setzero_f32();
flush = 0;
}
v_uint32 cmp = v_gt(vx_load_expand_q(mask + i), vx_setzero_u32());
v_float32 s = v_reinterpret_as_f32(v_and(v_reinterpret_as_u32(vx_load(src + i)), cmp));
facc = v_add(facc, v_mul(s, s));
}
dacc = v_add(dacc, v_cvt_f64(facc));
dacc = v_add(dacc, v_cvt_f64_high(facc));
result = v_reduce_sum(dacc);
for (; i < len; i++) {
if (mask[i]) {
double v = src[i];
result += v * v;
}
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const float* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_float32>::vlanes();
v_float32 facc = vx_setzero_f32();
for (; k <= cn - vstep; k += vstep) {
v_float32 s = vx_load(elem + k);
facc = v_add(facc, v_mul(s, s));
}
v_float64 dacc = v_add(v_cvt_f64(facc), v_cvt_f64_high(facc));
result += v_reduce_sum(dacc);
for (; k < cn; k++) {
double v = elem[k];
result += v * v;
}
}
}
}
return result;
}
};
#endif
template <>
struct MaskedNormInf_SIMD<uchar, int> {
inline int operator()(const uchar* src, const uchar* mask, int len, int cn) const {
int result = 0;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_uint8>::vlanes();
v_uint8 acc = vx_setzero_u8();
for (; i <= len - vstep; i += vstep) {
v_uint8 m = vx_load(mask + i);
v_uint8 s = vx_load(src + i);
v_uint8 sel = v_and(s, v_gt(m, vx_setzero_u8()));
acc = v_max(acc, sel);
}
result = (int)v_reduce_max(acc);
for (; i < len; i++) {
if (mask[i])
result = std::max(result, (int)src[i]);
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const uchar* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_uint8>::vlanes();
v_uint8 acc = vx_setzero_u8();
for (; k <= cn - vstep; k += vstep) {
acc = v_max(acc, vx_load(elem + k));
}
result = std::max(result, (int)v_reduce_max(acc));
for (; k < cn; k++)
result = std::max(result, (int)elem[k]);
}
}
}
return result;
}
};
template <>
struct MaskedNormL1_SIMD<uchar, int> {
inline int operator()(const uchar* src, const uchar* mask, int len, int cn) const {
int result = 0;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_uint8>::vlanes() / 4;
v_uint32 acc = vx_setzero_u32();
for (; i <= len - vstep; i += vstep) {
v_uint32 m = vx_load_expand_q(mask + i);
v_uint32 s = vx_load_expand_q(src + i);
v_uint32 sel = v_and(s, v_gt(m, vx_setzero_u32()));
acc = v_add(acc, sel);
}
result = (int)v_reduce_sum(acc);
for (; i < len; i++) {
if (mask[i])
result += src[i];
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const uchar* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_uint8>::vlanes() / 4;
v_uint32 acc = vx_setzero_u32();
for (; k <= cn - vstep; k += vstep) {
v_uint32 s = vx_load_expand_q(elem + k);
acc = v_add(acc, s);
}
result += (int)v_reduce_sum(acc);
for (; k < cn; k++)
result += elem[k];
}
}
}
return result;
}
};
template <>
struct MaskedNormInf_SIMD<ushort, int> {
inline int operator()(const ushort* src, const uchar* mask, int len, int cn) const {
int result = 0;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_uint16>::vlanes();
v_uint16 acc = vx_setzero_u16();
for (; i <= len - vstep; i += vstep) {
v_uint16 m = vx_load_expand(mask + i);
v_uint16 cmp = v_gt(m, vx_setzero_u16());
v_uint16 s = vx_load(src + i);
v_uint16 sel = v_and(s, cmp);
acc = v_max(acc, sel);
}
result = (int)v_reduce_max(acc);
for (; i < len; i++) {
if (mask[i])
result = std::max(result, (int)src[i]);
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const ushort* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_uint16>::vlanes();
v_uint16 acc = vx_setzero_u16();
for (; k <= cn - vstep; k += vstep) {
acc = v_max(acc, vx_load(elem + k));
}
result = std::max(result, (int)v_reduce_max(acc));
for (; k < cn; k++)
result = std::max(result, (int)elem[k]);
}
}
}
return result;
}
};
template <>
struct MaskedNormL1_SIMD<ushort, int> {
inline int operator()(const ushort* src, const uchar* mask, int len, int cn) const {
int result = 0;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_uint16>::vlanes();
v_uint32 acc32 = vx_setzero_u32();
v_uint64 acc64 = vx_setzero_u64();
int acc32_elems = 0;
for (; i <= len - vstep; i += vstep, acc32_elems += vstep) {
if (acc32_elems >= 512) {
v_uint64 lo64, hi64;
v_expand(acc32, lo64, hi64);
acc64 = v_add(acc64, v_add(lo64, hi64));
acc32 = vx_setzero_u32();
acc32_elems = 0;
}
v_uint16 m = vx_load_expand(mask + i);
v_uint16 cmp = v_gt(m, vx_setzero_u16());
v_uint16 s = v_and(vx_load(src + i), cmp);
v_uint32 lo32, hi32;
v_expand(s, lo32, hi32);
acc32 = v_add(acc32, v_add(lo32, hi32));
}
v_uint64 lo64, hi64;
v_expand(acc32, lo64, hi64);
acc64 = v_add(acc64, v_add(lo64, hi64));
result = (int)v_reduce_sum(acc64);
for (; i < len; i++) {
if (mask[i])
result += src[i];
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const ushort* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_uint16>::vlanes();
v_uint32 acc = vx_setzero_u32();
for (; k <= cn - vstep; k += vstep) {
v_uint32 lo32, hi32;
v_expand(vx_load(elem + k), lo32, hi32);
acc = v_add(acc, v_add(lo32, hi32));
}
result += (int)v_reduce_sum(acc);
for (; k < cn; k++)
result += elem[k];
}
}
}
return result;
}
};
template <>
struct MaskedNormL2_SIMD<ushort, double> {
inline double operator()(const ushort* src, const uchar* mask, int len, int cn) const {
double result = 0.0;
if (cn == 1) {
int i = 0;
const int vstep = VTraits<v_uint16>::vlanes();
v_uint64 acc = vx_setzero_u64();
for (; i <= len - vstep; i += vstep) {
v_uint16 m = vx_load_expand(mask + i);
v_uint16 cmp = v_gt(m, vx_setzero_u16());
v_uint16 s = v_and(vx_load(src + i), cmp);
v_uint32 lo32, hi32;
v_expand(s, lo32, hi32);
v_uint64 lo64a, lo64b, hi64a, hi64b;
v_expand(v_mul(lo32, lo32), lo64a, lo64b);
v_expand(v_mul(hi32, hi32), hi64a, hi64b);
acc = v_add(acc, v_add(v_add(lo64a, lo64b), v_add(hi64a, hi64b)));
}
result = (double)v_reduce_sum(acc);
for (; i < len; i++) {
if (mask[i]) {
double v = src[i];
result += v * v;
}
}
}
else {
for (int i = 0; i < len; i++) {
if (mask[i]) {
const ushort* elem = src + i * cn;
int k = 0;
const int vstep = VTraits<v_uint16>::vlanes();
v_uint64 acc = vx_setzero_u64();
for (; k <= cn - vstep; k += vstep) {
v_uint32 lo32, hi32;
v_expand(vx_load(elem + k), lo32, hi32);
v_uint64 lo64a, lo64b, hi64a, hi64b;
v_expand(v_mul(lo32, lo32), lo64a, lo64b);
v_expand(v_mul(hi32, hi32), hi64a, hi64b);
acc = v_add(acc, v_add(v_add(lo64a, lo64b), v_add(hi64a, hi64b)));
}
result += (double)v_reduce_sum(acc);
for (; k < cn; k++) {
double v = elem[k];
result += v * v;
}
}
}
}
return result;
}
};
template<typename T, typename ST> int
normInf_(const T* src, const uchar* mask, ST* _result, int len, int cn)
{
@@ -1168,15 +1697,9 @@ normInf_(const T* src, const uchar* mask, ST* _result, int len, int cn)
{
NormInf_SIMD<T, ST> op;
result = std::max(result, op(src, len*cn));
}
else
{
for( int i = 0; i < len; i++, src += cn )
if( mask[i] )
{
for( int k = 0; k < cn; k++ )
result = std::max(result, (ST)cv_abs(src[k]));
}
} else {
MaskedNormInf_SIMD<T, ST> op;
result = std::max(result, op(src, mask, len, cn));
}
*_result = result;
return 0;
@@ -1190,15 +1713,9 @@ normL1_(const T* src, const uchar* mask, ST* _result, int len, int cn)
{
NormL1_SIMD<T, ST> op;
result += op(src, len*cn);
}
else
{
for( int i = 0; i < len; i++, src += cn )
if( mask[i] )
{
for( int k = 0; k < cn; k++ )
result += cv_abs(src[k]);
}
} else {
MaskedNormL1_SIMD<T, ST> op;
result += op(src, mask, len, cn);
}
*_result = result;
return 0;
@@ -1215,15 +1732,8 @@ normL2_(const T* src, const uchar* mask, ST* _result, int len, int cn)
}
else
{
for( int i = 0; i < len; i++, src += cn )
if( mask[i] )
{
for( int k = 0; k < cn; k++ )
{
ST v = (ST)src[k];
result += v*v;
}
}
MaskedNormL2_SIMD<T, ST> op;
result += op(src, mask, len, cn);
}
*_result = result;
return 0;
+44
View File
@@ -98,6 +98,50 @@ __kernel void copyToMask(__global const uchar * srcptr, int src_step, int src_of
}
}
#elif defined(SET_DIAG)
#ifdef cl_khr_fp64
#pragma OPENCL EXTENSION cl_khr_fp64 : enable
#endif
__kernel void setDiag(__global uchar* dst, int dst_step, int dst_offset,
int dst_rows, int dst_cols,
__global const uchar* src, int src_step, int src_offset,
int len)
{
int x = get_global_id(0);
int y = get_global_id(1);
if (x < dst_cols && y < dst_rows)
{
int elem_size = (int)sizeof(T1) * cn;
int dst_idx = mad24(y, dst_step, mad24(x, elem_size, dst_offset));
if (x == y && x < len)
{
#if IS_ROW_VECTOR
int src_idx = src_offset + x * elem_size;
#else
int src_idx = mad24(x, src_step, src_offset);
#endif
__global T1* dst_ptr = (__global T1*)(dst + dst_idx);
__global const T1* src_ptr = (__global const T1*)(src + src_idx);
#pragma unroll
for (int c = 0; c < cn; ++c)
dst_ptr[c] = src_ptr[c];
}
else
{
__global T1* dst_ptr = (__global T1*)(dst + dst_idx);
#pragma unroll
for (int c = 0; c < cn; ++c)
dst_ptr[c] = (T1)0;
}
}
}
#else
#ifndef dstST
+24 -10
View File
@@ -529,7 +529,6 @@ bool FileStorage::Impl::open(const char *filename_or_buf, int _flags, const char
bool write_base64 = (write_mode || append) && (_flags & FileStorage::BASE64) != 0;
bool isGZ = false;
size_t fnamelen = 0;
std::vector<std::string> params;
//if ( !mem_mode )
@@ -552,18 +551,30 @@ bool FileStorage::Impl::open(const char *filename_or_buf, int _flags, const char
flags = _flags;
if (!mem_mode) {
char *dot_pos = strrchr((char *) filename.c_str(), '.');
size_t dot_idx = filename.find_last_of('.');
char compression = '\0';
if (dot_pos && dot_pos[1] == 'g' && dot_pos[2] == 'z' &&
(dot_pos[3] == '\0' || (cv_isdigit(dot_pos[3]) && dot_pos[4] == '\0'))) {
if (append) {
CV_Error(cv::Error::StsNotImplemented, "Appending data to compressed file is not implemented");
if (dot_idx != std::string::npos)
{
std::string ext = filename.substr(dot_idx);
// Instead of `ext.starts_with(".gz")
if (ext.size() >= 3 && (ext[1] == 'g') && (ext[2] == 'z') )
{
if (ext.size() == 3)
{
// ".gz"
isGZ = true;
}
else if(ext.size() == 4 && cv_isdigit(ext[3]))
{
// ".gz[0-9]"
isGZ = true;
compression = ext[3];
filename.pop_back(); // Replace `gz[0-9]' to 'gz'.
}
}
isGZ = true;
compression = dot_pos[3];
if (compression)
dot_pos[3] = '\0', fnamelen--;
}
if (!isGZ) {
@@ -575,6 +586,9 @@ bool FileStorage::Impl::open(const char *filename_or_buf, int _flags, const char
}
} else {
#if USE_ZLIB
if (append) {
CV_Error(cv::Error::StsNotImplemented, "Appending data to compressed file is not implemented");
}
char mode[] = {write_mode ? 'w' : 'r', 'b', compression ? compression : '3', '\0'};
gzfile = gzopen(filename.c_str(), mode);
if (!gzfile)
+45 -1
View File
@@ -1029,10 +1029,54 @@ UMat UMat::reshape(int new_cn, int new_rows) const
return hdr;
}
#ifdef HAVE_OPENCL
namespace {
static bool ocl_setDiag(const UMat& d, UMat& m, int len)
{
int cn = d.channels();
int depth = d.depth();
if (depth == CV_64F && !ocl::Device::getDefault().doubleFPConfig())
return false;
String opts = format("-D SET_DIAG -D T1=%s -D cn=%d -D IS_ROW_VECTOR=%d",
ocl::memopTypeToStr(depth),
cn,
(d.rows == 1) ? 1 : 0);
ocl::Kernel k("setDiag", ocl::core::copyset_oclsrc, opts);
if (k.empty())
return false;
k.args(ocl::KernelArg::WriteOnly(m),
ocl::KernelArg::ReadOnlyNoSize(d),
len);
size_t globalsize[2] = { (size_t)len, (size_t)len };
return k.run(2, globalsize, NULL, false);
}
}
#endif
UMat UMat::diag(const UMat& d, UMatUsageFlags usageFlags)
{
CV_Assert( d.cols == 1 || d.rows == 1 );
CV_INSTRUMENT_REGION();
CV_Assert(d.cols == 1 || d.rows == 1);
int len = d.rows + d.cols - 1;
#ifdef HAVE_OPENCL
if (ocl::useOpenCL())
{
UMat m(len, len, d.type(), usageFlags);
if (ocl_setDiag(d, m, len))
{
CV_IMPL_ADD(CV_IMPL_OCL);
return m;
}
}
#endif
UMat m(len, len, d.type(), Scalar(0), usageFlags);
UMat md = m.diag();
if( d.cols == 1 )
+8
View File
@@ -2995,6 +2995,14 @@ TEST(Core_Norm, NORM_L2_8UC4)
EXPECT_EQ(kNorm, cv::norm(a, b, NORM_L2));
}
TEST(Core_Norm, NORM_L2SQR_16SC4_large)
{
const int sizes[] = {1, 116, 40};
Mat src(3, sizes, CV_16SC4, Scalar::all(16384));
const double expected = static_cast<double>(src.total()) * src.channels() * 16384.0 * 16384.0;
EXPECT_EQ(expected, cv::norm(src, NORM_L2SQR));
}
TEST(Core_ConvertTo, regression_12121)
{
{
+31 -1
View File
@@ -680,13 +680,26 @@ public:
protected:
void run_func();
void prepare_to_validation( int test_case_idx );
double get_success_error_level( int test_case_idx, int i, int j );
};
CxCore_DFTTest::CxCore_DFTTest() : CxCore_DXTBaseTest( true, true, false )
{
}
double CxCore_DFTTest::get_success_error_level( int test_case_idx, int i, int j )
{
CV_Assert(i == OUTPUT);
CV_Assert(j == 0);
int depth = test_mat[i][j].depth();
// NOTE: non-default threshold intorduced for ARMPL integration
if (depth == CV_32F)
return 1.5e-4;
return CxCore_DXTBaseTest::get_success_error_level(test_case_idx, i, j);
}
void CxCore_DFTTest::run_func()
{
@@ -745,6 +758,9 @@ public:
protected:
void run_func();
void prepare_to_validation( int test_case_idx );
#if defined(HAVE_ARMPL)
double get_success_error_level( int test_case_idx, int i, int j ) CV_OVERRIDE;
#endif
};
@@ -752,6 +768,20 @@ CxCore_DCTTest::CxCore_DCTTest() : CxCore_DXTBaseTest( false, false, false )
{
}
#if defined(HAVE_ARMPL)
double CxCore_DCTTest::get_success_error_level(int, int i, int j)
{
CV_Assert(i == OUTPUT);
CV_Assert(j == 0);
int depth = test_mat[i][j].depth();
if (depth == CV_32F)
return 1.67e-5;
return 1e-12;
}
#endif
void CxCore_DCTTest::run_func()
{
+10 -10
View File
@@ -292,17 +292,17 @@ template<typename R> struct TheTest
}
// reinterpret_as
v_uint8 vu8 = v_reinterpret_as_u8(r1); out.a.clear(); v_store((uchar*)out.a.d, vu8); EXPECT_EQ(data.a, out.a);
v_int8 vs8 = v_reinterpret_as_s8(r1); out.a.clear(); v_store((schar*)out.a.d, vs8); EXPECT_EQ(data.a, out.a);
v_uint16 vu16 = v_reinterpret_as_u16(r1); out.a.clear(); v_store((ushort*)out.a.d, vu16); EXPECT_EQ(data.a, out.a);
v_int16 vs16 = v_reinterpret_as_s16(r1); out.a.clear(); v_store((short*)out.a.d, vs16); EXPECT_EQ(data.a, out.a);
v_uint32 vu32 = v_reinterpret_as_u32(r1); out.a.clear(); v_store((unsigned*)out.a.d, vu32); EXPECT_EQ(data.a, out.a);
v_int32 vs32 = v_reinterpret_as_s32(r1); out.a.clear(); v_store((int*)out.a.d, vs32); EXPECT_EQ(data.a, out.a);
v_uint64 vu64 = v_reinterpret_as_u64(r1); out.a.clear(); v_store((uint64*)out.a.d, vu64); EXPECT_EQ(data.a, out.a);
v_int64 vs64 = v_reinterpret_as_s64(r1); out.a.clear(); v_store((int64*)out.a.d, vs64); EXPECT_EQ(data.a, out.a);
v_float32 vf32 = v_reinterpret_as_f32(r1); out.a.clear(); v_store((float*)out.a.d, vf32); EXPECT_EQ(data.a, out.a);
AlignedData<R> out_u8; v_uint8 vu8 = v_reinterpret_as_u8(r1); v_store((uchar*)out_u8.a.d, vu8); EXPECT_EQ(data.a, out_u8.a);
AlignedData<R> out_s8; v_int8 vs8 = v_reinterpret_as_s8(r1); v_store((schar*)out_s8.a.d, vs8); EXPECT_EQ(data.a, out_s8.a);
AlignedData<R> out_u16; v_uint16 vu16 = v_reinterpret_as_u16(r1); v_store((ushort*)out_u16.a.d, vu16); EXPECT_EQ(data.a, out_u16.a);
AlignedData<R> out_s16; v_int16 vs16 = v_reinterpret_as_s16(r1); v_store((short*)out_s16.a.d, vs16); EXPECT_EQ(data.a, out_s16.a);
AlignedData<R> out_u32; v_uint32 vu32 = v_reinterpret_as_u32(r1); v_store((unsigned*)out_u32.a.d, vu32); EXPECT_EQ(data.a, out_u32.a);
AlignedData<R> out_s32; v_int32 vs32 = v_reinterpret_as_s32(r1); v_store((int*)out_s32.a.d, vs32); EXPECT_EQ(data.a, out_s32.a);
AlignedData<R> out_u64; v_uint64 vu64 = v_reinterpret_as_u64(r1); v_store((uint64*)out_u64.a.d, vu64); EXPECT_EQ(data.a, out_u64.a);
AlignedData<R> out_s64; v_int64 vs64 = v_reinterpret_as_s64(r1); v_store((int64*)out_s64.a.d, vs64); EXPECT_EQ(data.a, out_s64.a);
AlignedData<R> out_f32; v_float32 vf32 = v_reinterpret_as_f32(r1); v_store((float*)out_f32.a.d, vf32); EXPECT_EQ(data.a, out_f32.a);
#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F)
v_float64 vf64 = v_reinterpret_as_f64(r1); out.a.clear(); v_store((double*)out.a.d, vf64); EXPECT_EQ(data.a, out.a);
AlignedData<R> out_f64; v_float64 vf64 = v_reinterpret_as_f64(r1); v_store((double*)out_f64.a.d, vf64); EXPECT_EQ(data.a, out_f64.a);
#endif
#if CV_SIMD_WIDTH == 16
+12 -2
View File
@@ -2218,9 +2218,19 @@ T fsWriteRead(const T& expectedValue, const char* ext)
fs_w.release();
FileStorage fs_r(fname, FileStorage::READ);
T value;
fs_r["value"] >> value;
fs_r.release();
// If ext is `.gz[0-9]`, fname on storage will end with `.gz`.
// FileStorage::Impl::open() truncates the last digit internally.
if (isdigit(fname.back()))
{
fname.pop_back();
}
remove(fname.c_str());
return value;
}
@@ -2275,7 +2285,7 @@ TEST_P(FileStorage_exact_type, long_int_mat)
}
INSTANTIATE_TEST_CASE_P(Core_InputOutput,
FileStorage_exact_type, Values(".yml", ".xml", ".json")
FileStorage_exact_type, Values(".yml", ".xml", ".json", ".xml.gz", ".xml.gz0", ".xml.gz9")
);
TEST(Core_InputOutput, YAML_Compatibility)
+12
View File
@@ -1370,6 +1370,8 @@ TEST(Core_Matx, from_initializer_list)
Mat_<double> a = (Mat_<double>(2,2) << 10, 11, 12, 13);
Matx22d b = {10, 11, 12, 13};
ASSERT_EQ( cvtest::norm(a, b, NORM_INF), 0.);
Mat_<double> c({2, 2}, {10, 11, 12, 13});
ASSERT_EQ( cvtest::norm(c, b, NORM_INF), 0.);
}
TEST(Core_Mat, regression_9507)
@@ -1825,6 +1827,11 @@ TEST(Mat, from_initializer_list)
auto D = Mat_<double>({2, 3}, {1, 2, 3, 4, 5, 6});
EXPECT_EQ(2, D.rows);
EXPECT_EQ(3, D.cols);
double angle = 30, a = cos(angle*CV_PI/180), b = sin(angle*CV_PI/180);
Mat R({2, 2}, {a, -b, b, a});
ASSERT_EQ(CV_64FC1, R.type());
ASSERT_EQ(cv::Size(2, 2), R.size());
}
TEST(Mat_, from_initializer_list)
@@ -1837,6 +1844,11 @@ TEST(Mat_, from_initializer_list)
ASSERT_DOUBLE_EQ(cvtest::norm(A, B, NORM_INF), 0.);
ASSERT_DOUBLE_EQ(cvtest::norm(A, C, NORM_INF), 0.);
ASSERT_DOUBLE_EQ(cvtest::norm(B, C, NORM_INF), 0.);
double angle = 30, a = cos(angle*CV_PI/180), b = sin(angle*CV_PI/180);
Mat_<double> R({2, 2}, {a, -b, b, a});
ASSERT_EQ(CV_64FC1, R.type());
ASSERT_EQ(cv::Size(2, 2), R.size());
}
+172
View File
@@ -3617,5 +3617,177 @@ TEST(Core_BFloat, convert)
#endif
}
////////////////////////////////////////////////////////////////////////////
// See https://github.com/opencv/opencv/issues/28930
typedef testing::TestWithParam<std::tuple<int,int,int,int, int>> Core_Point_DotProduct_regression28930;
TEST_P(Core_Point_DotProduct_regression28930, basic)
{
const int x1 = std::get<0>(GetParam());
const int y1 = std::get<1>(GetParam());
const int x2 = std::get<2>(GetParam());
const int y2 = std::get<3>(GetParam());
const int expect = std::get<4>(GetParam());
cv::Point pt1(x1, y1);
cv::Point pt2(x2, y2);
EXPECT_EQ(pt1.dot(pt2), expect) << "Failed for: (" << x1 << "," << y1 << ") dot (" << x2 << "," << y2 << ")";
}
INSTANTIATE_TEST_CASE_P(/* */, Core_Point_DotProduct_regression28930,
testing::Values(
// 1. INT_MIN*INT_MIN + INT_MIN*INT_MIN = 2^62 + 2^62 => Saturates to MAX
std::make_tuple( INT_MIN, INT_MIN,
INT_MIN, INT_MIN,
INT_MAX),
// 2. INT_MIN*INT_MAX + INT_MAX*INT_MAX = 4611686014132420609 + -4611686016279904256 = -2147483647
std::make_tuple( INT_MAX, INT_MIN,
INT_MAX, INT_MAX,
-2147483647),
// 3. INT_MAX*INT_MAX + INT_MAX*INT_MIN = 4611686014132420609 + -4611686016279904256 = -2147483647
std::make_tuple( INT_MAX, INT_MAX,
INT_MAX, INT_MIN,
-2147483647),
// 4. 46340^2 = 2147395600 (Under INT_MAX)
std::make_tuple( 46340, 0,
46340, 0,
2147395600),
// 5. 46341^2 = 2147488281 (Over INT_MAX) => Saturates to MAX
std::make_tuple( 46341, 0,
46341, 0,
INT_MAX),
// 6. -46340 * 46340 = -2147395600 (Over INT_MIN)
std::make_tuple( -46340, 0,
46340, 0,
-2147395600),
// 7. -46341 * 46341 = -2147488281 (Under INT_MIN) => Saturates to MIN
std::make_tuple( -46341, 0,
46341, 0,
INT_MIN),
// 8. Zero
std::make_tuple( 0, 0,
0, 0,
0),
// 9. Simple max
std::make_tuple( INT_MAX, 0,
1, 0,
INT_MAX)
));
typedef testing::TestWithParam<std::tuple<int,int,int,int,int,int, int>> Core_Point3i_DotProduct_regression28930;
TEST_P(Core_Point3i_DotProduct_regression28930, basic)
{
const int x1 = std::get<0>(GetParam());
const int y1 = std::get<1>(GetParam());
const int z1 = std::get<2>(GetParam());
const int x2 = std::get<3>(GetParam());
const int y2 = std::get<4>(GetParam());
const int z2 = std::get<5>(GetParam());
const int expect = std::get<6>(GetParam());
cv::Point3i pt1(x1, y1, z1);
cv::Point3i pt2(x2, y2, z2);
EXPECT_EQ(pt1.dot(pt2), expect) << "Failed for: (" << x1 << "," << y1 << "," << z1 << ") dot (" << x2 << "," << y2 << "," << z2 << ")";
}
INSTANTIATE_TEST_CASE_P(/* */, Core_Point3i_DotProduct_regression28930,
testing::Values(
// 1. INT_MIN*INT_MIN + INT_MIN*INT_MIN = 2^62 + 2^62 => Saturates to MAX
std::make_tuple( INT_MIN, INT_MIN, 0,
INT_MIN, INT_MIN, 0,
INT_MAX),
// 2. INT_MIN*INT_MAX + INT_MAX*INT_MAX = 4611686014132420609 + -4611686016279904256 = -2147483647
std::make_tuple( INT_MAX, INT_MIN, 0,
INT_MAX, INT_MAX, 0,
-2147483647),
// 3. INT_MAX*INT_MAX + INT_MAX*INT_MIN = 4611686014132420609 + -4611686016279904256 = -2147483647
std::make_tuple( INT_MAX, INT_MAX, 0,
INT_MAX, INT_MIN, 0,
-2147483647),
// 4. 46340^2 = 2147395600 (Under INT_MAX)
std::make_tuple( 46340, 0, 0,
46340, 0, 0,
2147395600),
// 5. 46341^2 = 2147488281 (Over INT_MAX) => Saturates to MAX
std::make_tuple( 46341, 0, 0,
46341, 0, 0,
INT_MAX),
// 6. -46340 * 46340 = -2147395600 (Over INT_MIN)
std::make_tuple( -46340, 0, 0,
46340, 0, 0,
-2147395600),
// 7. -46341 * 46341 = -2147488281 (Under INT_MIN) => Saturates to MIN
std::make_tuple( -46341, 0, 0,
46341, 0, 0,
INT_MIN),
// 8. Zero
std::make_tuple( 0, 0, 0,
0, 0, 0,
0),
// 9. Simple max
std::make_tuple( INT_MAX, 0, 0,
1, 0, 0,
INT_MAX),
// 10. All positive, no overflow
std::make_tuple( 1, 2, 3,
4, 5, 6,
(1*4 + 2*5 + 3*6)),
// 11. All negative, no overflow
std::make_tuple( -1, -2, -3,
-4, -5, -6,
(-1*-4 + -2*-5 + -3*-6)),
// 12. Three large positive products => saturate to MAX
std::make_tuple( INT_MAX, INT_MAX, INT_MAX,
INT_MAX, INT_MAX, INT_MAX,
INT_MAX),
// 13. Three large positive products from INT_MIN*INT_MIN => saturate to MAX
std::make_tuple( INT_MIN, INT_MIN, INT_MIN,
INT_MIN, INT_MIN, INT_MIN,
INT_MAX),
// 14. Three large negative products => saturate to MIN
std::make_tuple( INT_MIN, INT_MIN, INT_MIN,
INT_MAX, INT_MAX, INT_MAX,
INT_MIN),
// 15. Mixed signs: + + -
std::make_tuple( INT_MAX, INT_MAX, INT_MIN,
INT_MAX, INT_MAX, INT_MAX,
INT_MAX),
// 16. Mixed signs: + - +
std::make_tuple( INT_MAX, INT_MIN, INT_MAX,
INT_MAX, INT_MAX, INT_MAX,
INT_MAX),
// 17. Mixed signs: - + -
std::make_tuple( INT_MIN, INT_MAX, INT_MIN,
INT_MAX, INT_MAX, INT_MAX,
INT_MIN)
));
}} // namespace
/* End of file. */
@@ -302,9 +302,16 @@ public:
ov::Shape(pads_begin), ov::Shape(pads_end), ov::Shape(kernel_size),
rounding_type, pad_type);
} else if (type == AVE) {
#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_GE(2025030000)
std::vector<size_t> dilations(kernel_size.size(), 1);
pool = std::make_shared<ov::op::v16::AvgPool>(input, ov::Strides(strides), ov::Strides(dilations),
ov::Shape(pads_begin), ov::Shape(pads_end), ov::Shape(kernel_size),
!avePoolPaddedArea, rounding_type, pad_type);
#else
pool = std::make_shared<ov::op::v1::AvgPool>(input, ov::Strides(strides),
ov::Shape(pads_begin), ov::Shape(pads_end), ov::Shape(kernel_size),
!avePoolPaddedArea, rounding_type, pad_type);
#endif
} else if (type == SUM) {
ov::Shape inpShape = input.get_shape();
CV_Assert(inpShape.size() == 2 + kernel_size.size());
@@ -1510,9 +1510,15 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
char *wptr = weights + (k0_block * DkHkWkCg + c0 * CONV_MR) * esz;
float *cptr = cbuf_task + stripe * CONV_NR;
#ifdef CONV_ARM_FP16
hfloat* cptr_f16 = (hfloat*)cbuf_task + stripe*CONV_NR;
#endif // CONV_ARM_FP16
for (int k = k0_block; k < k1_block; k += CONV_MR,
wptr += DkHkWkCg * CONV_MR * esz, cptr += CONV_MR * ldc, cptr_f16 += CONV_MR * ldc)
wptr += DkHkWkCg * CONV_MR * esz,
#ifdef CONV_ARM_FP16
cptr_f16 += CONV_MR * ldc,
#endif // CONV_ARM_FP16
cptr += CONV_MR * ldc)
{
#if CV_TRY_AVX2
if (conv->useAVX2)
@@ -1546,12 +1552,18 @@ void runFastConv(InputArray _input, OutputArray _output, const Ptr<FastConv>& co
size_t outofs = ((n * ngroups + g) * Kg + k0_block) * out_planesize + zyx0;
const float *cptr = cbuf_task;
#ifdef CONV_ARM_FP16
const hfloat *cptr_fp16 = (const hfloat *)cbuf_task;
#endif // CONV_ARM_FP16
float *outptr = out + outofs;
const float *pbptr = fusedAddPtr0 ? fusedAddPtr0 + outofs : 0;
for (int k = k0_block; k < k1_block; k++,
cptr += ldc, cptr_fp16 += ldc, outptr += out_planesize,
cptr += ldc,
#ifdef CONV_ARM_FP16
cptr_fp16 += ldc,
#endif // CONV_ARM_FP16
outptr += out_planesize,
pbptr += (pbptr ? out_planesize : 0))
{
float biasval = biasptr[k];
+4 -1
View File
@@ -212,7 +212,10 @@ TEST_P(Test_Int8_layers, AvePooling)
if (backend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
testLayer("layer_pooling_ave", "Caffe", 0.0021, 0.0075);
testLayer("ave_pool_same", "TensorFlow", 0.00153, 0.0041);
testLayer("average_pooling_1d", "ONNX", 0.002, 0.0048);
#if defined(INF_ENGINE_RELEASE) && INF_ENGINE_VER_MAJOR_LT(2025030000)
if (backend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
#endif
testLayer("average_pooling_1d", "ONNX", 0.002, 0.0048);
if (backend != DNN_BACKEND_INFERENCE_ENGINE_NGRAPH)
testLayer("average_pooling", "ONNX", 0.0014, 0.0032);
testLayer("average_pooling_dynamic_axes", "ONNX", 0.0014, 0.006);
+1 -1
View File
@@ -230,7 +230,7 @@ static void filterEllipticKeyPointsByImageSize( std::vector<EllipticKeyPoint>& k
std::vector<EllipticKeyPoint> filtered;
filtered.reserve(keypoints.size());
std::vector<EllipticKeyPoint>::const_iterator it = keypoints.begin();
for( int i = 0; it != keypoints.end(); ++it, i++ )
for( ; it != keypoints.end(); ++it )
{
if( it->center.x + it->boundingBox.width < imgSize.width &&
it->center.x - it->boundingBox.width > 0 &&
+29 -11
View File
@@ -663,33 +663,51 @@ int Index::radiusSearch(InputArray _query, OutputArray _indices,
if( algo == FLANN_INDEX_LSH )
CV_Error( Error::StsNotImplemented, "LSH index does not support radiusSearch operation" );
int rsearch_ret;
switch( distType )
{
case FLANN_DIST_HAMMING:
return runRadiusSearch< HammingDistance >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< HammingDistance >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_L2:
return runRadiusSearch< ::cvflann::L2<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::L2<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_L1:
return runRadiusSearch< ::cvflann::L1<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::L1<float> >(index, query, indices, dists, radius, params);
break;
#if MINIFLANN_SUPPORT_EXOTIC_DISTANCE_TYPES
case FLANN_DIST_DNAMMING:
return runRadiusSearch< DNAmmingDistance >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< DNAmmingDistance >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_MAX:
return runRadiusSearch< ::cvflann::MaxDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::MaxDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_HIST_INTERSECT:
return runRadiusSearch< ::cvflann::HistIntersectionDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::HistIntersectionDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_HELLINGER:
return runRadiusSearch< ::cvflann::HellingerDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::HellingerDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_CHI_SQUARE:
return runRadiusSearch< ::cvflann::ChiSquareDistance<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::ChiSquareDistance<float> >(index, query, indices, dists, radius, params);
break;
case FLANN_DIST_KL:
return runRadiusSearch< ::cvflann::KL_Divergence<float> >(index, query, indices, dists, radius, params);
rsearch_ret = runRadiusSearch< ::cvflann::KL_Divergence<float> >(index, query, indices, dists, radius, params);
break;
#endif
default:
CV_Error(Error::StsBadArg, "Unknown/unsupported distance type");
return -1;
}
return -1;
if (rsearch_ret > maxResults)
rsearch_ret = maxResults;
if (rsearch_ret < maxResults) {
if (_indices.needed())
indices.colRange(0, rsearch_ret).copyTo(_indices);
if (_dists.needed())
dists.colRange(0, rsearch_ret).copyTo(_dists);
}
return rsearch_ret;
}
flann_distance_t Index::getDistance() const
+81
View File
@@ -0,0 +1,81 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
namespace opencv_test { namespace {
// Regression test: radiusSearch returned nn < maxResults but left the output
// vectors sized to maxResults, so nn != indices.size() and nn > maxResults
// could both occur for queries with fewer actual neighbors than maxResults.
TEST(Flann_Index, radiusSearch_output_size_matches_returned_count)
{
std::vector<cv::Point2f> corners = {
{3679.83f,1857.22f},{3324.43f,1850.67f},{3278.83f,1502.32f},{3621.32f,1508.69f},
{3662.26f,1837.97f},{3336.06f,1839.28f},{3291.74f,1516.03f},{3608.94f,1518.72f},
{2980.66f,1843.22f},{2628.11f,1837.14f},{2607.73f,1491.57f},{2948.22f,1496.76f},
{2289.31f,1830.19f},{1943.99f,1824.17f},{1945.51f,1482.34f},{2280.68f,1487.39f},
{1607.47f,1819.11f},{1260.04f,1813.26f},{1283.94f,1470.34f},{1620.03f,1475.85f},
{923.063f,1808.08f},{576.193f,1802.4f},{624.713f,1459.78f},{959.715f,1464.93f},
{3270.25f,1494.98f},{2952.95f,1492.19f},{2922.75f,1190.02f},{3231.05f,1194.81f},
{3284.82f,1507.62f},{2942.74f,1502.59f},{2912.32f,1178.33f},{3242.98f,1183.19f},
{2612.82f,1496.37f},{2276.44f,1491.63f},{2267.61f,1170.13f},{2593.64f,1174.43f},
{1949.73f,1486.61f},{1614.92f,1480.64f},{1626.77f,1160.34f},{1952.0f,1165.51f},
{1289.38f,1476.21f},{953.709f,1470.21f},{986.779f,1148.92f},{1311.98f,1154.89f},
{3567.28f,1195.1f},{3237.51f,1189.03f},{3197.63f,884.15f},{3518.61f,888.76f},
{2918.31f,1183.62f},{2588.69f,1179.37f},{2570.64f,875.672f},{2889.87f,878.902f},
{2271.97f,1174.25f},{1947.62f,1169.61f},{1948.56f,868.197f},{2263.8f,872.533f},
{1630.99f,1164.6f},{1306.74f,1159.52f},{1327.81f,858.358f},{1642.69f,862.765f},
{992.896f,1155.52f},{666.107f,1149.96f},{705.246f,845.776f},{1023.25f,851.582f},
{3204.48f,889.981f},{2883.49f,885.252f},{2859.31f,593.752f},{3175.43f,594.736f},
{2575.86f,880.331f},{2259.44f,876.647f},{2252.33f,589.235f},{2560.47f,592.332f},
{1954.36f,873.708f},{1636.7f,868.068f},{1646.93f,580.76f},{1956.14f,584.797f},
{1332.02f,862.624f},{1017.11f,856.71f},{1045.32f,572.178f},{1350.95f,577.635f},
{3481.36f,605.324f},{3169.05f,601.083f},{3138.84f,324.116f},{3446.12f,325.545f},
{2866.14f,599.61f},{2555.38f,597.142f},{2541.51f,320.931f},{2845.5f,322.419f},
{2256.79f,593.253f},{1950.17f,590.12f},{1951.8f,317.445f},{2250.88f,319.418f},
{1654.16f,587.663f},{1344.83f,582.787f},{1362.8f,309.253f},{1663.89f,313.056f},
{1050.94f,577.867f},{741.476f,571.648f},{776.433f,300.444f},{1076.97f,304.56f},
{3327.05f,1847.65f},{2977.74f,1840.48f},{2945.48f,1499.68f},{3281.82f,1504.97f},
{2630.85f,1834.22f},{2287.2f,1828.06f},{2278.56f,1489.51f},{2610.64f,1494.31f},
{1946.11f,1822.05f},{1604.75f,1816.18f},{1617.11f,1478.59f},{1947.62f,1484.48f},
{1262.96f,1810.53f},{920.46f,1805.05f},{956.712f,1467.57f},{1286.66f,1473.27f},
{3618.7f,1511.71f},{3281.82f,1504.97f},{3240.24f,1186.11f},{3564.22f,1192.53f},
{2945.48f,1499.68f},{2610.64f,1494.31f},{2591.52f,1176.55f},{2915.32f,1180.97f},
{2278.56f,1489.51f},{1947.62f,1484.48f},{1949.81f,1167.56f},{2269.79f,1172.18f},
{1617.11f,1478.59f},{1286.66f,1473.27f},{1308.98f,1157.53f},{1628.88f,1162.47f},
{956.712f,1467.57f},{627.315f,1462.81f},{669.943f,1146.75f},{989.494f,1151.86f},
{3240.25f,1186.11f},{2915.32f,1180.98f},{2887.03f,881.727f},{3200.69f,886.725f},
{2591.52f,1176.55f},{2269.79f,1172.19f},{2261.62f,874.587f},{2573.63f,878.324f},
{1949.81f,1167.55f},{1628.88f,1162.47f},{1640.44f,864.751f},{1950.74f,870.259f},
{1308.98f,1157.53f},{989.494f,1151.86f},{1019.41f,854.787f},{1329.92f,860.49f},
{3515.88f,891.68f},{3200.69f,886.725f},{3171.89f,598.263f},{3478.26f,602.785f},
{2887.04f,881.725f},{2573.63f,878.324f},{2558.28f,594.392f},{2863.1f,597.005f},
{2261.62f,874.587f},{1950.74f,870.259f},{1952.4f,588.114f},{2254.56f,591.241f},
{1640.44f,864.751f},{1329.92f,860.49f},{1348.65f,579.559f},{1650.55f,584.209f},
{1019.41f,854.787f},{708.649f,849.44f},{745.388f,568.534f},{1047.42f,574.313f},
{3171.89f,598.263f},{2863.1f,597.005f},{2842.6f,325.169f},{3141.93f,326.654f},
{2558.28f,594.392f},{2254.56f,591.241f},{2248.65f,321.425f},{2544.55f,323.537f},
{1952.4f,588.113f},{1650.55f,584.209f},{1660.07f,316.284f},{1954.02f,319.457f},
{1348.65f,579.559f},{1047.43f,574.312f},{1073.06f,307.674f},{1366.42f,312.707f}
};
cv::flann::KDTreeIndexParams indexParams(1);
cv::Mat data = cv::Mat(corners).reshape(1, static_cast<int>(corners.size()));
cv::flann::Index index(data, indexParams);
const int maxResults = 4;
for (int i = 0; i < (int)corners.size(); i++)
{
SCOPED_TRACE(cv::format("Data row: %d", i));
std::vector<int> indices(maxResults);
std::vector<float> dists(maxResults);
int nn = index.radiusSearch(data.row(i), indices, dists, 100, maxResults);
EXPECT_EQ(nn, (int)indices.size());
EXPECT_LE(nn, maxResults);
}
}
}} // namespace
+7 -1
View File
@@ -3960,9 +3960,14 @@ CV_EXPORTS_W int connectedComponentsWithStats(InputArray image, OutputArray labe
/** @brief Finds contours in a binary image.
The function retrieves contours from the binary image using the algorithm @cite Suzuki85 . The contours
The function retrieves contours from the binary image. The contours
are a useful tool for shape analysis and object detection and recognition. See squares.cpp in the
OpenCV sample directory.
@note Since OpenCV 4.14, when mode is #RETR_LIST and no hierarchy is requested, this function
automatically uses the TRUCO parallel algorithm @cite TRUCO2026, a scalable lock-free method for
contour extraction. In all other cases, the sequential @cite Suzuki85 algorithm is used.
@note Since opencv 3.2 source image is not modified by this function.
@param image Source, an 8-bit single-channel image. Non-zero pixels are treated as 1's. Zero
@@ -3992,6 +3997,7 @@ CV_EXPORTS_W void findContours( InputArray image, OutputArrayOfArrays contours,
CV_EXPORTS void findContours( InputArray image, OutputArrayOfArrays contours,
int mode, int method, Point offset = Point());
//! @brief Find contours using link runs algorithm
//!
//! This function implements an algorithm different from cv::findContours:
+74
View File
@@ -141,4 +141,78 @@ PERF_TEST_P(TestMinEnclosingCircleWorstCase, minEnclosingCircle_sequential,
SANITY_CHECK_NOTHING();
}
// ============================================================
// findTRUContours performance tests
// ============================================================
typedef TestBaseWithParam< tuple<Size, int, int> > TestFindTRUContours;
PERF_TEST_P(TestFindTRUContours, findTRUContours,
Combine(
Values(sz1080p, sz2160p), // image size
Values(128, 512, 2048), // circle count
Values(1, 0) // nthreads: 1=single-thread baseline, 0=all available
)
)
{
Size img_size = get<0>(GetParam());
int num_circles = get<1>(GetParam());
int nthreads = get<2>(GetParam());
RNG rng(12345);
Mat img = Mat::zeros(img_size, CV_8UC1);
for (int i = 0; i < num_circles; ++i)
{
Point center(rng.uniform(50, img_size.width - 50),
rng.uniform(50, img_size.height - 50));
int radius = rng.uniform(10, 200);
circle(img, center, radius, Scalar::all(255), FILLED);
}
Mat binary;
adaptiveThreshold(img, binary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 0);
vector<vector<Point>> contours;
int prev_nthreads=cv::getNumThreads();
cv::setNumThreads(nthreads);
TEST_CYCLE() findContours(binary, contours, RETR_LIST, CHAIN_APPROX_NONE);
cv::setNumThreads(prev_nthreads);
SANITY_CHECK_NOTHING();
}
// Baseline: same image, findContours(RETR_LIST, CHAIN_APPROX_NONE) for direct comparison
typedef TestBaseWithParam< tuple<Size, int> > TestFindContoursBaseline;
PERF_TEST_P(TestFindContoursBaseline, findContours_baseline_for_TRUCO,
Combine(
Values(sz1080p, sz2160p),
Values(128, 512, 2048)
)
)
{
Size img_size = get<0>(GetParam());
int num_circles = get<1>(GetParam());
RNG rng(12345);
Mat img = Mat::zeros(img_size, CV_8UC1);
for (int i = 0; i < num_circles; ++i)
{
Point center(rng.uniform(50, img_size.width - 50),
rng.uniform(50, img_size.height - 50));
int radius = rng.uniform(10, 200);
circle(img, center, radius, Scalar::all(255), FILLED);
}
Mat binary;
adaptiveThreshold(img, binary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 0);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
TEST_CYCLE() findContours(binary, contours, hierarchy, RETR_LIST, CHAIN_APPROX_NONE);
SANITY_CHECK_NOTHING();
}
} } // namespace
+42
View File
@@ -97,4 +97,46 @@ PERF_TEST_P( Image_KernelSize, GaborFilter2d,
SANITY_CHECK(filteredImage, 1e-6, ERROR_RELATIVE);
}
// Performance test for the tiled parallel FilterEngine path (images >= 1MP).
// Exercises filter2D and sepFilter2D separately across common types and border modes.
typedef TestBaseWithParam< tuple<Size, int, BorderMode, bool> > ImgProc_ParallelFilter_Perf;
PERF_TEST_P( ImgProc_ParallelFilter_Perf, filter2D_parallel,
Combine(
Values( Size(1280, 1024), sz1080p ),
Values( CV_8UC1, CV_8UC3, CV_32FC1 ),
Values( BORDER_DEFAULT, BORDER_CONSTANT ),
Values( false, true ) // false = filter2D, true = sepFilter2D
)
)
{
const Size sz = get<0>(GetParam());
const int type = get<1>(GetParam());
const int borderMode = get<2>(GetParam());
const bool isSep = get<3>(GetParam());
Mat src(sz, type);
Mat dst(sz, type);
declare.in(src, WARMUP_RNG).out(dst);
if (isSep)
{
Mat kx = (Mat_<float>(1, 3) << 0.25f, 0.5f, 0.25f);
Mat ky = (Mat_<float>(3, 1) << 0.25f, 0.5f, 0.25f);
TEST_CYCLE() cv::sepFilter2D(src, dst, -1, kx, ky,
Point(-1, -1), 0, borderMode);
}
else
{
Mat kernel = (Mat_<float>(3, 3) <<
1/16.f, 2/16.f, 1/16.f,
2/16.f, 4/16.f, 2/16.f,
1/16.f, 2/16.f, 1/16.f);
TEST_CYCLE() cv::filter2D(src, dst, -1, kernel,
Point(-1, -1), 0, borderMode);
}
SANITY_CHECK_NOTHING();
}
} // namespace
+40
View File
@@ -0,0 +1,40 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "perf_precomp.hpp"
namespace opencv_test {
CV_ENUM(BorderMode, BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REFLECT_101)
CV_ENUM(TargetDepth, CV_8U, CV_16S)
typedef tuple<Size, int, TargetDepth, BorderMode> LaplacianParams;
typedef perf::TestBaseWithParam<LaplacianParams> Perf_Laplacian;
PERF_TEST_P(Perf_Laplacian, Laplacian,
testing::Combine(
testing::Values(szVGA, sz720p, sz1080p),
testing::Values(1, 3, 5), // ksize: 1, 3, 5
TargetDepth::all(), // CV_8U and CV_16S
BorderMode::all()
))
{
Size sz = get<0>(GetParam());
int ksize = get<1>(GetParam());
int ddepth = get<2>(GetParam());
int borderMode = get<3>(GetParam());
Mat src(sz, CV_8UC1);
Mat dst(sz, ddepth == CV_16S ? CV_16SC1 : CV_8UC1);
declare.in(src, WARMUP_RNG).out(dst);
TEST_CYCLE()
{
cv::Laplacian(src, dst, ddepth, ksize, 1.0, 0.0, borderMode);
}
SANITY_CHECK(dst);
}
} // namespace opencv_test
@@ -369,6 +369,7 @@ void boxFilter(InputArray _src, OutputArray _dst, int ddepth,
CV_INSTRUMENT_REGION();
CV_Assert(!_src.empty());
CV_Assert(ksize.width > 0 && ksize.height > 0);
CV_OCL_RUN(_dst.isUMat() &&
(borderType == BORDER_REPLICATE || borderType == BORDER_CONSTANT ||
+13 -18
View File
@@ -69,13 +69,14 @@ template<typename T, typename ST>
struct RowSum :
public BaseRowFilter
{
RowSum( int _ksize, int _anchor ) :
BaseRowFilter()
RowSum( int _ksize, int _anchor )
{
ksize = _ksize;
anchor = _anchor;
}
bool isStateless() const CV_OVERRIDE { return true; }
virtual void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
{
CV_INSTRUMENT_REGION();
@@ -180,8 +181,7 @@ template<typename ST, typename T>
struct ColumnSum :
public BaseColumnFilter
{
ColumnSum( int _ksize, int _anchor, double _scale ) :
BaseColumnFilter()
ColumnSum( int _ksize, int _anchor, double _scale )
{
ksize = _ksize;
anchor = _anchor;
@@ -280,8 +280,7 @@ template<>
struct ColumnSum<int, uchar> :
public BaseColumnFilter
{
ColumnSum( int _ksize, int _anchor, double _scale ) :
BaseColumnFilter()
ColumnSum( int _ksize, int _anchor, double _scale )
{
ksize = _ksize;
anchor = _anchor;
@@ -436,8 +435,7 @@ public BaseColumnFilter
{
enum { SHIFT = 23 };
ColumnSum( int _ksize, int _anchor, double _scale ) :
BaseColumnFilter()
ColumnSum( int _ksize, int _anchor, double _scale )
{
ksize = _ksize;
anchor = _anchor;
@@ -613,8 +611,7 @@ template<>
struct ColumnSum<int, short> :
public BaseColumnFilter
{
ColumnSum( int _ksize, int _anchor, double _scale ) :
BaseColumnFilter()
ColumnSum( int _ksize, int _anchor, double _scale )
{
ksize = _ksize;
anchor = _anchor;
@@ -763,8 +760,7 @@ template<>
struct ColumnSum<int, ushort> :
public BaseColumnFilter
{
ColumnSum( int _ksize, int _anchor, double _scale ) :
BaseColumnFilter()
ColumnSum( int _ksize, int _anchor, double _scale )
{
ksize = _ksize;
anchor = _anchor;
@@ -910,8 +906,7 @@ template<>
struct ColumnSum<int, int> :
public BaseColumnFilter
{
ColumnSum( int _ksize, int _anchor, double _scale ) :
BaseColumnFilter()
ColumnSum( int _ksize, int _anchor, double _scale )
{
ksize = _ksize;
anchor = _anchor;
@@ -1044,8 +1039,7 @@ template<>
struct ColumnSum<int, float> :
public BaseColumnFilter
{
ColumnSum( int _ksize, int _anchor, double _scale ) :
BaseColumnFilter()
ColumnSum( int _ksize, int _anchor, double _scale )
{
ksize = _ksize;
anchor = _anchor;
@@ -1700,13 +1694,14 @@ template<typename T, typename ST>
struct SqrRowSum :
public BaseRowFilter
{
SqrRowSum( int _ksize, int _anchor ) :
BaseRowFilter()
SqrRowSum( int _ksize, int _anchor )
{
ksize = _ksize;
anchor = _anchor;
}
bool isStateless() const CV_OVERRIDE { return true; }
virtual void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
{
CV_INSTRUMENT_REGION();
+2
View File
@@ -303,6 +303,8 @@ void contourTreeToResults(CTree& tree,
void approximateChainTC89(const ContourCodesStorage& chain, const Point& origin, const int method,
ContourPointsStorage& output);
void findTRUContours(InputArray _src, OutputArrayOfArrays _contours, int minSize=0, bool binarize=false, int method=CHAIN_APPROX_NONE);
} // namespace cv
#endif // OPENCV_CONTOURS_COMMON_HPP
+1 -2
View File
@@ -111,8 +111,7 @@ public:
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++)
for (int j = 0; j < (int)contours.size(); j++)
{
int start = contours[j];
int cur = start;
+26
View File
@@ -648,6 +648,32 @@ void cv::findContours(InputArray _image,
return;
}
// Fast path: RETR_LIST without hierarchy → findTRUContours (parallel contour extraction)
if (mode == RETR_LIST && !_hierarchy.needed())
{
// findTRUContours requires FOREGROUND=255; binarize=true thresholds the padded
// image in-place, avoiding an extra allocation (findContours accepts any non-zero value)
findTRUContours(_image, _contours, 0, true,method);
if (offset != Point())
{
if (_contours.kind() == _InputArray::STD_VECTOR_VECTOR)
{
auto& vv = *reinterpret_cast<std::vector<std::vector<Point>>*>(_contours.getObj());
for (auto& c : vv)
for (auto& p : c)
p += offset;
}
else
{
const Scalar shift(offset.x, offset.y);
const int n = (int)_contours.size().height;
for (int i = 0; i < n; i++)
_contours.getMat(i) += shift;
}
}
return;
}
// TODO: need enum value, need way to return contour starting points with chain codes
if (method == 0 /*CV_CHAIN_CODE*/)
{
+649
View File
@@ -0,0 +1,649 @@
#include "precomp.hpp"
#include "contours_common.hpp"
#include "opencv2/core/hal/intrin.hpp"
#include <map>
namespace{
// Tunable block size. 1024 points = 8KB (Fits easily in L1 Cache)
template <size_t BLOCK_SIZE = 2048>
class TRUCOPagedContour {
public:
struct Block {
cv::Point data[BLOCK_SIZE];
};
TRUCOPagedContour() {
allocateBlock();
// Initialize pointers to the start of the first block
curr_ptr_ = all_blocks_[0]->data;
end_ptr_ = curr_ptr_ + BLOCK_SIZE;
}
~TRUCOPagedContour() {
for (Block* b : all_blocks_) cv::fastFree(b);
}
// --- HOT PATH: Minimal instructions ---
// No counter updates, just raw pointer arithmetic.
inline void push_back(const cv::Point& pt) {
if (curr_ptr_ == end_ptr_) {
current_block_idx_++;
if (current_block_idx_ == all_blocks_.size()) {
allocateBlock();
}
curr_ptr_ = all_blocks_[current_block_idx_]->data;
end_ptr_ = curr_ptr_ + BLOCK_SIZE;
}
*curr_ptr_++ = pt;
}
inline void pop_back() {
// Safety check: do nothing if completely empty
if (current_block_idx_ == 0 && curr_ptr_ == all_blocks_[0]->data) return;
// Check if we are at the start of the current block
if (curr_ptr_ == all_blocks_[current_block_idx_]->data) {
// Move to the previous block
current_block_idx_--;
// Point to the end of the previous block
curr_ptr_ = all_blocks_[current_block_idx_]->data + BLOCK_SIZE;
end_ptr_ = curr_ptr_;
}
curr_ptr_--;
}
inline const cv::Point& back() const {
// Handle case where back() crosses block boundary
if (curr_ptr_ == all_blocks_[current_block_idx_]->data) {
return all_blocks_[current_block_idx_ - 1]->data[BLOCK_SIZE - 1];
}
return *(curr_ptr_ - 1);
}
inline const cv::Point& front() const {
return all_blocks_[0]->data[0];
}
// Calculated on demand (O(1) arithmetic, but slightly more math than reading a variable)
size_t size() const {
size_t elements_in_last = curr_ptr_ - all_blocks_[current_block_idx_]->data;
return (current_block_idx_ * BLOCK_SIZE) + elements_in_last;
}
void clear() {
current_block_idx_ = 0;
if (!all_blocks_.empty()) {
curr_ptr_ = all_blocks_[0]->data;
end_ptr_ = curr_ptr_ + BLOCK_SIZE;
}
}
// Optimized Copy: Uses block-wise memcpy
void copyTo(std::vector<cv::Point>& out) const {
size_t total = size();
out.resize(total);
if (total == 0) return;
cv::Point* dst = out.data();
// 1. Copy full blocks
for (size_t i = 0; i < current_block_idx_; ++i) {
std::memcpy(dst, all_blocks_[i]->data, BLOCK_SIZE * sizeof(cv::Point));
dst += BLOCK_SIZE;
}
// 2. Copy partial last block
size_t last_block_count = curr_ptr_ - all_blocks_[current_block_idx_]->data;
if (last_block_count > 0) {
std::memcpy(dst, all_blocks_[current_block_idx_]->data, last_block_count * sizeof(cv::Point));
}
}
private:
void grow() {
current_block_idx_++;
if (current_block_idx_ == all_blocks_.size()) {
allocateBlock();
}
curr_ptr_ = all_blocks_[current_block_idx_]->data;
end_ptr_ = curr_ptr_ + BLOCK_SIZE;
}
void allocateBlock() {
Block* b = (Block*)cv::fastMalloc(sizeof(Block));
all_blocks_.push_back(b);
}
std::vector<Block*> all_blocks_;
size_t current_block_idx_ = 0;
// Fast pointers for the hot loop
cv::Point* curr_ptr_ = nullptr;
cv::Point* end_ptr_ = nullptr;
};
////IMPLEMENTATION
struct AccumulatorT:public std::vector<std::vector<cv::Point>>{
std::vector<int> idx_internal_lastLine,idx_external_firstLine;
};
class TRUCOntourTracer : public cv::ParallelLoopBody
{
public:
// We use a pointer to the accumulator to avoid passing huge objects
// Accumulator: Vector of (Vector of Contours), where Contour is Vector of Points
using AccumulatorType = std::vector<AccumulatorT>;
TRUCOntourTracer(const cv::Mat& img,
const std::vector<cv::Range>& stripRanges,
AccumulatorType& accumulator,
size_t minSize)
: padded_(img), ranges_(stripRanges), accumulator_(accumulator), minSize_(minSize)
{
step_ = padded_.step;
int istep = (int)step_;
// 0: East (Right)
offsets_[0] = 1;
// 1: NE (Up-Right)
offsets_[1] = -istep + 1;
// 2: North (Up)
offsets_[2] = -istep;
// 3: NW (Up-Left)
offsets_[3] = -istep - 1;
// 4: West (Left)
offsets_[4] = -1;
// 5: SW (Down-Left)
offsets_[5] = istep - 1;
// 6: South (Down)
offsets_[6] = istep;
// 7: SE (Down-Right)
offsets_[7] = istep + 1;
memcpy(offsets_ + 8, offsets_, 8 * sizeof(int));
}
//trace external contour marking EAST pixels (VISITED_OUTER_RIGHT) only so that later the analysis of the internal contour is exactly as expected
void traceExternalContourMock( int r,int c,uchar *row_ptr, const cv::Range& rowRange)const{
int curr_x = c , curr_y = r;
int start_dir = -1 ;
int search_idx = 5;
uchar* curr_ptr = row_ptr + c , * start_ptr = curr_ptr;
int dir=-1;
bool is_first_move = true;
// 3. TRACING LOOP
while(true)
{
// Check neighbors
for (int n = 0; n < 8; ++n)
{
int idx = search_idx + n;
// Use offset cache
uchar* neighbor = curr_ptr + offsets_[idx];
if (*neighbor == BACKGROUND) continue;
dir = idx & 7;
if (((search_idx <= 1) || (dir <= search_idx - 2)) && (curr_x!=c && curr_y!=r))//do nt apply to first pixel in the way back
*curr_ptr = VISITED_OUTER_RIGHT;
// --- EXECUTE MOVE ---
curr_y += dy_[dir];
curr_x += dx_[dir];
// Check bounds //we need to move out of the range //if first line, and internal contour, we let it go, but no further from this line
if( curr_y < rowRange.start )
return ;
// Short-circuit Jacob's Check
if (curr_ptr == start_ptr) {
if (!is_first_move && dir == start_dir) {
return ;//done
}
}
curr_ptr = neighbor;//move ptr
// Reset search index for Moore neighbor
search_idx = (dir +6) & 7;
break;
}
if (is_first_move) {
if(dir==-1){//single pixel
break;//not moved
}
start_dir = dir;
is_first_move = false;
}
}
}
bool traceContour( TRUCOPagedContour<4096>* buffer, int r,int c,uchar *row_ptr, const cv::Range& rowRange,bool isExternal)const{
buffer->clear();
int curr_x = c , curr_y = r;
int start_dir = -1 ;
int search_idx = isExternal ? 5 :1;
uchar* curr_ptr = row_ptr + c , * start_ptr = curr_ptr;
int dir=-1;
// int sign=isExternal?1:-1;
bool is_first_move = true;
// QUick test to find the first element of an internal contour. We know it should be NE
if(!isExternal){
int n=0;
for ( n = 0; n < 8; ++n)
{
int idx = search_idx + n;
if ( *(curr_ptr + offsets_[idx]) == BACKGROUND) continue;
if(curr_x+ dx_[ idx & 7]!=c+1 || curr_y+ dy_[ idx & 7]!=r-1) return false;
break;
}
if(n==8) return false;//isolated pixels must not be considered as internal
}
// 3. TRACING LOOP
while(true)
{
buffer->push_back({ (curr_x - 1), (curr_y - 1)});
// Check neighbors
for (int n = 0; n < 8; ++n)
{
int idx = search_idx + n;
// Use offset cache
uchar* neighbor = curr_ptr + offsets_[idx];
if (*neighbor == BACKGROUND) continue;
dir = idx & 7;
// --- EXECUTE MOVE ---
curr_y += dy_[dir];
curr_x += dx_[dir];
// Check bounds //we need to move out of the range //if first line, and internal contour, we let it go, but no further from this line
if( curr_y < rowRange.start ){
return false;
}
if ((search_idx <= 1) || (dir <= search_idx - 2))
{
*curr_ptr = VISITED_OUTER_RIGHT;
}
else if (*curr_ptr == FOREGROUND)
{
*curr_ptr = VISITED_;
}
// Short-circuit Jacob's Check
if (curr_ptr == start_ptr) {
if (!is_first_move && dir == start_dir) {
return true;//done
}
}
curr_ptr = neighbor;//move ptr
// Reset search index for Moore neighbor
search_idx = (dir +6) & 7;
break;
}
if (is_first_move) {
if(dir==-1){//single pixel
*curr_ptr = VISITED_OUTER_RIGHT;
break;//not moved
}
start_dir = dir;
is_first_move = false;
}
}
return true;
}
void operator()(const cv::Range& range) const CV_OVERRIDE
{
// Pre-allocate buffer to avoid re-allocation during moves
TRUCOPagedContour<4096> buffer;
int cols = padded_.cols;
for (int i = range.start; i < range.end; ++i)
{
const cv::Range& rowRange = ranges_[i];
auto& local_contours = accumulator_[i];
// Hint for result vector size
local_contours.reserve(2048);
for (int r = rowRange.start; r <= rowRange.end; ++r)
{
uchar* row_ptr = padded_.data + r * step_;
// "c" is updated by the find* functions
for (int c = 1; c < cols - 1; )
{
// 1. FAST SCAN: Skip background pixels
if ((c = findStartContourPoint(row_ptr, cols, c)) == cols) break;
// 2. CHECK: Only process if actually FOREGROUND (redundancy check)
if (row_ptr[c] == FOREGROUND && r<rowRange.end )
{
if( traceContour(&buffer,r,c,row_ptr,rowRange,true)){
// Post-processing
if (buffer.size() > 1 && buffer.back() == buffer.front()) {
buffer.pop_back();
}
if (buffer.size() >= minSize_) {
// Instead of copying the vector, we move it.
local_contours.emplace_back();
buffer.copyTo(local_contours.back());
if( r==rowRange.start){//register this so we can zip them as Suzuki&Abe method
local_contours.idx_external_firstLine.push_back((int)(local_contours.size()-1) );
}
}
}
}
else if( row_ptr[c] == FOREGROUND && r==rowRange.end ){//extra step to mark east pixels only so that internal contours can be correctly extracted in this line
traceExternalContourMock(r,c,row_ptr,rowRange);
}
// 3. FAST SCAN: Find end of current component to skip processing it again
c = findEndContourPoint(row_ptr, cols, c + 1);
if(c>=cols)break;//end of row
//internal contour
if(row_ptr[c-1]>VISITED_OUTER_RIGHT && r>rowRange.start){//inner contours of first line are handled by the thread above
if(traceContour(&buffer,r,c-1,row_ptr,rowRange,false)){
// Post-processing
if (buffer.size() > 1 && buffer.back() == buffer.front()) {
buffer.pop_back();
}
if (buffer.size() >= minSize_) {
local_contours.emplace_back();
buffer.copyTo(local_contours.back());
if( r==rowRange.end){//register this so we can zip them as Suzuki&Abe method
local_contours.idx_internal_lastLine.push_back((int)(local_contours.size() -1));
}
}
}
}
}
}
}
}
static inline int findStartContourPoint(uchar* src_data, int width, int j)
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
cv::v_uint8 v_zero = cv::vx_setzero_u8();
for (; j <= width - cv::VTraits<cv::v_uint8>::vlanes(); j += cv::VTraits<cv::v_uint8>::vlanes())
{
cv::v_uint8 vmask = (cv::v_ne(cv::vx_load((uchar*)(src_data + j)), v_zero));
if (cv::v_check_any(vmask))
{
j += cv::v_scan_forward(vmask);
return j;
}
}
#endif
for (; j < width && !src_data[j]; ++j)
;
return j;
}
inline static int findEndContourPoint(uchar* src_data,int width, int j)
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
if (j < width && !src_data[j])
{
return j;
}
else
{
cv::v_uint8 v_zero = cv::vx_setzero_u8();
for (; j <= width - cv::VTraits<cv::v_uint8>::vlanes(); j += cv::VTraits<cv::v_uint8>::vlanes())
{
cv::v_uint8 vmask = (cv::v_eq(cv::vx_load((uchar*)(src_data + j)), v_zero));
if (cv::v_check_any(vmask))
{
j += cv::v_scan_forward(vmask);
return j;
}
}
}
#endif
for (; j < width && src_data[j]; ++j)
;
return j;
}
private:
cv::Mat padded_;
const std::vector<cv::Range>& ranges_;
AccumulatorType& accumulator_;
size_t minSize_;
size_t step_;
int offsets_[16];
// 0=E, 1=NE, 2=N, 3=NW, 4=W, 5=SW, 6=S, 7=SE (CCW Rotation)
const int dx_[8] = { 1, 1, 0, -1, -1, -1, 0, 1 };
const int dy_[8] = { 0, -1, -1, -1, 0, 1, 1, 1 };
// Constants defined once
const uchar FOREGROUND = 255;
const uchar BACKGROUND = 0;
const uchar VISITED_OUTER_RIGHT = 100;
const uchar VISITED_ = 200;
};
void approxContour(std::vector<cv::Point> &inout,cv::ContourApproximationModes contApprox_) {
size_t n = inout.size();
if (n <= 1) return;
if (contApprox_ == cv::CHAIN_APPROX_SIMPLE) {
std::vector<cv::Point> result;
result.reserve(n);
for (size_t i = 0; i < n; ++i) {
size_t prev_i = (i == 0) ? n - 1 : i - 1;
size_t next_i = (i == n - 1) ? 0 : i + 1;
cv::Point v1 = inout[i] - inout[prev_i];
cv::Point v2 = inout[next_i] - inout[i];
if (v1 != v2) {
result.push_back(inout[i]);
}
}
if (result.empty() && n > 0) result.push_back(inout[0]);
inout = std::move(result);
} else if (contApprox_ == cv::CHAIN_APPROX_TC89_L1 || contApprox_ == cv::CHAIN_APPROX_TC89_KCOS) {
auto getCode = [](cv::Point d) -> schar {
if (d.x == 1) {
if (d.y == 0) return 0;
if (d.y == -1) return 1;
if (d.y == 1) return 7;
} else if (d.x == 0) {
if (d.y == -1) return 2;
if (d.y == 1) return 6;
} else if (d.x == -1) {
if (d.y == -1) return 3;
if (d.y == 0) return 4;
if (d.y == 1) return 5;
}
return 0;
};
cv::ContourCodesStorage::storage_t codesStorage;
cv::ContourCodesStorage codes(&codesStorage);
for (size_t i = 0; i < n; ++i) {
cv::Point delta = inout[(i + 1) % n] - inout[i];
codes.push_back(getCode(delta));
}
cv::ContourPointsStorage::storage_t pointsStorage;
cv::ContourPointsStorage points(&pointsStorage);
cv::approximateChainTC89(codes, inout[0], contApprox_, points);
inout.clear();
inout.reserve(points.size());
for (size_t i = 0; i < points.size(); ++i) {
inout.push_back(points.at(i));
}
}
}
// ==========================================================
// 1. The Core Implementation (Operates on std::vector directly)
// ==========================================================
void findTRUContoursImpl(cv::Mat& padded,
std::vector<std::vector<cv::Point>>& outContours,
int minSize,int contApprox)
{
// Load Balancing Logic
const int nstripes = cv::getNumThreads();
std::vector<cv::Range> balancedRanges;
if (nstripes > 1) {
int rowsPerStripe = (padded.rows - 2) / nstripes;
int remainingRows = (padded.rows - 2) % nstripes;
int currentRow = 1;
for (int t = 0; t < nstripes; ++t) {
int startRow = currentRow;
int endRow = startRow + rowsPerStripe + (t < remainingRows ? 1 : 0);
balancedRanges.emplace_back(startRow, endRow);
currentRow = endRow;
}
}
else {
balancedRanges.emplace_back(1, padded.rows - 1);
}
// Parallel Execution
std::vector<AccumulatorT> threadAccumulators(balancedRanges.size());
TRUCOntourTracer worker(padded, balancedRanges, threadAccumulators, minSize);
cv::parallel_for_(cv::Range(0, (int)balancedRanges.size()), worker);
// REORG To Match Suzuki & Abe's Contour Ordering.
// Every adjacent strip pair (t, t+1) shares a boundary row. On that row:
// * thread t recorded INTERNAL fragments -> idx_internal_lastLine (tail of accT)
// * thread t+1 recorded EXTERNAL fragments -> idx_external_firstLine (head of accT1)
// Both runs are already in X-ascending order (left-to-right raster scan),
// so a two-pointer merge restores the sequential Suzuki & Abe ordering.
for (size_t t = 0; t + 1 < threadAccumulators.size(); ++t) {
auto& accT = threadAccumulators[t];
auto& accT1 = threadAccumulators[t + 1];
const size_t kI = accT.idx_internal_lastLine.size();
const size_t kE = accT1.idx_external_firstLine.size();
if (kI == 0 && kE == 0) continue;
const size_t tailStart = accT.size() - kI;
// Merge by ascending X of each contour's first point. Contours are moved,
// not copied — only std::vector handles (three pointers) change hands.
std::vector<std::vector<cv::Point>> merged;
merged.reserve(kI + kE);
size_t i = tailStart, iEnd = accT.size();
size_t j = 0, jEnd = kE;
while (i < iEnd && j < jEnd) {
if (accT[i].front().x <= accT1[j].front().x)
merged.emplace_back(std::move(accT[i++]));
else
merged.emplace_back(std::move(accT1[j++]));
}
while (i < iEnd) merged.emplace_back(std::move(accT[i++]));
while (j < jEnd) merged.emplace_back(std::move(accT1[j++]));
// Replace accT's tail with the merged run.
accT.resize(tailStart);
for (auto& c : merged) accT.emplace_back(std::move(c));
// Drop the consumed externals from the head of accT1.
accT1.erase(accT1.begin(), accT1.begin() + kE);
// Any remaining bookkeeping in accT1 indexed absolute positions — fix them.
for (auto& idx : accT1.idx_internal_lastLine)
idx -= static_cast<int>(kE);
// This boundary's bookkeeping is now consumed.
accT.idx_internal_lastLine.clear();
accT1.idx_external_firstLine.clear();
}
// ZERO-COPY MERGE
size_t totalContours = 0;
for (auto& tVec : threadAccumulators)
totalContours += tVec.size();
outContours.clear();
outContours.reserve(totalContours);
// move the contours from thread accumulators to output without copying pixel data
for (auto& tVec : threadAccumulators) {
// move_iterator moves the vector internals (pointers) without copying pixel data
outContours.insert(outContours.end(),
std::make_move_iterator(tVec.begin()),
std::make_move_iterator(tVec.end()));
}
// reverse the order to match original findContours Suzuki&Abe
std::reverse(outContours.begin(), outContours.end());
//7. now, lets do the contour approximation if needed in parallel
if(contApprox!=cv::CHAIN_APPROX_NONE){
cv::parallel_for_(cv::Range(0, (int)outContours.size()), [&](const cv::Range& range){
for(int i=range.start;i<range.end;i++){
approxContour(outContours[i],(cv::ContourApproximationModes)contApprox);
}
});
}
}
}
namespace cv{
// ==========================================================
//
// Public API: Handles OutputArray and dispatches to the core implementation
// This is a modified version of the original TRUCO parallel algorithm to produce the exact same output
// as original findContours with RETR_LIST mode (no hierarchy, all contours are external). It also supports contour approximation.
//
// ==========================================================
void findTRUContours(InputArray _src, OutputArrayOfArrays _contours, int minSize, bool binarize,int method)
{
CV_INSTRUMENT_REGION();
Mat src = _src.getMat();
CV_Assert(!src.empty() && src.type() == CV_8UC1);
// Buffer handling
cv::Mat padded;
cv::copyMakeBorder(src, padded, 1, 1, 1, 1, cv::BORDER_CONSTANT, 0);
if (binarize)
cv::threshold(padded, padded, 0, 255, cv::THRESH_BINARY);
// Fast path: caller passed std::vector<std::vector<cv::Point>> directly.
// Write into it without any intermediate copy.
if (_contours.kind() == _InputArray::STD_VECTOR_VECTOR) {
auto* vec = reinterpret_cast<std::vector<std::vector<cv::Point>>*>(_contours.getObj());
findTRUContoursImpl(padded, *vec, minSize,method);
}
else{ // Slow path: generic OutputArray — build in a temp vector then copy.
std::vector<std::vector<cv::Point>> tempContours;
findTRUContoursImpl(padded, tempContours, minSize,method);
_contours.create((int)tempContours.size(), 1, 0, -1, true);
for (size_t i = 0; i < tempContours.size(); i++) {
_contours.create((int)tempContours[i].size(), 1, CV_32SC2, (int)i, true);
Mat m = _contours.getMat((int)i);
std::memcpy(m.data, tempContours[i].data(), tempContours[i].size() * sizeof(cv::Point));
}
}
}
}
+16
View File
@@ -750,6 +750,22 @@ void cv::Laplacian( InputArray _src, OutputArray _dst, int ddepth, int ksize,
if( ksize == 1 || ksize == 3 )
{
Mat src = _src.getMat();
Mat dst = _dst.getMat();
Point ofs;
Size wsz(src.cols, src.rows);
if(!(borderType & BORDER_ISOLATED))
src.locateROI(wsz, ofs);
CALL_HAL(laplacian, cv_hal_laplacian,
src.ptr(), src.step,
dst.ptr(), dst.step,
src.cols, src.rows,
sdepth, ddepth, cn,
ksize, borderType & ~BORDER_ISOLATED,
(uint8_t)0);
filter2D( _src, _dst, ddepth, kernel, Point(-1, -1), delta, borderType );
}
else
+1
View File
@@ -1495,6 +1495,7 @@ FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color )
/* draws simple or filled circle */
CV_DISABLE_UBSAN
static void
Circle( Mat& img, Point center, int radius, const void* color, int fill )
{
+15 -6
View File
@@ -63,14 +63,14 @@
namespace cv {
BaseRowFilter::BaseRowFilter() { ksize = anchor = -1; }
BaseRowFilter::BaseRowFilter() : ksize(-1), anchor(-1) {}
BaseRowFilter::~BaseRowFilter() {}
BaseColumnFilter::BaseColumnFilter() { ksize = anchor = -1; }
BaseColumnFilter::BaseColumnFilter() : ksize(-1), anchor(-1) {}
BaseColumnFilter::~BaseColumnFilter() {}
void BaseColumnFilter::reset() {}
BaseFilter::BaseFilter() { ksize = Size(-1,-1); anchor = Point(-1,-1); }
BaseFilter::BaseFilter() : ksize(-1, -1), anchor(-1, -1) {}
BaseFilter::~BaseFilter() {}
void BaseFilter::reset() {}
@@ -207,6 +207,14 @@ int FilterEngine::proceed(const uchar* src, int srcstep, int count,
CV_CPU_DISPATCH_MODES_ALL);
}
bool FilterEngine::isStateless() const
{
bool s2d = !filter2D || filter2D->isStateless();
bool sr = !rowFilter || rowFilter->isStateless();
bool sc = !columnFilter || columnFilter->isStateless();
return s2d && sr && sc;
}
void FilterEngine::apply(const Mat& src, Mat& dst, const Size& wsz, const Point& ofs)
{
CV_INSTRUMENT_REGION();
@@ -455,16 +463,18 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
vecOp = _vecOp;
CV_Assert( _kernel.type() == DataType<KT>::type );
preprocess2DKernel( _kernel, coords, coeffs );
ptrs.resize( coords.size() );
}
bool isStateless() const CV_OVERRIDE { return true; }
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width, int cn) CV_OVERRIDE
{
KT _delta = delta;
const Point* pt = &coords[0];
const KT* kf = (const KT*)&coeffs[0];
const ST** kp = (const ST**)&ptrs[0];
int i, k, nz = (int)coords.size();
AutoBuffer<const ST*> _kp(nz);
const ST** kp = _kp.data();
CastOp castOp = castOp0;
width *= cn;
@@ -507,7 +517,6 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
std::vector<Point> coords;
std::vector<uchar> coeffs;
std::vector<uchar*> ptrs;
KT delta;
CastOp castOp0;
VecOp vecOp;
+2
View File
@@ -48,6 +48,8 @@
namespace cv
{
#ifdef HAVE_OPENCL
bool ocl_sepFilter2D(
InputArray _src, OutputArray _dst, int ddepth,
+261 -3
View File
@@ -43,6 +43,7 @@
#include "precomp.hpp"
#include "opencv2/core/hal/intrin.hpp"
#include "filter.hpp"
#include "opencv2/core/utils/tls.hpp"
#include <cstddef>
@@ -296,12 +297,264 @@ int FilterEngine__proceed(FilterEngine& this_, const uchar* src, int srcstep, in
return dy;
}
// Lightweight tile border fill, templated on element size (bytes) so the
// compiler can inline and optimize memcpy for common fixed sizes (1, 2, 4 …).
// Avoids the full cv::copyMakeBorder() overhead: no OpenCL/IPP dispatch,
// no Mat header re-allocation, and a single AutoBuffer for the border table.
template<int esz>
static void fillTileBorder(
const uchar* src, size_t srcstep, int src_w, int src_h,
uchar* dst, size_t dststep,
int pad_top, int pad_bottom, int pad_left, int pad_right,
int borderType, const uchar* constVal)
{
const int dst_w = src_w + pad_left + pad_right;
// ── Left/right column offset table (byte offsets into a source row) ────────
AutoBuffer<int> _tab(pad_left + pad_right);
int* tab = _tab.data();
for (int i = 0; i < pad_left; i++)
tab[i] = borderInterpolate(i - pad_left, src_w, borderType) * esz;
for (int i = 0; i < pad_right; i++)
tab[pad_left + i] = borderInterpolate(src_w + i, src_w, borderType) * esz;
// For BORDER_CONSTANT top/bottom rows, pre-build a full-width fill row.
AutoBuffer<uchar> _constRow;
uchar* constRow = nullptr;
if (borderType == BORDER_CONSTANT && (pad_top > 0 || pad_bottom > 0))
{
_constRow.allocate(dst_w * esz);
constRow = _constRow.data();
for (int x = 0; x < dst_w; x++)
memcpy(constRow + x * esz, constVal, esz);
}
// ── Interior rows ─────────────────────────────────────────────────────────
uchar* dstRow = dst + pad_top * dststep;
for (int r = 0; r < src_h; r++, dstRow += dststep, src += srcstep)
{
// Copy source pixels into the interior portion of the row.
memcpy(dstRow + pad_left * esz, src, src_w * esz);
if (borderType == BORDER_CONSTANT)
{
for (int i = 0; i < pad_left; i++)
memcpy(dstRow + i * esz, constVal, esz);
for (int i = 0; i < pad_right; i++)
memcpy(dstRow + (pad_left + src_w + i) * esz, constVal, esz);
}
else
{
for (int i = 0; i < pad_left; i++)
memcpy(dstRow + i * esz, src + tab[i], esz);
for (int i = 0; i < pad_right; i++)
memcpy(dstRow + (pad_left + src_w + i) * esz, src + tab[pad_left + i], esz);
}
}
// ── Top rows ──────────────────────────────────────────────────────────────
for (int r = 0; r < pad_top; r++)
{
if (borderType == BORDER_CONSTANT)
memcpy(dst + r * dststep, constRow, dst_w * esz);
else
{
int j = borderInterpolate(r - pad_top, src_h, borderType);
memcpy(dst + r * dststep, dst + (pad_top + j) * dststep, dst_w * esz);
}
}
// ── Bottom rows ───────────────────────────────────────────────────────────
uchar* dstBot = dst + (pad_top + src_h) * dststep;
for (int r = 0; r < pad_bottom; r++)
{
if (borderType == BORDER_CONSTANT)
memcpy(dstBot + r * dststep, constRow, dst_w * esz);
else
{
int j = borderInterpolate(src_h + r, src_h, borderType);
memcpy(dstBot + r * dststep, dst + (pad_top + j) * dststep, dst_w * esz);
}
}
}
class TiledFilterInvoker : public ParallelLoopBody
{
struct TiledFilterBuffers {
Mat padded_tile;
Mat hbuf;
};
public:
TiledFilterInvoker(FilterEngine& _fe, const Mat& _src, Mat& _dst, int _tileSize = 128)
: fe(_fe), src(_src), dst(_dst), tileSize(_tileSize)
{
tilesX = (dst.cols + tileSize - 1) / tileSize;
}
virtual void operator() (const Range& range) const CV_OVERRIDE
{
int ax = fe.anchor.x, kwidth = fe.ksize.width;
int ay = fe.anchor.y, kheight = fe.ksize.height;
int dx1 = ax, dx2 = kwidth - ax - 1;
int dy1 = ay, dy2 = kheight - ay - 1;
int borderType = fe.rowBorderType;
int cn = CV_MAT_CN(fe.srcType);
bool isSep = fe.isSeparable();
TiledFilterBuffers& tls = tlsData.getRef();
for (int i = range.start; i < range.end; i++)
{
int ty = i / tilesX;
int tx = i % tilesX;
int dst_x = tx * tileSize;
int dst_y = ty * tileSize;
int w = std::min(tileSize, dst.cols - dst_x);
int h = std::min(tileSize, dst.rows - dst_y);
int src_x1 = dst_x - dx1, src_y1 = dst_y - dy1;
int src_x2 = dst_x + w + dx2, src_y2 = dst_y + h + dy2;
int pad_top = std::max(0, -src_y1);
int pad_bottom = std::max(0, src_y2 - src.rows);
int pad_left = std::max(0, -src_x1);
int pad_right = std::max(0, src_x2 - src.cols);
int clamped_x1 = std::max(0, src_x1);
int clamped_y1 = std::max(0, src_y1);
int clamped_x2 = std::min(src.cols, src_x2);
int clamped_y2 = std::min(src.rows, src_y2);
Mat src_region = src(Rect(clamped_x1, clamped_y1,
clamped_x2 - clamped_x1,
clamped_y2 - clamped_y1));
Mat tile_mat;
if (pad_top == 0 && pad_bottom == 0 && pad_left == 0 && pad_right == 0)
{
tile_mat = src_region;
}
else
{
int padded_w = w + dx1 + dx2;
int padded_h = h + dy1 + dy2;
if (tls.padded_tile.cols < padded_w || tls.padded_tile.rows < padded_h || tls.padded_tile.type() != fe.srcType)
tls.padded_tile.create(padded_h, padded_w, fe.srcType);
tile_mat = tls.padded_tile(Rect(0, 0, padded_w, padded_h));
const int esz = (int)CV_ELEM_SIZE(fe.srcType);
const uchar* cval = fe.constBorderValue.empty() ? nullptr : &fe.constBorderValue[0];
#define FILL_BORDER(E) fillTileBorder<E>(src_region.ptr(), (size_t)src_region.step, \
src_region.cols, src_region.rows, tile_mat.ptr(), (size_t)tile_mat.step, \
pad_top, pad_bottom, pad_left, pad_right, borderType, cval)
switch (esz)
{
case 1: FILL_BORDER(1); break;
case 2: FILL_BORDER(2); break;
case 3: FILL_BORDER(3); break;
case 4: FILL_BORDER(4); break;
case 6: FILL_BORDER(6); break;
case 8: FILL_BORDER(8); break;
case 12: FILL_BORDER(12); break;
case 16: FILL_BORDER(16); break;
default:
{
// Generic fallback for exotic element sizes.
Scalar bv = Scalar::all(0);
if (!fe.constBorderValue.empty())
{
const uchar* bptr = &fe.constBorderValue[0];
int depth = CV_MAT_DEPTH(fe.srcType);
for (int k = 0; k < cn; k++)
{
switch(depth)
{
case CV_8U: bv[k] = bptr[k]; break;
case CV_8S: bv[k] = ((const schar*)bptr)[k]; break;
case CV_16U: bv[k] = ((const ushort*)bptr)[k]; break;
case CV_16S: bv[k] = ((const short*)bptr)[k]; break;
case CV_32S: bv[k] = ((const int*)bptr)[k]; break;
case CV_32F: bv[k] = ((const float*)bptr)[k]; break;
case CV_64F: bv[k] = ((const double*)bptr)[k]; break;
default: bv[k] = bptr[k];
}
}
}
copyMakeBorder(src_region, tile_mat, pad_top, pad_bottom, pad_left, pad_right, borderType, bv);
}
}
#undef FILL_BORDER
}
uchar* dst_ptr = dst.ptr(dst_y) + dst_x * dst.elemSize();
if (isSep)
{
int hstep = (int)alignSize(w * CV_ELEM_SIZE(fe.bufType), VEC_ALIGN);
if (tls.hbuf.rows < tile_mat.rows || tls.hbuf.cols < hstep)
tls.hbuf.create(tile_mat.rows, hstep, CV_8U);
uchar* hbuf = tls.hbuf.ptr();
for (int r = 0; r < tile_mat.rows; r++)
(*fe.rowFilter)(tile_mat.ptr(r), hbuf + r * hstep, w, cn);
AutoBuffer<const uchar*> _brows(h + kheight - 1);
const uchar** brows = _brows.data();
for (int m = 0; m < h + kheight - 1; m++)
brows[m] = hbuf + m * hstep;
(*fe.columnFilter)(brows, dst_ptr, (int)dst.step, h, w * cn);
}
else
{
AutoBuffer<const uchar*> _brows(h + kheight - 1);
const uchar** brows = _brows.data();
for (int k = 0; k < h + kheight - 1; k++)
brows[k] = tile_mat.ptr(k);
(*fe.filter2D)(brows, dst_ptr, (int)dst.step, h, w, cn);
}
}
}
private:
FilterEngine& fe;
const Mat& src;
Mat& dst;
int tileSize;
int tilesX;
mutable TLSData<TiledFilterBuffers> tlsData;
};
void FilterEngine__apply(FilterEngine& this_, const Mat& src, Mat& dst, const Size& wsz, const Point& ofs)
{
CV_INSTRUMENT_REGION();
CV_DbgAssert(src.type() == this_.srcType && dst.type() == this_.dstType);
// Tiled Fast Path for stateless parallel filters on large images.
int nthreads = cv::getNumThreads();
if (this_.isStateless() && nthreads > 1 &&
(size_t)src.total() >= std::max((size_t)1024 * 1024, (size_t)nthreads * 64 * 1024) &&
this_.rowBorderType == this_.columnBorderType)
{
// For in-place operations (e.g. morphologyEx MORPH_OPEN), clone src so that
// concurrent tiles read from an immutable snapshot rather than racing on writes.
Mat src_copy = (src.data == dst.data) ? src.clone() : src;
// Heuristic: Balance L2 cache locality (128) vs parallel load balancing (64).
int tileSize = (src.total() < (size_t)nthreads * 128 * 128 * 4) ? 64 : 128;
int totalTiles = ((dst.cols + tileSize - 1) / tileSize) * ((dst.rows + tileSize - 1) / tileSize);
TiledFilterInvoker invoker(this_, src_copy, dst, tileSize);
parallel_for_(Range(0, totalTiles), invoker);
return;
}
FilterEngine__start(this_, wsz, src.size(), ofs);
int y = this_.startY - ofs.y;
FilterEngine__proceed(this_,
@@ -2398,6 +2651,8 @@ template<typename ST, typename DT, class VecOp> struct RowFilter : public BaseRo
vecOp = _vecOp;
}
bool isStateless() const CV_OVERRIDE { return true; }
void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
{
CV_INSTRUMENT_REGION();
@@ -2599,6 +2854,8 @@ template<class CastOp, class VecOp> struct ColumnFilter : public BaseColumnFilte
(kernel.rows == 1 || kernel.cols == 1));
}
bool isStateless() const CV_OVERRIDE { return true; }
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width) CV_OVERRIDE
{
CV_INSTRUMENT_REGION();
@@ -3116,16 +3373,18 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
vecOp = _vecOp;
CV_Assert( _kernel.type() == DataType<KT>::type );
preprocess2DKernel( _kernel, coords, coeffs );
ptrs.resize( coords.size() );
}
bool isStateless() const CV_OVERRIDE { return true; }
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width, int cn) CV_OVERRIDE
{
KT _delta = delta;
const Point* pt = &coords[0];
const KT* kf = (const KT*)&coeffs[0];
const ST** kp = (const ST**)&ptrs[0];
int i, k, nz = (int)coords.size();
AutoBuffer<const ST*> _kp(nz);
const ST** kp = _kp.data();
CastOp castOp = castOp0;
width *= cn;
@@ -3168,7 +3427,6 @@ template<typename ST, class CastOp, class VecOp> struct Filter2D : public BaseFi
std::vector<Point> coords;
std::vector<uchar> coeffs;
std::vector<uchar*> ptrs;
KT delta;
CastOp castOp0;
VecOp vecOp;
+9
View File
@@ -74,6 +74,8 @@ public:
virtual ~BaseRowFilter();
//! the filtering operator. Must be overridden in the derived classes. The horizontal border interpolation is done outside of the class.
virtual void operator()(const uchar* src, uchar* dst, int width, int cn) = 0;
//! returns true if the filter holds no mutable state between calls (safe for concurrent tile-parallel invocation)
virtual bool isStateless() const { return false; }
int ksize;
int anchor;
@@ -104,6 +106,8 @@ public:
virtual void operator()(const uchar** src, uchar* dst, int dststep, int dstcount, int width) = 0;
//! resets the internal buffers, if any
virtual void reset();
//! returns true if the filter holds no mutable state between calls (safe for concurrent tile-parallel invocation)
virtual bool isStateless() const { return false; }
int ksize;
int anchor;
@@ -132,6 +136,8 @@ public:
virtual void operator()(const uchar** src, uchar* dst, int dststep, int dstcount, int width, int cn) = 0;
//! resets the internal buffers, if any
virtual void reset();
//! returns true if the filter holds no mutable state between calls (safe for concurrent tile-parallel invocation)
virtual bool isStateless() const { return false; }
Size ksize;
Point anchor;
@@ -251,6 +257,9 @@ public:
int remainingInputRows() const;
int remainingOutputRows() const;
//! returns true if the engine's filters are entirely stateless and thus safe for 2D parallel block chunking
bool isStateless() const;
int srcType;
int dstType;
int bufType;
+21
View File
@@ -1388,6 +1388,27 @@ inline int hal_ni_scharr(const uchar* src_data, size_t src_step, uchar* dst_data
#define cv_hal_scharr hal_ni_scharr
//! @endcond
/**
@brief Computes Laplacian filter
@param src_data Source image data
@param src_step Source image step
@param dst_data Destination image data
@param dst_step Destination image step
@param width Source image width
@param height Source image height
@param src_depth Depth of source image
@param dst_depth Depth of destination image
@param cn Number of channels
@param ksize Kernel size (1, 3, or 5)
@param border_type Border type
@param border_value Border value for CONSTANT
*/
inline int hal_ni_laplacian(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, int width, int height, int src_depth, int dst_depth, int cn, int ksize, int border_type, uchar border_value) { return CV_HAL_ERROR_NOT_IMPLEMENTED;}
//! @cond IGNORED
#define cv_hal_laplacian hal_ni_laplacian
//! @endcond
/**
@brief Perform Gaussian Blur and downsampling for input tile.
@param depth Depths of source and destination image
+2
View File
@@ -2352,6 +2352,7 @@ static void warpAffine(int src_type,
parallel_for_(range, invoker, dst.total()/(double)(1<<16));
}
CV_DISABLE_UBSAN
void warpAffineBlocklineNN(int *adelta, int *bdelta, short* xy, int X0, int Y0, int bw)
{
CALL_HAL(warpAffineBlocklineNN, cv_hal_warpAffineBlocklineNN, adelta, bdelta, xy, X0, Y0, bw);
@@ -2382,6 +2383,7 @@ void warpAffineBlocklineNN(int *adelta, int *bdelta, short* xy, int X0, int Y0,
}
}
CV_DISABLE_UBSAN
void warpAffineBlockline(int *adelta, int *bdelta, short* xy, short* alpha, int X0, int Y0, int bw)
{
CALL_HAL(warpAffineBlockline, cv_hal_warpAffineBlockline, adelta, bdelta, xy, alpha, X0, Y0, bw);
-1
View File
@@ -977,7 +977,6 @@ double LineSegmentDetectorImpl::rect_nfa(const rect& rec) const
double top_y = ordered_y[0].y, bottom_y = ordered_y[2].y;
// Loop around all points in the region and count those that are aligned.
std::vector<cv::Point> points;
double left_limit, right_limit;
for(int y = (int) ceil(top_y); y <= (int) ceil(bottom_y); ++y)
{
+11 -5
View File
@@ -43,6 +43,7 @@
#include "precomp.hpp"
#include <limits.h>
#include "opencv2/core/hal/intrin.hpp"
#include "filter.hpp"
/****************************************************************************************\
Basic Morphological Operations: Erosion & Dilation
@@ -495,6 +496,8 @@ template<class Op, class VecOp> struct MorphRowFilter : public BaseRowFilter
anchor = _anchor;
}
bool isStateless() const CV_OVERRIDE { return true; }
void operator()(const uchar* src, uchar* dst, int width, int cn) CV_OVERRIDE
{
CV_INSTRUMENT_REGION();
@@ -551,6 +554,8 @@ template<class Op, class VecOp> struct MorphColumnFilter : public BaseColumnFilt
anchor = _anchor;
}
bool isStateless() const CV_OVERRIDE { return true; }
void operator()(const uchar** _src, uchar* dst, int dststep, int count, int width) CV_OVERRIDE
{
CV_INSTRUMENT_REGION();
@@ -638,7 +643,7 @@ template<class Op, class VecOp> struct MorphColumnFilter : public BaseColumnFilt
};
template<class Op, class VecOp> struct MorphFilter : BaseFilter
template<class Op, class VecOp> struct MorphFilter : public BaseFilter
{
typedef typename Op::rtype T;
@@ -651,16 +656,18 @@ template<class Op, class VecOp> struct MorphFilter : BaseFilter
std::vector<uchar> coeffs; // we do not really the values of non-zero
// kernel elements, just their locations
preprocess2DKernel( _kernel, coords, coeffs );
ptrs.resize( coords.size() );
}
bool isStateless() const CV_OVERRIDE { return true; }
void operator()(const uchar** src, uchar* dst, int dststep, int count, int width, int cn) CV_OVERRIDE
{
CV_INSTRUMENT_REGION();
const Point* pt = &coords[0];
const T** kp = (const T**)&ptrs[0];
int i, k, nz = (int)coords.size();
AutoBuffer<const T*> _kp(nz);
const T** kp = _kp.data();
Op op;
width *= cn;
@@ -671,7 +678,7 @@ template<class Op, class VecOp> struct MorphFilter : BaseFilter
for( k = 0; k < nz; k++ )
kp[k] = (const T*)src[pt[k].y] + pt[k].x*cn;
i = vecOp(&ptrs[0], nz, dst, width);
i = vecOp((uchar**)kp, nz, dst, width);
#if CV_ENABLE_UNROLLED
for( ; i <= width - 4; i += 4 )
{
@@ -700,7 +707,6 @@ template<class Op, class VecOp> struct MorphFilter : BaseFilter
}
std::vector<Point> coords;
std::vector<uchar*> ptrs;
VecOp vecOp;
};
+226 -271
View File
@@ -240,8 +240,8 @@ TEST(Imgproc_ConnectedComponents, missing_background_pixels)
TEST(Imgproc_ConnectedComponents, spaghetti_bbdt_sauf_stats)
{
cv::Mat1b img(16, 16);
img << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
cv::Mat1b img({16, 16}, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0,
@@ -256,7 +256,8 @@ TEST(Imgproc_ConnectedComponents, spaghetti_bbdt_sauf_stats)
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
});
cv::Mat1i labels;
cv::Mat1i stats;
@@ -360,68 +361,64 @@ TEST(Imgproc_ConnectedComponents, spaghetti_bbdt_sauf_stats)
TEST(Imgproc_ConnectedComponents, chessboard_even)
{
cv::Size size(16, 16);
cv::Mat1b input(size);
cv::Mat1i output_8c(size);
cv::Mat1i output_4c(size);
const auto size = {16, 16};
cv::Mat1b input(size, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
cv::Mat1i output_8c(size, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
cv::Mat1i output_4c(size, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64,
65, 0, 66, 0, 67, 0, 68, 0, 69, 0, 70, 0, 71, 0, 72, 0,
0, 73, 0, 74, 0, 75, 0, 76, 0, 77, 0, 78, 0, 79, 0, 80,
81, 0, 82, 0, 83, 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0,
0, 89, 0, 90, 0, 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96,
97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0,
0, 105, 0, 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112,
113, 0, 114, 0, 115, 0, 116, 0, 117, 0, 118, 0, 119, 0, 120, 0,
0, 121, 0, 122, 0, 123, 0, 124, 0, 125, 0, 126, 0, 127, 0, 128
});
// Chessboard image with even number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1;
output_8c <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1;
output_4c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64,
65, 0, 66, 0, 67, 0, 68, 0, 69, 0, 70, 0, 71, 0, 72, 0,
0, 73, 0, 74, 0, 75, 0, 76, 0, 77, 0, 78, 0, 79, 0, 80,
81, 0, 82, 0, 83, 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0,
0, 89, 0, 90, 0, 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96,
97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0,
0, 105, 0, 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112,
113, 0, 114, 0, 115, 0, 116, 0, 117, 0, 118, 0, 119, 0, 120, 0,
0, 121, 0, 122, 0, 123, 0, 124, 0, 125, 0, 126, 0, 127, 0, 128;
}
int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI };
@@ -448,65 +445,61 @@ TEST(Imgproc_ConnectedComponents, chessboard_even)
TEST(Imgproc_ConnectedComponents, chessboard_odd)
{
cv::Size size(15, 15);
cv::Mat1b input(size);
cv::Mat1i output_8c(size);
cv::Mat1i output_4c(size);
const auto size = {15, 15};
cv::Mat1b input(size, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
cv::Mat1i output_8c(size, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
cv::Mat1i output_4c(size, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0,
16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23,
0, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0,
31, 0, 32, 0, 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38,
0, 39, 0, 40, 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0,
46, 0, 47, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53,
0, 54, 0, 55, 0, 56, 0, 57, 0, 58, 0, 59, 0, 60, 0,
61, 0, 62, 0, 63, 0, 64, 0, 65, 0, 66, 0, 67, 0, 68,
0, 69, 0, 70, 0, 71, 0, 72, 0, 73, 0, 74, 0, 75, 0,
76, 0, 77, 0, 78, 0, 79, 0, 80, 0, 81, 0, 82, 0, 83,
0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, 89, 0, 90, 0,
91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, 0, 97, 0, 98,
0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0,
106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, 0, 113
});
// Chessboard image with odd number of rows and cols
// Note that this is the maximum number of labels for 4-way connectivity
{
input <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1;
output_8c <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1;
output_4c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0,
16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23,
0, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0,
31, 0, 32, 0, 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38,
0, 39, 0, 40, 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0,
46, 0, 47, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53,
0, 54, 0, 55, 0, 56, 0, 57, 0, 58, 0, 59, 0, 60, 0,
61, 0, 62, 0, 63, 0, 64, 0, 65, 0, 66, 0, 67, 0, 68,
0, 69, 0, 70, 0, 71, 0, 72, 0, 73, 0, 74, 0, 75, 0,
76, 0, 77, 0, 78, 0, 79, 0, 80, 0, 81, 0, 82, 0, 83,
0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, 89, 0, 90, 0,
91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, 0, 97, 0, 98,
0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0,
106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, 0, 113;
}
int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI };
@@ -533,66 +526,61 @@ TEST(Imgproc_ConnectedComponents, chessboard_odd)
TEST(Imgproc_ConnectedComponents, maxlabels_8conn_even)
{
cv::Size size(16, 16);
cv::Mat1b input(size);
cv::Mat1i output_8c(size);
cv::Mat1i output_4c(size);
{
input <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
output_8c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
output_4c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
}
const auto size = {16, 16};
cv::Mat1b input(size, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
});
cv::Mat1i output_8c(size, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
});
cv::Mat1i output_4c(size, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
});
int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI };
@@ -619,63 +607,58 @@ TEST(Imgproc_ConnectedComponents, maxlabels_8conn_even)
TEST(Imgproc_ConnectedComponents, maxlabels_8conn_odd)
{
cv::Size size(15, 15);
cv::Mat1b input(size);
cv::Mat1i output_8c(size);
cv::Mat1i output_4c(size);
{
input <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1;
output_8c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64;
output_4c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64;
}
const auto size = {15, 15};
cv::Mat1b input(size, {
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1
});
cv::Mat1i output_8c(size, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64
});
cv::Mat1i output_4c(size, {
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64
});
int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI };
@@ -702,24 +685,10 @@ TEST(Imgproc_ConnectedComponents, maxlabels_8conn_odd)
TEST(Imgproc_ConnectedComponents, single_row)
{
cv::Size size(1, 15);
cv::Mat1b input(size);
cv::Mat1i output_8c(size);
cv::Mat1i output_4c(size);
{
input <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1;
output_8c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8;
output_4c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8;
}
const auto size = {1, 15};
cv::Mat1b input(size, {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1});
cv::Mat1i output_8c(size, {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8});
cv::Mat1i output_4c(size, {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8});
int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI };
@@ -746,24 +715,10 @@ TEST(Imgproc_ConnectedComponents, single_row)
TEST(Imgproc_ConnectedComponents, single_column)
{
cv::Size size(15, 1);
cv::Mat1b input(size);
cv::Mat1i output_8c(size);
cv::Mat1i output_4c(size);
{
input <<
1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1;
output_8c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8;
output_4c <<
1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8;
}
const auto size = {15, 1};
cv::Mat1b input(size, {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1});
cv::Mat1i output_8c(size, {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8});
cv::Mat1i output_4c(size, {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8});
int ccltype[] = { cv::CCL_DEFAULT, cv::CCL_WU, cv::CCL_GRANA, cv::CCL_BOLELLI, cv::CCL_SAUF, cv::CCL_BBDT, cv::CCL_SPAGHETTI };
@@ -0,0 +1,254 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
namespace opencv_test { namespace {
//helps to temporarily change the number of threads and restore it back after the scope
struct CvNThreadScope{
int nprev;
CvNThreadScope(int n){
nprev=cv::getNumThreads();
cv::setNumThreads(n);
}
~CvNThreadScope(){
cv::setNumThreads(nprev);
}
};
// Order-independent contour-set comparison
static bool trucoContoursMatch(const vector<vector<Point>>& cont1, const vector<vector<Point>>& cont2)
{
//order senstive hash
auto Hash=[](const std::vector<cv::Point>& contour) {
// FNV-1a 64-bit hash constants
constexpr uint64_t FNV_OFFSET = 1469598103934665603ULL;
constexpr uint64_t FNV_PRIME = 1099511628211ULL;
uint64_t hash = FNV_OFFSET;
// Mix in the size so that contours with different lengths
// but same prefix produce different hashes
uint64_t size = static_cast<uint64_t>(contour.size());
for (int i = 0; i < 8; ++i) {
hash ^= (size >> (i * 8)) & 0xFF;
hash *= FNV_PRIME;
}
// Mix in each point's x and y coordinates byte by byte
for (const cv::Point& p : contour) {
uint32_t x = static_cast<uint32_t>(p.x);
uint32_t y = static_cast<uint32_t>(p.y);
for (int i = 0; i < 4; ++i) {
hash ^= (x >> (i * 8)) & 0xFF;
hash *= FNV_PRIME;
}
for (int i = 0; i < 4; ++i) {
hash ^= (y >> (i * 8)) & 0xFF;
hash *= FNV_PRIME;
}
}
return hash;
};
std::set<uint64> hashes1,hashes2;
for(auto &contour:cont1){
hashes1.insert( Hash(contour));
}
for(auto &contour:cont2){
hashes2.insert( Hash(contour));
}
for(auto &h1:hashes1){//element in cont and not in cont2
if( hashes2.find(h1) ==hashes2.end()) return false;
}
return true;
}
typedef testing::TestWithParam<ContourApproximationModes> Imgproc_FindTRUContours;
TEST_P(Imgproc_FindTRUContours, nthreads_consistency)
{
ContourApproximationModes method = GetParam();
const Size sz(1000, 1000);
RNG& rng = TS::ptr()->get_rng();
Mat noise(sz, CV_8UC1);
cvtest::randUni(rng, noise, 0, 255);
Mat blurred;
boxFilter(noise, blurred, CV_8U, Size(5, 5));
Mat img;
cv::threshold(blurred, img, 128, 255, THRESH_BINARY);
vector<vector<Point>> ref_contours;
vector<vector<Point>> ref_contours_m0;
{
CvNThreadScope nt(1);
findContours(img, ref_contours, RETR_LIST, method);
}
std::vector<int> thread_counts;
for(int i=2;i<40;i++) thread_counts.push_back(i);
for (int t : thread_counts)
{
SCOPED_TRACE(cv::format("nthreads=%d method=%d", t, (int)method));
CvNThreadScope nt(t);
vector<vector<Point>> contours;
findContours(img, contours, RETR_LIST, method); //will use TRUCO because NOT using hierarchy AND RETR_LIST
auto match=trucoContoursMatch(ref_contours, contours);
EXPECT_TRUE(match);
}
}
TEST_P(Imgproc_FindTRUContours, circles_vs_standard)
{
ContourApproximationModes method = GetParam();
const Size sz(4000, 4000);
const int ITER = cvtest::debugLevel >= 10?100:10;
const int NUM_CIRCLES = 250;
RNG& rng = TS::ptr()->get_rng();
for (int iter = 0; iter < ITER; ++iter)
{
SCOPED_TRACE(cv::format("iter=%d method=%d", iter, (int)method));
Mat img(sz, CV_8UC1, Scalar::all(0));
for (int i = 0; i < NUM_CIRCLES; ++i)
{
Point center(rng.uniform(50, sz.width - 50),
rng.uniform(50, sz.height - 50));
int radius = rng.uniform(10, 150);
circle(img, center, radius, Scalar::all(255), FILLED);
}
Mat binary;
adaptiveThreshold(img, binary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 11, 0);
vector<vector<Point>> ref_contours;
vector<Vec4i> hierarchy;
findContours(binary, ref_contours, hierarchy, RETR_LIST, method); //will call suzuki abe because using hierarchy
EXPECT_TRUE(!hierarchy.empty());
vector<vector<Point>> truco_contours;
findContours(binary, truco_contours, RETR_LIST, method);
EXPECT_TRUE(trucoContoursMatch(ref_contours, truco_contours)); //will use TRUCO because NOT using hierarchy AND RETR_LIST
}
}
TEST_P(Imgproc_FindTRUContours, noise_threshold)
{
ContourApproximationModes method = GetParam();
const Size sz(1500, 1500);
RNG& rng = TS::ptr()->get_rng();
const int levels[] = {86, 128, 170};
const int ITER = 2;
std::vector<int> thread_counts;
for(int i=2; i<40; i+=3) thread_counts.push_back(i);
for(int i=0; i<ITER; i++)
{
for (int level : levels)
{
SCOPED_TRACE(cv::format("level=%d method=%d", level, (int)method));
Mat noise(sz, CV_8UC1);
cvtest::randUni(rng, noise, 0, 255);
Mat blurred;
boxFilter(noise, blurred, CV_8U, Size(5, 5));
Mat binary;
cv::threshold(blurred, binary, level, 255, THRESH_BINARY);
vector<vector<Point>> ref_contours;
vector<Vec4i> hierarchy;
findContours(binary, ref_contours, hierarchy, RETR_LIST, method);//will call suzuki&abe because using hierarchy
EXPECT_TRUE(!hierarchy.empty());
for(auto nt: thread_counts){
CvNThreadScope ts(nt);
vector<vector<Point>> truco_contours;
findContours(binary, truco_contours, RETR_LIST, method);//will call TRUCO abe because NOT using hierarchy
EXPECT_TRUE(trucoContoursMatch(ref_contours, truco_contours));
}
}
}
}
TEST_P(Imgproc_FindTRUContours, nested_rectangles)
{
ContourApproximationModes method = GetParam();
const int DIM = 1500;
const Size sz(DIM, DIM);
const int NUM = 25;
Mat img(sz, CV_8UC1, Scalar::all(0));
Rect rect(1, 1, DIM - 2, DIM - 2);
for (int i = 0; i < NUM; ++i)
{
rectangle(img, rect, Scalar::all(255));
rect.x += 10;
rect.y += 10;
rect.width -= 20;
rect.height -= 20;
if (rect.width <= 0 || rect.height <= 0)
break;
}
vector<vector<Point>> ref_contours;
vector<Vec4i> hierarchy;
findContours(img, ref_contours, hierarchy, RETR_LIST, method);//will call suzuki abe because using hierarchy
EXPECT_TRUE(!hierarchy.empty());
vector<vector<Point>> truco_contours;
findContours(img, truco_contours, RETR_LIST, method);//will use TRUCO because NOT using hierarchy AND RETR_LIST
EXPECT_TRUE(trucoContoursMatch(ref_contours, truco_contours));
}
TEST_P(Imgproc_FindTRUContours, mixed_figures)
{
ContourApproximationModes method = GetParam();
const Size sz(1800, 1600);
RNG& rng = TS::ptr()->get_rng();
const int ITER = cvtest::debugLevel >= 10?100:10;
for (int iter = 0; iter < ITER; ++iter)
{
SCOPED_TRACE(cv::format("iter=%d method=%d", iter, (int)method));
Mat img(sz, CV_8UC1, Scalar::all(0));
for (int i = 0; i < 5; ++i)
{
Rect r(rng.uniform(10, sz.width / 2),
rng.uniform(10, sz.height / 2),
rng.uniform(20, 100),
rng.uniform(20, 100));
r &= Rect(0, 0, sz.width - 1, sz.height - 1);
rectangle(img, r, Scalar::all(255), FILLED);
}
for (int i = 0; i < 5; ++i)
{
Point center(rng.uniform(50, sz.width - 50),
rng.uniform(50, sz.height - 50));
int radius = rng.uniform(10, 50);
circle(img, center, radius, Scalar::all(255), FILLED);
}
for (int i = 0; i < 3; ++i)
{
Point pts[3];
for (auto& p : pts)
p = Point(rng.uniform(10, sz.width - 10),
rng.uniform(10, sz.height - 10));
const Point* ppts = pts;
int npts = 3;
fillPoly(img, &ppts, &npts, 1, Scalar::all(255));
}
vector<vector<Point>> ref_contours;
vector<Vec4i> hierarchy;
findContours(img, ref_contours, hierarchy, RETR_LIST, method);//will call suzuki abe because using hierarchy
EXPECT_TRUE(!hierarchy.empty());
vector<vector<Point>> truco_contours;
findContours(img, truco_contours, RETR_LIST, method);//will use TRUCO because NOT using hierarchy AND RETR_LIST
EXPECT_TRUE(trucoContoursMatch(ref_contours, truco_contours));
}
}
INSTANTIATE_TEST_CASE_P(Imgproc, Imgproc_FindTRUContours,
testing::Values(CHAIN_APPROX_NONE,CHAIN_APPROX_SIMPLE, CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS));
}} // namespace
+203
View File
@@ -1247,4 +1247,207 @@ INSTANTIATE_TEST_CASE_P(/**/, Imgproc_sepFilter2D_types,
testing::Values(CV_16S, CV_32F, CV_64F),
);
// Verify that the tiled parallel FilterEngine path produces bit-exact results
// compared to the sequential (single-threaded) path for large images.
typedef tuple<Size, int, int, int> ParallelFilterParams;
typedef TestWithParam<ParallelFilterParams> ImgProc_ParallelFilter;
static void runFilter(const Mat& src, Mat& dst, int borderType, bool isSep)
{
if (isSep)
{
Mat kx = (Mat_<float>(1, 3) << 0.25f, 0.5f, 0.25f);
Mat ky = (Mat_<float>(3, 1) << 0.25f, 0.5f, 0.25f);
cv::sepFilter2D(src, dst, -1, kx, ky, Point(-1, -1), 0, borderType);
}
else
{
Mat kernel = (Mat_<float>(3, 3) <<
1/16.f, 2/16.f, 1/16.f,
2/16.f, 4/16.f, 2/16.f,
1/16.f, 2/16.f, 1/16.f);
cv::filter2D(src, dst, -1, kernel, Point(-1, -1), 0, borderType);
}
}
class ScopedThreadsGuard
{
public:
ScopedThreadsGuard() : old_threads(getNumThreads()) {}
~ScopedThreadsGuard() { setNumThreads(old_threads); }
void set(int n) { setNumThreads(n); }
private:
int old_threads;
};
TEST_P(ImgProc_ParallelFilter, accuracy)
{
const Size sz = get<0>(GetParam());
const int type = get<1>(GetParam());
const int borderType = get<2>(GetParam());
const bool isSep = get<3>(GetParam()) != 0;
Mat src(sz, type);
randu(src, 0, 256);
ScopedThreadsGuard threadsGuard;
const int prev_threads = getNumThreads();
// Parallel run — use at least 2 threads to exercise the tiled path.
threadsGuard.set(std::max(2, prev_threads));
Mat dst_par;
runFilter(src, dst_par, borderType, isSep);
// Sequential reference.
threadsGuard.set(1);
Mat dst_seq;
runFilter(src, dst_seq, borderType, isSep);
Mat diff;
double max_err = 0;
absdiff(dst_par, dst_seq, diff);
minMaxLoc(diff.reshape(1), nullptr, &max_err);
EXPECT_EQ(0.0, max_err) << "Parallel and sequential filter results differ";
}
INSTANTIATE_TEST_CASE_P(FullImage, ImgProc_ParallelFilter,
Combine(
Values(Size(1200, 1200), Size(2000, 1000)),
Values(CV_8UC1, CV_8UC3, CV_32FC1),
Values(BORDER_DEFAULT, BORDER_CONSTANT),
Values(0, 1) // 0 = filter2D, 1 = sepFilter2D
)
);
// Verify compound morphological operations (in-place second pass) are bitexact.
TEST(ImgProc_ParallelFilter, morphology_compound)
{
const Size sz(1920, 1080);
const int types[] = { CV_8UC1, CV_8UC3 };
const int morphOps[] = { MORPH_OPEN, MORPH_CLOSE, MORPH_TOPHAT, MORPH_BLACKHAT };
for (int ti = 0; ti < 2; ti++)
{
Mat src(sz, types[ti]);
randu(src, 0, 256);
for (int oi = 0; oi < 4; oi++)
{
ScopedThreadsGuard threadsGuard;
threadsGuard.set(std::max(2, getNumThreads()));
Mat dst_par;
cv::morphologyEx(src, dst_par, morphOps[oi], Mat());
threadsGuard.set(1);
Mat dst_seq;
cv::morphologyEx(src, dst_seq, morphOps[oi], Mat());
Mat diff;
double max_err = 0;
absdiff(dst_par, dst_seq, diff);
minMaxLoc(diff.reshape(1), nullptr, &max_err);
EXPECT_EQ(0.0, max_err)
<< "morphOp=" << morphOps[oi]
<< " type=" << types[ti]
<< ": parallel vs sequential results differ";
}
}
}
// Regression test for ndsrvp HAL filter padding robustness:
// Exercises extreme-but-valid anchor positions with small images and large kernels
// to ensure HAL implementations handle boundary-dominated padding correctly.
TEST(Imgproc_Filter2D, padding_bounds_extreme_anchor)
{
// Case 1: 1x1 image, large kernel, anchor at far right
{
Mat src = (Mat_<uchar>(1, 1) << 128);
Mat kernel = Mat::ones(1, 7, CV_32F) / 7.0f;
Mat dst;
Point anchor(6, 0);
EXPECT_NO_THROW(cv::filter2D(src, dst, -1, kernel, anchor, 0, BORDER_REPLICATE));
EXPECT_EQ(dst.size(), src.size());
EXPECT_NEAR(dst.at<uchar>(0, 0), 128, 1);
}
// Case 2: 1x1 image, large kernel, anchor at far left
{
Mat src = (Mat_<uchar>(1, 1) << 200);
Mat kernel = Mat::ones(1, 9, CV_32F) / 9.0f;
Mat dst;
Point anchor(0, 0);
EXPECT_NO_THROW(cv::filter2D(src, dst, -1, kernel, anchor, 0, BORDER_REPLICATE));
EXPECT_EQ(dst.size(), src.size());
EXPECT_NEAR(dst.at<uchar>(0, 0), 200, 1);
}
// Case 3: 2x2 image, 11x11 kernel, various anchors
{
Mat src = (Mat_<uchar>(2, 2) << 100, 150, 200, 250);
Mat kernel = Mat::ones(11, 11, CV_32F) / 121.0f;
Mat dst;
for (int ax : {0, 5, 10}) {
for (int ay : {0, 5, 10}) {
Point anchor(ax, ay);
EXPECT_NO_THROW(cv::filter2D(src, dst, -1, kernel, anchor, 0, BORDER_REPLICATE));
EXPECT_EQ(dst.size(), src.size());
}
}
}
// Case 4: ROI near edge of larger image (non-zero offset)
{
Mat full(10, 10, CV_8UC1, Scalar(100));
Mat roi = full(Rect(8, 8, 2, 2));
Mat kernel = Mat::ones(5, 5, CV_32F) / 25.0f;
Mat dst;
EXPECT_NO_THROW(cv::filter2D(roi, dst, -1, kernel, Point(4, 4), 0, BORDER_REPLICATE));
EXPECT_EQ(dst.size(), roi.size());
EXPECT_NO_THROW(cv::filter2D(roi, dst, -1, kernel, Point(0, 0), 0, BORDER_REPLICATE));
EXPECT_EQ(dst.size(), roi.size());
}
// Case 5: all border types with all valid anchors for wide kernel on narrow image
{
Mat src = (Mat_<uchar>(1, 3) << 10, 20, 30);
Mat kernel = Mat::ones(1, 15, CV_32F) / 15.0f;
Mat dst;
int borderTypes[] = {BORDER_REPLICATE, BORDER_REFLECT, BORDER_REFLECT_101, BORDER_CONSTANT};
for (int bt : borderTypes) {
for (int ax = 0; ax < 15; ax++) {
EXPECT_NO_THROW(cv::filter2D(src, dst, -1, kernel, Point(ax, 0), 0, bt))
<< "borderType=" << bt << " anchor_x=" << ax;
EXPECT_EQ(dst.size(), src.size());
}
}
}
}
// Regression test: small ROI with BORDER_ISOLATED and kernel larger than ROI width.
// The HAL must handle the case where border regions dominate the center span.
TEST(Imgproc_Filter2D, padding_bounds_roi_isolated)
{
Mat full(20, 20, CV_8UC1, Scalar(100));
Mat roi = full(Rect(5, 5, 3, 3));
roi.setTo(Scalar(200));
Mat kernel = Mat::ones(7, 7, CV_32F) / 49.0f;
Mat dst;
for (int ax = 0; ax < 7; ax++) {
for (int ay = 0; ay < 7; ay++) {
EXPECT_NO_THROW(
cv::filter2D(roi, dst, -1, kernel, Point(ax, ay), 0,
BORDER_REPLICATE | BORDER_ISOLATED))
<< "anchor=(" << ax << "," << ay << ")";
EXPECT_EQ(dst.size(), roi.size());
double minv, maxv;
minMaxLoc(dst, &minv, &maxv);
EXPECT_NEAR(minv, 200, 1) << "anchor=(" << ax << "," << ay << ")";
EXPECT_NEAR(maxv, 200, 1) << "anchor=(" << ax << "," << ay << ")";
}
}
}
}} // namespace
+1
View File
@@ -18,6 +18,7 @@ endforeach(m)
# header blacklist
ocv_list_filterout(opencv_hdrs "modules/.*.h$")
ocv_list_filterout(opencv_hdrs "modules/calib3d/include/opencv2/calib3d/private.hpp")
ocv_list_filterout(opencv_hdrs "modules/core/include/opencv2/core/fast_math.hpp")
ocv_list_filterout(opencv_hdrs "modules/core/.*/cuda")
ocv_list_filterout(opencv_hdrs "modules/core/.*/opencl")
+69
View File
@@ -0,0 +1,69 @@
This is the documentation for the iOS OpenCV framework.
## Quick Start Tutorial
Follow the steps below to get a simple "Hello World" app running on iOS
* Open Xcode and create a new project from the **File** > **New** > **Project...** menu
![New Project menu](common-create-project.png)
* From template selection dialog select the `iOS` tab and then select `App`
![Template selection dialog](ios-create-app.png)
* Enter a name for the project and set **Interface** to **Storyboard** and **Language** to **Swift**
![App settings dialog](ios-create-app-settings.png)
* Choose a location to save the project
![Choose project location dialog](common-create-app-location.png)
* Create a new folder `Framework`
![Create new folder menu](ios-create-framework-folder.png)
* In a Finder window copy the framework file to the newly created folder. The framework should appear in the project navigator as below. (Note: in this case the framework has extension `.framework` but depending on how you built it or from where you obtained it your framework may have extension `.xcframework`)
![Add framework](ios-add-framework.png)
* In the project navigator select the `ViewController.swift` file
![Select ViewController file](ios-select-viewcontroller.png)
* Add an `import` for the OpenCV framework below the existing import for `UIKit`
```swift, copy
import opencv2
```
* Replace the implementation of the `viewDidLoad` function with the following code
```swift, copy
override func viewDidLoad() {
super.viewDidLoad()
let white = Scalar(255, 255, 255, 255)
let black = Scalar(0, 0, 0, 255)
let m = Mat(rows: 400, cols: Int32(view.frame.width), type: CvType.CV_8UC4, scalar: white)
Imgproc.putText(img: m, text: "Hello World", org: Point(x: 10, y: 150), fontFace: HersheyFonts.FONT_HERSHEY_DUPLEX, fontScale: 2, color: black)
let image = m.toUIImage()
let imageView = UIImageView(image: image)
self.view.addSubview(imageView)
}
```
* The `ViewController.swift` file should now look like this
![Updated ViewController code](ios-add-opencv-code.png)
* Launch the app
![Launch app button](ios-launch-app.png)
* The app home screen should look like this
![App screenshot](ios-app-screenshot.png)
If that has all worked well then congratulations. If not then feel free to reach out to the OpenCV community for help. Also feel free to contribute fixes and improvements.
+85
View File
@@ -0,0 +1,85 @@
This is the documentation for the macOS OpenCV framework.
## Quick Start Tutorial
Follow the steps below to get a simple "Hello World" app running on macOS
* Open Xcode and create a new project from the **File** > **New** > **Project...** menu
![New Project menu](common-create-project.png)
* From the template selection dialog select the **macOS** tab and then select **App**
![Template selection dialog](macos-create-app.png)
* Enter a name for the project and set **Interface** to **Storyboard** and **Language** to **Swift**
![App settings dialog](macos-create-app-settings.png)
* Choose a location to save the project
![Choose project location dialog](common-create-app-location.png)
* Create a new folder `Framework`
![Create new folder menu](macos-create-framework-folder.png)
* In a Finder window copy the framework file to the newly created folder. The framework should appear in the project navigator as below. (Note: in this case the framework has extension `.framework` but depending on how you built it or from where you obtained it your framework may have extension `.xcframework`)
![Add framework](macos-add-framework.png)
* Select the project root in the project navigator and on the **General** tab locate the **Frameworks, Libraries and Embedded Content** section
![Frameworks, Libraries and Embedded Content](macos-add-dependencies-1.png)
* In the **Choose frameworks and libraries to add** dialog add the following
* OpenCL.framework
* liblapack.tbd
* libblas.tbd
![Choose frameworks and libraries to add](macos-add-dependencies-2.png)
* The **Frameworks, Libraries and Embedded Content** section should now look like this
![Choose frameworks and libraries to add](macos-add-dependencies-3.png)
* In the project navigator select the `ViewController.swift` file
![Select ViewController file](macos-select-viewcontroller.png)
* Add an `import` for the OpenCV framework below the existing import for `Cocoa`
```swift, copy
import opencv2
```
* Replace the implementation of the `viewDidLoad` function with the following code
```swift, copy
override func viewDidLoad() {
super.viewDidLoad()
let white = Scalar(255, 255, 255, 255)
let black = Scalar(0, 0, 0, 255)
let m = Mat(rows: 400, cols: Int32(view.frame.width), type: CvType.CV_8UC4, scalar: white)
Imgproc.putText(img: m, text: "Hello World", org: Point(x: 10, y: 250), fontFace: HersheyFonts.FONT_HERSHEY_DUPLEX, fontScale: 2, color: black)
let image = m.toNSImage()
let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: Int(m.cols()), height: Int(m.rows())))
imageView.image = image
self.view.addSubview(imageView)
}
```
* The `ViewController.swift` file should now look like this
![Updated ViewController code](macos-add-opencv-code.png)
* Launch the app
![Launch app button](macos-launch-app.png)
* The app home screen should look like this
![App screenshot](macos-app-screenshot.png)
If that has all worked well then congratulations. If not then feel free to reach out to the OpenCV community for help. Also feel free to contribute fixes and improvements.
-13
View File
@@ -1,13 +0,0 @@
## About
This is the documentation for the Objective-C/Swift OpenCV wrapper
To get started: add the OpenCV framework to your project and add the following to your source
###Objective-C
#import <OpenCV/OpenCV.h>
###Swift
import OpenCV
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+63 -14
View File
@@ -100,6 +100,9 @@ method_dict = {
("Mat", "dot") : "-dot:"
}
enum_value_lookup = {}
enums = set()
modules = []
@@ -709,12 +712,25 @@ def see_lookup(objc_class, see):
if (see_class, see_method) in method_dict:
method = method_dict[(see_class, see_method)]
if see_class == objc_class:
return method
return "``{}``".format(method[1:])
else:
return ("-" if method[0] == "-" else "") + "[" + see_class + " " + method[1:] + "]"
return "``{}/{}``".format(see_class, method[1:])
else:
return see
def hash_link(link_text, objc_class, function_name):
if link_text == function_name:
return "*{}*".format(link_text) # make bold
elif link_text in enums:
return "``{}``".format(link_text)
elif link_text in enum_value_lookup:
return "``{}/{}``".format(enum_value_lookup[link_text], link_text)
elif (objc_class, link_text) in method_dict:
return "``{}/{}``".format(objc_class, link_text)
elif ("Core", link_text) in method_dict:
return "``Core/{}``".format(link_text)
else:
return "#{}".format(link_text) # leave as is
class ObjectiveCWrapperGenerator(object):
def __init__(self):
@@ -1116,13 +1132,16 @@ class ObjectiveCWrapperGenerator(object):
if fi.docstring:
lines = fi.docstring.splitlines()
toWrite = []
last_line_was_param = False
for index, line in enumerate(lines):
line = re.sub(r"([(\s])#(.+?)([).,\s]|$)", lambda x: x.group(1) + hash_link(x.group(2), ci.objc_name, fi.name) + x.group(3), line)
p0 = line.find("@param")
if p0 != -1:
p0 += 7 # len("@param" + 1)
p1 = line.find(' ', p0)
p1 = len(line) if p1 == -1 else p1
name = line[p0:p1]
last_line_was_param = True
for arg in args:
if arg.name == name:
toWrite.append(re.sub(r'\*\s*@param ', '* @param ', line))
@@ -1130,10 +1149,14 @@ class ObjectiveCWrapperGenerator(object):
else:
s0 = line.find("@see")
if s0 != -1:
if last_line_was_param:
# separate from @param lines
toWrite.append(" *")
sees = line[(s0 + 5):].split(",")
toWrite.append(line[:(s0 + 5)] + ", ".join(["`" + see_lookup(ci.objc_name, see.strip()) + "`" for see in sees]))
toWrite.append(line[:s0] + "- SeeAlso " + ", ".join([see_lookup(ci.objc_name, see.strip()) for see in sees]))
else:
toWrite.append(line)
last_line_was_param = False
for line in toWrite:
method_declarations.write(line + "\n")
@@ -1318,6 +1341,10 @@ $unrefined_call$epilogue$ret
if ci.cname in enum_fix:
typeNameShort = enum_fix[ci.cname].get(typeNameShort, typeNameShort)
enums.add(typeNameShort)
for c in consts:
enum_value_lookup[c.name] = typeNameShort
ci.enum_declarations.write("""
// C++: enum {1} ({2})
typedef NS_ENUM(int, {1}) {{
@@ -1463,16 +1490,37 @@ typedef NS_ENUM(int, {1}) {{
self.save(opencv_modulemap_file, opencv_modulemap)
available_modules = " ".join(["-DAVAILABLE_" + m['name'].upper() for m in config['modules']])
cmakelist_template = read_contents(os.path.join(SCRIPT_DIR, 'templates/cmakelists.template'))
cmakelist = Template(cmakelist_template).substitute(modules = ";".join(modules), framework = framework_name, objc_target=objc_target, module_availability_defines=available_modules)
doc_catalog = output_objc_path + "/../Documentation.docc"
doc_resources = os.path.join(doc_catalog, "Resources")
cmakelist = Template(cmakelist_template).substitute(
modules = ";".join(modules),
framework = framework_name,
objc_target = objc_target,
module_availability_defines = available_modules,
)
self.save(os.path.join(dstdir, "CMakeLists.txt"), cmakelist)
mkdir_p(doc_resources)
mkdir_p(os.path.join(output_objc_build_path, "framework_build"))
mkdir_p(os.path.join(output_objc_build_path, "test_build"))
mkdir_p(os.path.join(output_objc_build_path, "doc_build"))
with open(os.path.join(SCRIPT_DIR, '../doc/README.md')) as readme_in:
readme_body = readme_in.read()
readme_body += "\n\n\n##Modules\n\n" + ", ".join(["`" + m.capitalize() + "`" for m in modules])
with open(os.path.join(output_objc_build_path, "doc_build/README.md"), "w") as readme_out:
readme_out.write(readme_body)
landing_page_body_in = ""
if objc_target == "ios" or objc_target == "osx":
with open(os.path.join(SCRIPT_DIR, '../doc/LandingPage_' + objc_target + '.md')) as landing_page_in:
landing_page_body_in = landing_page_in.read()
landing_page_body_out = "# ``" + framework_name + "`` Framework\n\n" + landing_page_body_in
landing_page_body_out += "\n\n\n## Modules\n\n" + ", ".join(["``" + m.capitalize() + "``" for m in modules]) + "\n"
with open(os.path.join(doc_catalog, "LandingPage.md"), "w") as landing_page_out:
landing_page_out.write(landing_page_body_out)
for m in config['modules']:
pic_files = os.path.join(ROOT_DIR, m['location'], 'doc', 'pics')
if os.path.exists(pic_files):
copy_tree(pic_files, doc_resources)
if objc_target == "ios":
copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/ios'), doc_resources)
if objc_target == "osx":
copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/osx'), doc_resources)
if objc_target == "ios" or objc_target == "osx":
copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/common'), doc_resources)
if framework_name != "OpenCV":
for dirname, dirs, files in os.walk(os.path.join(testdir, "test")):
if dirname.endswith('/resources'):
@@ -1542,10 +1590,11 @@ def sanitize_documentation_string(doc, type):
doc = re.sub(re.compile('\\\\f\\$(.*?)\\\\f\\$', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(re.compile('\\\\f\\[(.*?)\\\\f\\]', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(re.compile('\\\\f\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(r"!\[(.*)]\((?:.*/)*(.+)\.(?:png|jpg|svg)\)", "![\\1](\\2)", doc)
doc = doc.replace("@anchor", "") \
.replace("@brief ", "").replace("\\brief ", "") \
.replace("@cite", "CITE:") \
.replace("@cite", "*Cite:*") \
.replace("@code{.cpp}", "<code>") \
.replace("@code{.txt}", "<code>") \
.replace("@code", "<code>") \
@@ -1557,14 +1606,14 @@ def sanitize_documentation_string(doc, type):
.replace("@endcode", "</code>") \
.replace("@endinternal", "") \
.replace("@file", "") \
.replace("@include", "INCLUDE:") \
.replace("@include", "Include:") \
.replace("@ingroup", "") \
.replace("@internal", "") \
.replace("@overload", "") \
.replace("@param[in]", "@param") \
.replace("@param[out]", "@param") \
.replace("@ref", "REF:") \
.replace("@note", "NOTE:") \
.replace("@ref", "*Ref:*") \
.replace("@note", "*Note:*") \
.replace("@returns", "@return") \
.replace("@sa ", "@see ") \
.replace("@snippet", "SNIPPET:") \
@@ -22,7 +22,14 @@ else()
endif()
file(GLOB_RECURSE objc_headers "*\.h")
add_library($framework STATIC $${objc_sources})
set_source_files_properties(Documentation.docc PROPERTIES
XCODE_LAST_KNOWN_FILE_TYPE folder.documentationcatalog
)
add_library($framework STATIC
$${objc_sources}
Documentation.docc
)
set_target_properties($framework PROPERTIES LINKER_LANGUAGE CXX)
@@ -0,0 +1,11 @@
# $framework Documentation - How To View
## View Locally
* Start a web server in the directory where this HowTo file is located. For example ``python3 -m http.server 8000``
* In a browser navigate to URL http://127.0.0.1:8000/$hosting_base_path/documentation/$framework/
## Deploying to a server
* Upload the content of the directory where this HowTo file is located to the document root of the web server maintaining the same folder hierarchy
* In a browser navigate to URL https://{server-domain}/$hosting_base_path/documentation/$framework/
@@ -266,36 +266,42 @@ Mat Dictionary::getBitsFromByteList(const Mat &byteList, int markerSize, int rot
Dictionary getPredefinedDictionary(PredefinedDictionaryType name) {
// The maximum number of bits that can be corrected is theoretically (d-1)/2,
// where d is the minimum Hamming distance between any two codes in the dictionary.
// However, we use a more conservative limit (d/2)-1 to reduce the probability
// of false positives during detection. This formula is equivalent to the
// theoretical limit for even distances and stricter for odd distances.
// DictionaryData constructors calls
// moved out of globals so construted on first use, which allows lazy-loading of opencv dll
static const Dictionary DICT_ARUCO_DATA = Dictionary(Mat(1024, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_BYTES), 5, 0);
static const Dictionary DICT_ARUCO_DATA = Dictionary(Mat(1024, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_BYTES), 5, (3-1)/2);
static const Dictionary DICT_4X4_50_DATA = Dictionary(Mat(50, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
static const Dictionary DICT_4X4_100_DATA = Dictionary(Mat(100, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
static const Dictionary DICT_4X4_250_DATA = Dictionary(Mat(250, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 1);
static const Dictionary DICT_4X4_1000_DATA = Dictionary(Mat(1000, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, 0);
static const Dictionary DICT_4X4_50_DATA = Dictionary(Mat(50, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (4-1)/2);
static const Dictionary DICT_4X4_100_DATA = Dictionary(Mat(100, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (3-1)/2);
static const Dictionary DICT_4X4_250_DATA = Dictionary(Mat(250, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (3-1)/2);
static const Dictionary DICT_4X4_1000_DATA = Dictionary(Mat(1000, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_4X4_1000_BYTES), 4, (2-1)/2);
static const Dictionary DICT_5X5_50_DATA = Dictionary(Mat(50, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
static const Dictionary DICT_5X5_100_DATA = Dictionary(Mat(100, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 3);
static const Dictionary DICT_5X5_250_DATA = Dictionary(Mat(250, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
static const Dictionary DICT_5X5_1000_DATA = Dictionary(Mat(1000, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, 2);
static const Dictionary DICT_5X5_50_DATA = Dictionary(Mat(50, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (8-1)/2);
static const Dictionary DICT_5X5_100_DATA = Dictionary(Mat(100, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (7-1)/2);
static const Dictionary DICT_5X5_250_DATA = Dictionary(Mat(250, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (6-1)/2);
static const Dictionary DICT_5X5_1000_DATA = Dictionary(Mat(1000, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_5X5_1000_BYTES), 5, (5-1)/2);
static const Dictionary DICT_6X6_50_DATA = Dictionary(Mat(50, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 6);
static const Dictionary DICT_6X6_100_DATA = Dictionary(Mat(100, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
static const Dictionary DICT_6X6_250_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 5);
static const Dictionary DICT_6X6_1000_DATA = Dictionary(Mat(1000, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, 4);
static const Dictionary DICT_6X6_50_DATA = Dictionary(Mat(50, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (13-1)/2);
static const Dictionary DICT_6X6_100_DATA = Dictionary(Mat(100, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (12-1)/2);
static const Dictionary DICT_6X6_250_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (11-1)/2);
static const Dictionary DICT_6X6_1000_DATA = Dictionary(Mat(1000, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_6X6_1000_BYTES), 6, (9-1)/2);
static const Dictionary DICT_7X7_50_DATA = Dictionary(Mat(50, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 9);
static const Dictionary DICT_7X7_100_DATA = Dictionary(Mat(100, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
static const Dictionary DICT_7X7_250_DATA = Dictionary(Mat(250, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 8);
static const Dictionary DICT_7X7_1000_DATA = Dictionary(Mat(1000, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, 6);
static const Dictionary DICT_7X7_50_DATA = Dictionary(Mat(50, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (19-1)/2);
static const Dictionary DICT_7X7_100_DATA = Dictionary(Mat(100, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (18-1)/2);
static const Dictionary DICT_7X7_250_DATA = Dictionary(Mat(250, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (17-1)/2);
static const Dictionary DICT_7X7_1000_DATA = Dictionary(Mat(1000, (7 * 7 + 7) / 8, CV_8UC4, (uchar*)DICT_7X7_1000_BYTES), 7, (14-1)/2);
static const Dictionary DICT_APRILTAG_16h5_DATA = Dictionary(Mat(30, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_16h5_BYTES), 4, 0);
static const Dictionary DICT_APRILTAG_25h9_DATA = Dictionary(Mat(35, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_25h9_BYTES), 5, 0);
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, 0);
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, 0);
static const Dictionary DICT_APRILTAG_16h5_DATA = Dictionary(Mat(30, (4 * 4 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_16h5_BYTES), 4, (5-1)/2);
static const Dictionary DICT_APRILTAG_25h9_DATA = Dictionary(Mat(35, (5 * 5 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_25h9_BYTES), 5, (9-1)/2);
static const Dictionary DICT_APRILTAG_36h10_DATA = Dictionary(Mat(2320, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h10_BYTES), 6, (10-1)/2);
static const Dictionary DICT_APRILTAG_36h11_DATA = Dictionary(Mat(587, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_APRILTAG_36h11_BYTES), 6, (11-1)/2);
static const Dictionary DICT_ARUCO_MIP_36h12_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_MIP_36h12_BYTES), 6, 12);
static const Dictionary DICT_ARUCO_MIP_36h12_DATA = Dictionary(Mat(250, (6 * 6 + 7) / 8, CV_8UC4, (uchar*)DICT_ARUCO_MIP_36h12_BYTES), 6, (12-1)/2);
switch(name) {
@@ -141,4 +141,34 @@ bool pyopencv_to(PyObject* obj, Ptr<cv::IStreamReader>& p, const ArgInfo&)
return false;
}
static PyObject* pycvVideoEnter(PyObject* self, PyObject* /*args*/, PyObject* /*kw*/) {
Py_INCREF(self);
return self;
}
static PyObject* pycvVideoCaptureExit(PyObject* self, PyObject* /*args*/, PyObject* /*kw*/) {
Ptr<cv::VideoCapture>* obj_getp = nullptr;
if (pyopencv_VideoCapture_getp(self, obj_getp) && obj_getp && *obj_getp) {
(*obj_getp)->release();
}
Py_RETURN_NONE;
}
static PyObject* pycvVideoWriterExit(PyObject* self, PyObject* /*args*/, PyObject* /*kw*/) {
Ptr<cv::VideoWriter>* obj_getp = nullptr;
if (pyopencv_VideoWriter_getp(self, obj_getp) && obj_getp && *obj_getp) {
(*obj_getp)->release();
}
Py_RETURN_NONE;
}
#define PYOPENCV_EXTRA_METHODS_VideoCapture \
{"__enter__", CV_PY_FN_WITH_KW(pycvVideoEnter), "Context manager enter"}, \
{"__exit__", CV_PY_FN_WITH_KW(pycvVideoCaptureExit), "Context manager exit"},
#define PYOPENCV_EXTRA_METHODS_VideoWriter \
{"__enter__", CV_PY_FN_WITH_KW(pycvVideoEnter), "Context manager enter"}, \
{"__exit__", CV_PY_FN_WITH_KW(pycvVideoWriterExit), "Context manager exit"},
#endif // HAVE_OPENCV_VIDEOIO
@@ -5,6 +5,7 @@ import numpy as np
import cv2 as cv
import io
import sys
import tempfile
from tests_common import NewOpenCVTests
@@ -87,5 +88,22 @@ class Bindings(NewOpenCVTests):
self.assertTrue(hasFrame)
self.assertEqual(frame.shape, (576, 768, 3))
def test_context_manager(self):
video_file = self.find_file("cv/video/768x576.avi")
with cv.VideoCapture(video_file) as cap:
self.assertTrue(cap.isOpened(), "VideoCapture should be opened within context manager")
with tempfile.NamedTemporaryFile(suffix='.avi') as tmp:
with cv.VideoWriter(tmp.name, cv.VideoWriter_fourcc(*'MJPG'), 25, (640, 480)) as writer:
self.assertTrue(isinstance(writer, cv.VideoWriter))
try:
with cv.VideoCapture(video_file) as cap:
self.assertTrue(cap.isOpened())
raise RuntimeError("Testing context manager exception safety")
except RuntimeError:
pass
if __name__ == '__main__':
NewOpenCVTests.bootstrap()
+7 -1
View File
@@ -2756,7 +2756,13 @@ int videoInput::start(int deviceID, videoDevice *VD){
}
VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER*>(VD->pAmMediaType->pbFormat);
CV_Assert(pVih);
// Some legacy or virtual cameras (e.g., Microsoft Ball filter) return S_OK
// but leave pbFormat as NULL. We check for NULL here to avoid a crash.
// https://github.com/opencv/opencv/issues/28904
if (pVih == NULL) {
DebugPrintOut("ERROR: pbFormat field is not set!\n");
return false;
}
int currentWidth = HEADER(pVih)->biWidth;
int currentHeight = HEADER(pVih)->biHeight;
+4 -1
View File
@@ -335,7 +335,10 @@ public:
// Workaround for some gstreamer pipelines
if (apiPref == CAP_GSTREAMER)
{
expected_frame_count.start -= 1;
expected_frame_count.end += 1;
}
ASSERT_LE(expected_frame_count.start, actual);
ASSERT_GE(expected_frame_count.end, actual);
@@ -915,7 +918,7 @@ TEST_P(videowriter_acceleration, write)
throw SkipTestException(cv::String("Backend is not available/disabled: ") + backend_name);
#ifdef __linux__
if (cvtest::skipUnstableTests && backend == CAP_GSTREAMER &&
(extension == "mkv") && (codecid == "MPEG"))
(extension == "mkv") && (codecid == "MPEG" || codecid == "H264"))
{
throw SkipTestException("Unstable GSTREAMER test");
}
+1 -1
View File
@@ -84,7 +84,7 @@ endif()
ocv_target_compile_definitions(${the_module} PRIVATE OPENCV_MODULE_IS_PART_OF_WORLD=1)
# NOTE: https://github.com/opencv/opencv/issues/25543
if (WITH_QT)
if (WITH_QT AND QT_VERSION_MAJOR GREATER 5)
qt_disable_unicode_defines(${the_module})
endif()