From 0451629e22938dc1054581005d747721b8ac67b9 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 25 Aug 2017 18:55:31 +0300 Subject: [PATCH] core(persistence): resolve DMatch/KeyPoint problem --- .../core/include/opencv2/core/persistence.hpp | 106 ++++---- modules/core/src/persistence.cpp | 63 +++-- modules/core/test/test_io.cpp | 246 +++++++++++++++++- 3 files changed, 332 insertions(+), 83 deletions(-) diff --git a/modules/core/include/opencv2/core/persistence.hpp b/modules/core/include/opencv2/core/persistence.hpp index e0a6efa465..6746be91eb 100644 --- a/modules/core/include/opencv2/core/persistence.hpp +++ b/modules/core/include/opencv2/core/persistence.hpp @@ -44,6 +44,11 @@ #ifndef OPENCV_CORE_PERSISTENCE_HPP #define OPENCV_CORE_PERSISTENCE_HPP +#ifndef CV_DOXYGEN +/// Define to support persistence legacy formats +#define CV__LEGACY_PERSISTENCE +#endif + #ifndef __cplusplus # error persistence.hpp header must be compiled as C++ #endif @@ -700,8 +705,10 @@ CV_EXPORTS void write( FileStorage& fs, const String& name, double value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const String& value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const Mat& value ); CV_EXPORTS void write( FileStorage& fs, const String& name, const SparseMat& value ); +#ifdef CV__LEGACY_PERSISTENCE CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector& value); CV_EXPORTS void write( FileStorage& fs, const String& name, const std::vector& value); +#endif CV_EXPORTS void writeScalar( FileStorage& fs, int value ); CV_EXPORTS void writeScalar( FileStorage& fs, float value ); @@ -720,8 +727,12 @@ CV_EXPORTS void read(const FileNode& node, String& value, const String& default_ CV_EXPORTS void read(const FileNode& node, std::string& value, const std::string& default_value); CV_EXPORTS void read(const FileNode& node, Mat& mat, const Mat& default_mat = Mat() ); CV_EXPORTS void read(const FileNode& node, SparseMat& mat, const SparseMat& default_mat = SparseMat() ); +#ifdef CV__LEGACY_PERSISTENCE CV_EXPORTS void read(const FileNode& node, std::vector& keypoints); CV_EXPORTS void read(const FileNode& node, std::vector& matches); +#endif +CV_EXPORTS void read(const FileNode& node, KeyPoint& value, const KeyPoint& default_value); +CV_EXPORTS void read(const FileNode& node, DMatch& value, const DMatch& default_value); template static inline void read(const FileNode& node, Point_<_Tp>& value, const Point_<_Tp>& default_value) { @@ -948,27 +959,6 @@ void write(FileStorage& fs, const Scalar_<_Tp>& s ) write(fs, s.val[3]); } -static inline -void write(FileStorage& fs, const KeyPoint& kpt ) -{ - write(fs, kpt.pt.x); - write(fs, kpt.pt.y); - write(fs, kpt.size); - write(fs, kpt.angle); - write(fs, kpt.response); - write(fs, kpt.octave); - write(fs, kpt.class_id); -} - -static inline -void write(FileStorage& fs, const DMatch& m ) -{ - write(fs, m.queryIdx); - write(fs, m.trainIdx); - write(fs, m.imgIdx); - write(fs, m.distance); -} - static inline void write(FileStorage& fs, const Range& r ) { @@ -976,26 +966,6 @@ void write(FileStorage& fs, const Range& r ) write(fs, r.end); } -static inline -void write( FileStorage& fs, const std::vector& vec ) -{ - size_t npoints = vec.size(); - for(size_t i = 0; i < npoints; i++ ) - { - write(fs, vec[i]); - } -} - -static inline -void write( FileStorage& fs, const std::vector& vec ) -{ - size_t npoints = vec.size(); - for(size_t i = 0; i < npoints; i++ ) - { - write(fs, vec[i]); - } -} - template static inline void write( FileStorage& fs, const std::vector<_Tp>& vec ) { @@ -1060,17 +1030,26 @@ void write(FileStorage& fs, const String& name, const Range& r ) } static inline -void write(FileStorage& fs, const String& name, const KeyPoint& r ) +void write(FileStorage& fs, const String& name, const KeyPoint& kpt) { cv::internal::WriteStructContext ws(fs, name, FileNode::SEQ+FileNode::FLOW); - write(fs, r); + write(fs, kpt.pt.x); + write(fs, kpt.pt.y); + write(fs, kpt.size); + write(fs, kpt.angle); + write(fs, kpt.response); + write(fs, kpt.octave); + write(fs, kpt.class_id); } static inline -void write(FileStorage& fs, const String& name, const DMatch& r ) +void write(FileStorage& fs, const String& name, const DMatch& m) { cv::internal::WriteStructContext ws(fs, name, FileNode::SEQ+FileNode::FLOW); - write(fs, r); + write(fs, m.queryIdx); + write(fs, m.trainIdx); + write(fs, m.imgIdx); + write(fs, m.distance); } template static inline @@ -1091,6 +1070,24 @@ void write( FileStorage& fs, const String& name, const std::vector< std::vector< } } +#ifdef CV__LEGACY_PERSISTENCE +// This code is not needed anymore, but it is preserved here to keep source compatibility +// Implementation is similar to templates instantiations +static inline void write(FileStorage& fs, const KeyPoint& kpt) { write(fs, String(), kpt); } +static inline void write(FileStorage& fs, const DMatch& m) { write(fs, String(), m); } +static inline void write(FileStorage& fs, const std::vector& vec) +{ + cv::internal::VecWriterProxy w(&fs); + w(vec); +} +static inline void write(FileStorage& fs, const std::vector& vec) +{ + cv::internal::VecWriterProxy w(&fs); + w(vec); + +} +#endif + //! @} FileStorage //! @relates cv::FileNode @@ -1258,12 +1255,6 @@ void operator >> (const FileNode& n, std::vector<_Tp>& vec) /** @brief Reads KeyPoint from a file storage. */ //It needs special handling because it contains two types of fields, int & float. -static inline -void operator >> (const FileNode& n, std::vector& vec) -{ - read(n, vec); -} - static inline void operator >> (const FileNode& n, KeyPoint& kpt) { @@ -1271,15 +1262,22 @@ void operator >> (const FileNode& n, KeyPoint& kpt) it >> kpt.pt.x >> kpt.pt.y >> kpt.size >> kpt.angle >> kpt.response >> kpt.octave >> kpt.class_id; } -/** @brief Reads DMatch from a file storage. -*/ -//It needs special handling because it contains two types of fields, int & float. +#ifdef CV__LEGACY_PERSISTENCE +static inline +void operator >> (const FileNode& n, std::vector& vec) +{ + read(n, vec); +} static inline void operator >> (const FileNode& n, std::vector& vec) { read(n, vec); } +#endif +/** @brief Reads DMatch from a file storage. +*/ +//It needs special handling because it contains two types of fields, int & float. static inline void operator >> (const FileNode& n, DMatch& m) { diff --git a/modules/core/src/persistence.cpp b/modules/core/src/persistence.cpp index e2d223d4b0..f428063d70 100644 --- a/modules/core/src/persistence.cpp +++ b/modules/core/src/persistence.cpp @@ -7333,21 +7333,45 @@ void read( const FileNode& node, SparseMat& mat, const SparseMat& default_mat ) m->copyToSparseMat(mat); } -void write(FileStorage& fs, const String& objname, const std::vector& keypoints) +CV_EXPORTS void read(const FileNode& node, KeyPoint& value, const KeyPoint& default_value) { - cv::internal::WriteStructContext ws(fs, objname, CV_NODE_SEQ + CV_NODE_FLOW); - - int i, npoints = (int)keypoints.size(); - for( i = 0; i < npoints; i++ ) + if( node.empty() ) { - write(fs, keypoints[i]); + value = default_value; + return; } + node >> value; } +CV_EXPORTS void read(const FileNode& node, DMatch& value, const DMatch& default_value) +{ + if( node.empty() ) + { + value = default_value; + return; + } + node >> value; +} + +#ifdef CV__LEGACY_PERSISTENCE +void write( FileStorage& fs, const String& name, const std::vector& vec) +{ + // from template implementation + cv::internal::WriteStructContext ws(fs, name, FileNode::SEQ); + write(fs, vec); +} void read(const FileNode& node, std::vector& keypoints) { - keypoints.resize(0); + FileNode first_node = *(node.begin()); + if (first_node.isSeq()) + { + // modern scheme + FileNodeIterator it = node.begin(); + it >> keypoints; + return; + } + keypoints.clear(); FileNodeIterator it = node.begin(), it_end = node.end(); for( ; it != it_end; ) { @@ -7357,21 +7381,24 @@ void read(const FileNode& node, std::vector& keypoints) } } - -void write(FileStorage& fs, const String& objname, const std::vector& matches) +void write( FileStorage& fs, const String& name, const std::vector& vec) { - cv::internal::WriteStructContext ws(fs, objname, CV_NODE_SEQ + CV_NODE_FLOW); - - int i, n = (int)matches.size(); - for( i = 0; i < n; i++ ) - { - write(fs, matches[i]); - } + // from template implementation + cv::internal::WriteStructContext ws(fs, name, FileNode::SEQ); + write(fs, vec); } void read(const FileNode& node, std::vector& matches) { - matches.resize(0); + FileNode first_node = *(node.begin()); + if (first_node.isSeq()) + { + // modern scheme + FileNodeIterator it = node.begin(); + it >> matches; + return; + } + matches.clear(); FileNodeIterator it = node.begin(), it_end = node.end(); for( ; it != it_end; ) { @@ -7380,7 +7407,7 @@ void read(const FileNode& node, std::vector& matches) matches.push_back(m); } } - +#endif int FileNode::type() const { return !node ? NONE : (node->tag & TYPE_MASK); } bool FileNode::isNamed() const { return !node ? false : (node->tag & NAMED) != 0; } diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index a70b2aac98..8d1bb324b8 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -1146,15 +1146,19 @@ TEST(Core_InputOutput, FileStorage_DMatch_vector) EXPECT_STREQ(fs_result.c_str(), "%YAML:1.0\n" "---\n" -"dv: [ 1, 2, 3, -1.5000000000000000e+000, 2, 3, 4,\n" -" 1.5000000000000000e+000, 3, 2, 1, 5.0000000000000000e-001 ]\n" +"dv:\n" +" - [ 1, 2, 3, -1.5000000000000000e+000 ]\n" +" - [ 2, 3, 4, 1.5000000000000000e+000 ]\n" +" - [ 3, 2, 1, 5.0000000000000000e-001 ]\n" ); #else EXPECT_STREQ(fs_result.c_str(), "%YAML:1.0\n" "---\n" -"dv: [ 1, 2, 3, -1.5000000000000000e+00, 2, 3, 4, 1.5000000000000000e+00,\n" -" 3, 2, 1, 5.0000000000000000e-01 ]\n" +"dv:\n" +" - [ 1, 2, 3, -1.5000000000000000e+00 ]\n" +" - [ 2, 3, 4, 1.5000000000000000e+00 ]\n" +" - [ 3, 2, 1, 5.0000000000000000e-01 ]\n" ); #endif @@ -1200,19 +1204,26 @@ TEST(Core_InputOutput, FileStorage_DMatch_vector_vector) "%YAML:1.0\n" "---\n" "dvv:\n" -" - [ 1, 2, 3, -1.5000000000000000e+000, 2, 3, 4,\n" -" 1.5000000000000000e+000, 3, 2, 1, 5.0000000000000000e-001 ]\n" -" - [ 3, 2, 1, 5.0000000000000000e-001, 1, 2, 3,\n" -" -1.5000000000000000e+000 ]\n" +" -\n" +" - [ 1, 2, 3, -1.5000000000000000e+000 ]\n" +" - [ 2, 3, 4, 1.5000000000000000e+000 ]\n" +" - [ 3, 2, 1, 5.0000000000000000e-001 ]\n" +" -\n" +" - [ 3, 2, 1, 5.0000000000000000e-001 ]\n" +" - [ 1, 2, 3, -1.5000000000000000e+000 ]\n" ); #else EXPECT_STREQ(fs_result.c_str(), "%YAML:1.0\n" "---\n" "dvv:\n" -" - [ 1, 2, 3, -1.5000000000000000e+00, 2, 3, 4, 1.5000000000000000e+00,\n" -" 3, 2, 1, 5.0000000000000000e-01 ]\n" -" - [ 3, 2, 1, 5.0000000000000000e-01, 1, 2, 3, -1.5000000000000000e+00 ]\n" +" -\n" +" - [ 1, 2, 3, -1.5000000000000000e+00 ]\n" +" - [ 2, 3, 4, 1.5000000000000000e+00 ]\n" +" - [ 3, 2, 1, 5.0000000000000000e-01 ]\n" +" -\n" +" - [ 3, 2, 1, 5.0000000000000000e-01 ]\n" +" - [ 1, 2, 3, -1.5000000000000000e+00 ]\n" ); #endif @@ -1237,6 +1248,219 @@ TEST(Core_InputOutput, FileStorage_DMatch_vector_vector) } } + +TEST(Core_InputOutput, FileStorage_KeyPoint) +{ + cv::FileStorage fs("keypoint.xml", cv::FileStorage::WRITE | cv::FileStorage::MEMORY); + + cv::KeyPoint k(Point2f(1, 2), 16, 0, 100, 1, -1); + + EXPECT_NO_THROW(fs << "k" << k); + cv::String fs_result = fs.releaseAndGetString(); + EXPECT_STREQ(fs_result.c_str(), +"\n" +"\n" +"\n" +" 1. 2. 16. 0. 100. 1 -1\n" +"\n" +); + + cv::FileStorage fs_read(fs_result, cv::FileStorage::READ | cv::FileStorage::MEMORY); + + cv::KeyPoint k_read; + ASSERT_NO_THROW(fs_read["k"] >> k_read); + + EXPECT_EQ(k.pt, k_read.pt); + EXPECT_EQ(k.size, k_read.size); + EXPECT_EQ(k.angle, k_read.angle); + EXPECT_EQ(k.response, k_read.response); + EXPECT_EQ(k.octave, k_read.octave); + EXPECT_EQ(k.class_id, k_read.class_id); +} + +TEST(Core_InputOutput, FileStorage_KeyPoint_vector) +{ + cv::FileStorage fs("keypoint.xml", cv::FileStorage::WRITE | cv::FileStorage::MEMORY); + + cv::KeyPoint k1(Point2f(1, 2), 16, 0, 100, 1, -1); + cv::KeyPoint k2(Point2f(2, 3), 16, 45, 100, 1, -1); + cv::KeyPoint k3(Point2f(1, 2), 16, 90, 100, 1, -1); + std::vector kv; + kv.push_back(k1); + kv.push_back(k2); + kv.push_back(k3); + + EXPECT_NO_THROW(fs << "kv" << kv); + cv::String fs_result = fs.releaseAndGetString(); + EXPECT_STREQ(fs_result.c_str(), +"\n" +"\n" +"\n" +" <_>\n" +" 1. 2. 16. 0. 100. 1 -1\n" +" <_>\n" +" 2. 3. 16. 45. 100. 1 -1\n" +" <_>\n" +" 1. 2. 16. 90. 100. 1 -1\n" +"\n" +); + + cv::FileStorage fs_read(fs_result, cv::FileStorage::READ | cv::FileStorage::MEMORY); + + std::vector kv_read; + ASSERT_NO_THROW(fs_read["kv"] >> kv_read); + + ASSERT_EQ(kv.size(), kv_read.size()); + for (size_t i = 0; i < kv.size(); i++) + { + EXPECT_EQ(kv[i].pt, kv_read[i].pt); + EXPECT_EQ(kv[i].size, kv_read[i].size); + EXPECT_EQ(kv[i].angle, kv_read[i].angle); + EXPECT_EQ(kv[i].response, kv_read[i].response); + EXPECT_EQ(kv[i].octave, kv_read[i].octave); + EXPECT_EQ(kv[i].class_id, kv_read[i].class_id); + } +} + +TEST(Core_InputOutput, FileStorage_KeyPoint_vector_vector) +{ + cv::FileStorage fs("keypoint.xml", cv::FileStorage::WRITE | cv::FileStorage::MEMORY); + + cv::KeyPoint k1(Point2f(1, 2), 16, 0, 100, 1, -1); + cv::KeyPoint k2(Point2f(2, 3), 16, 45, 100, 1, -1); + cv::KeyPoint k3(Point2f(1, 2), 16, 90, 100, 1, -1); + std::vector kv1; + kv1.push_back(k1); + kv1.push_back(k2); + kv1.push_back(k3); + + std::vector kv2; + kv2.push_back(k3); + kv2.push_back(k1); + + std::vector< std::vector > kvv; + kvv.push_back(kv1); + kvv.push_back(kv2); + + EXPECT_NO_THROW(fs << "kvv" << kvv); + cv::String fs_result = fs.releaseAndGetString(); + EXPECT_STREQ(fs_result.c_str(), +"\n" +"\n" +"\n" +" <_>\n" +" <_>\n" +" 1. 2. 16. 0. 100. 1 -1\n" +" <_>\n" +" 2. 3. 16. 45. 100. 1 -1\n" +" <_>\n" +" 1. 2. 16. 90. 100. 1 -1\n" +" <_>\n" +" <_>\n" +" 1. 2. 16. 90. 100. 1 -1\n" +" <_>\n" +" 1. 2. 16. 0. 100. 1 -1\n" +"\n" +); + + cv::FileStorage fs_read(fs_result, cv::FileStorage::READ | cv::FileStorage::MEMORY); + + std::vector< std::vector > kvv_read; + ASSERT_NO_THROW(fs_read["kvv"] >> kvv_read); + + ASSERT_EQ(kvv.size(), kvv_read.size()); + for (size_t j = 0; j < kvv.size(); j++) + { + const std::vector& kv = kvv[j]; + const std::vector& kv_read = kvv_read[j]; + ASSERT_EQ(kvv.size(), kvv_read.size()); + for (size_t i = 0; i < kv.size(); i++) + { + EXPECT_EQ(kv[i].pt, kv_read[i].pt); + EXPECT_EQ(kv[i].size, kv_read[i].size); + EXPECT_EQ(kv[i].angle, kv_read[i].angle); + EXPECT_EQ(kv[i].response, kv_read[i].response); + EXPECT_EQ(kv[i].octave, kv_read[i].octave); + EXPECT_EQ(kv[i].class_id, kv_read[i].class_id); + } + } +} + + +#ifdef CV__LEGACY_PERSISTENCE +TEST(Core_InputOutput, FileStorage_LEGACY_DMatch_vector) +{ + cv::DMatch d1(1, 2, 3, -1.5f); + cv::DMatch d2(2, 3, 4, 1.5f); + cv::DMatch d3(3, 2, 1, 0.5f); + std::vector dv; + dv.push_back(d1); + dv.push_back(d2); + dv.push_back(d3); + + String fs_result = +"\n" +"\n" +"\n" +" 1 2 3 -1.5000000000000000e+00 2 3 4 1.5000000000000000e+00 3 2 1\n" +" 5.0000000000000000e-01\n" +"\n" + ; + + cv::FileStorage fs_read(fs_result, cv::FileStorage::READ | cv::FileStorage::MEMORY); + + std::vector dv_read; + ASSERT_NO_THROW(fs_read["dv"] >> dv_read); + + ASSERT_EQ(dv.size(), dv_read.size()); + for (size_t i = 0; i < dv.size(); i++) + { + EXPECT_EQ(dv[i].queryIdx, dv_read[i].queryIdx); + EXPECT_EQ(dv[i].trainIdx, dv_read[i].trainIdx); + EXPECT_EQ(dv[i].imgIdx, dv_read[i].imgIdx); + EXPECT_EQ(dv[i].distance, dv_read[i].distance); + } +} + + +TEST(Core_InputOutput, FileStorage_LEGACY_KeyPoint_vector) +{ + cv::KeyPoint k1(Point2f(1, 2), 16, 0, 100, 1, -1); + cv::KeyPoint k2(Point2f(2, 3), 16, 45, 100, 1, -1); + cv::KeyPoint k3(Point2f(1, 2), 16, 90, 100, 1, -1); + std::vector kv; + kv.push_back(k1); + kv.push_back(k2); + kv.push_back(k3); + + cv::String fs_result = +"\n" +"\n" +"\n" +" 1. 2. 16. 0. 100. 1 -1\n" +" 2. 3. 16. 45. 100. 1 -1\n" +" 1. 2. 16. 90. 100. 1 -1\n" +"\n" + ; + + cv::FileStorage fs_read(fs_result, cv::FileStorage::READ | cv::FileStorage::MEMORY); + + std::vector kv_read; + ASSERT_NO_THROW(fs_read["kv"] >> kv_read); + + ASSERT_EQ(kv.size(), kv_read.size()); + for (size_t i = 0; i < kv.size(); i++) + { + EXPECT_EQ(kv[i].pt, kv_read[i].pt); + EXPECT_EQ(kv[i].size, kv_read[i].size); + EXPECT_EQ(kv[i].angle, kv_read[i].angle); + EXPECT_EQ(kv[i].response, kv_read[i].response); + EXPECT_EQ(kv[i].octave, kv_read[i].octave); + EXPECT_EQ(kv[i].class_id, kv_read[i].class_id); + } +} +#endif + TEST(Core_InputOutput, FileStorage_format_xml) { FileStorage fs;