├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── include └── patch_match_opencv │ ├── adapters.h │ ├── distances │ └── euclidian.h │ ├── maps │ ├── distance_map_2d.h │ └── offset_map_2d.h │ └── patch_servers │ ├── base_patch_server.h │ ├── masked_patches.h │ ├── pixel_iterators.h │ └── whole_image_patches.h ├── lib └── catch.hpp └── test ├── assets ├── masked_patches │ ├── l_shaped_mask.png │ ├── l_shaped_mask_partial.png │ └── l_shaped_mask_total.png ├── patch_distances │ └── pink_orange.png └── patch_matcher │ ├── lena.png │ ├── lena_32_a.png │ ├── lena_32_b.png │ └── lena_offset_10.png ├── main.cpp ├── patch_match_opencv ├── patch_servers │ ├── test_masked_patches.cpp │ └── test_whole_image_patches.cpp ├── test_patch_distances.cpp └── test_patch_matcher.cpp └── test_utils.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .idea/ 3 | cmake-*/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "core"] 2 | path = core 3 | url = git@github.com:antoinewdg/patch-match-core.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(patch_match_opencv) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 5 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") 6 | 7 | set(SOURCE_FILES 8 | include/patch_match_opencv/distances/euclidian.h 9 | include/patch_match_opencv/patch_servers/pixel_iterators.h 10 | include/patch_match_opencv/patch_servers/whole_image_patches.h 11 | include/patch_match_opencv/patch_servers/masked_patches.h 12 | include/patch_match_opencv/patch_servers/base_patch_server.h 13 | include/patch_match_opencv/maps/distance_map_2d.h 14 | include/patch_match_opencv/maps/offset_map_2d.h 15 | include/patch_match_opencv/adapters.h 16 | ) 17 | 18 | set(TEST_FILES 19 | test/test_utils.h 20 | test/patch_match_opencv/test_patch_distances.cpp 21 | test/patch_match_opencv/patch_servers/test_whole_image_patches.cpp 22 | test/patch_match_opencv/patch_servers/test_masked_patches.cpp 23 | test/patch_match_opencv/test_patch_matcher.cpp 24 | ) 25 | 26 | 27 | include_directories( 28 | ${CMAKE_CURRENT_SOURCE_DIR}/include 29 | ${CMAKE_CURRENT_SOURCE_DIR}/lib 30 | ${CMAKE_CURRENT_SOURCE_DIR}/test 31 | ${CMAKE_CURRENT_SOURCE_DIR}/core/include 32 | ) 33 | 34 | 35 | add_executable(run_tests test/main.cpp ${SOURCE_FILES} ${TEST_FILES}) 36 | 37 | 38 | # EXTERNAL LIBRAIRIES ===================== 39 | 40 | # OpenCV 41 | FIND_PACKAGE(OpenCV 3.1 REQUIRED) 42 | INCLUDE_DIRECTORIES(${OpenCV_INCLUDE_DIRS}) 43 | set(TARGET_LIBS ${TARGET_LIBS} ${OpenCV_LIBS}) 44 | 45 | find_package(Boost 1.36.0 REQUIRED) 46 | if (Boost_FOUND) 47 | include_directories(${Boost_INCLUDE_DIRS}) 48 | endif () 49 | 50 | 51 | TARGET_LINK_LIBRARIES(run_tests ${TARGET_LIBS}) -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Antoine Wendlinger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Specialization of [my implementation](https://github.com/antoinewdg/patch-match-core) of the PatchMatch algorithm for 2 | OpenCV images. 3 | 4 | It provides out-of-the-box implementation of the various wrappers needed by the algorithm, and provides utilities to build 5 | custom ones. 6 | 7 | ## Documentation 8 | 9 | This project has currently no documentation because I do not see anyone having an immediate use of it besides myself, if you feel it could be of any use to you or have any question feel free to contact me. 10 | -------------------------------------------------------------------------------- /include/patch_match_opencv/adapters.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/25/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_OPENCV_ADAPTERS_H 6 | #define PATCH_MATCH_OPENCV_ADAPTERS_H 7 | 8 | #include "distances/euclidian.h" 9 | 10 | #include "maps/offset_map_2d.h" 11 | #include "maps/distance_map_2d.h" 12 | 13 | #include "patch_servers/whole_image_patches.h" 14 | #include "patch_servers/masked_patches.h" 15 | 16 | #endif //PATCH_MATCH_OPENCV_ADAPTERS_H 17 | -------------------------------------------------------------------------------- /include/patch_match_opencv/distances/euclidian.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/17/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_PATCH_DISTANCES_H 6 | #define PATCH_MATCH_PATCH_DISTANCES_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace pm { 14 | namespace opencv { 15 | using cv::Mat_; 16 | using cv::Vec; 17 | using cv::Vec2i; 18 | using cv::Rect; 19 | 20 | template 21 | class EuclidianSquaredDistance { 22 | 23 | public: 24 | typedef Vec2i left_type; 25 | typedef Vec2i right_type; 26 | typedef O output_type; 27 | typedef Mat_> mat_type; 28 | 29 | 30 | EuclidianSquaredDistance(const mat_type &a, const mat_type &b) : 31 | m_a(a), m_b(b) { 32 | } 33 | 34 | inline output_type operator()(const Vec2i &p, const Vec2i &q) { 35 | return _sq_euclidian_distance(m_a(_get_subrect(p)), m_b(_get_subrect(q))); 36 | } 37 | 38 | inline output_type operator()(const Vec2i &p, const Vec2i &q, output_type max_d) { 39 | return _sq_euclidian_distance( 40 | m_a(_get_subrect(p)), 41 | m_b(_get_subrect(q)), 42 | max_d 43 | ); 44 | } 45 | 46 | private: 47 | 48 | inline Rect _get_subrect(const Vec2i &p) { 49 | return Rect(p[1] - P / 2, p[0] - P / 2, P, P); 50 | } 51 | 52 | inline output_type _sq_euclidian_distance(const mat_type &s, const mat_type &t) { 53 | output_type d = output_type(0); 54 | 55 | for (int i = 0; i < P; i++) { 56 | for (int j = 0; j < P; j++) { 57 | 58 | for (int k = 0; k < 3; k++) { 59 | output_type temp = output_type(s(i, j)[k] - t(i, j)[k]); 60 | d += temp * temp; 61 | } 62 | } 63 | } 64 | 65 | return d; 66 | } 67 | 68 | 69 | inline output_type _sq_euclidian_distance(const mat_type &s, const mat_type &t, output_type max_d) { 70 | output_type d = output_type(0); 71 | 72 | for (int i = 0; i < P; i++) { 73 | for (int j = 0; j < P; j++) { 74 | 75 | for (int k = 0; k < 3; k++) { 76 | output_type temp = output_type(s(i, j)[k] - t(i, j)[k]); 77 | d += temp * temp; 78 | } 79 | if (d > max_d) { 80 | return d; 81 | } 82 | } 83 | } 84 | 85 | return d; 86 | } 87 | 88 | const mat_type &m_a, &m_b; 89 | }; 90 | } 91 | } 92 | 93 | #endif //PATCH_MATCH_PATCH_DISTANCES_H 94 | -------------------------------------------------------------------------------- /include/patch_match_opencv/maps/distance_map_2d.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/23/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_2D_OFFSET_MAP_H 6 | #define PATCH_MATCH_2D_OFFSET_MAP_H 7 | 8 | #include 9 | 10 | namespace pm { 11 | namespace opencv { 12 | using cv::Vec2i; 13 | using cv::Mat_; 14 | using cv::Size; 15 | 16 | template 17 | class DistanceMap2d { 18 | public: 19 | 20 | typedef Vec2i patch_type; 21 | typedef T distance_type; 22 | 23 | DistanceMap2d(Size size) : m_data(size) {} 24 | 25 | inline distance_type operator()(const patch_type &p) const { 26 | return m_data(p); 27 | } 28 | 29 | inline distance_type &operator()(const patch_type &p) { 30 | return m_data(p); 31 | } 32 | 33 | inline const Mat_ &to_mat() const { 34 | return m_data; 35 | } 36 | 37 | 38 | private: 39 | Mat_ m_data; 40 | }; 41 | } 42 | } 43 | 44 | #endif //PATCH_MATCH_2D_OFFSET_MAP_H 45 | -------------------------------------------------------------------------------- /include/patch_match_opencv/maps/offset_map_2d.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/25/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_OPENCV_OFFSET_MAP_2D_H 6 | #define PATCH_MATCH_OPENCV_OFFSET_MAP_2D_H 7 | 8 | #include 9 | 10 | namespace pm { 11 | namespace opencv { 12 | using cv::Vec2i; 13 | using cv::Mat_; 14 | using cv::Size; 15 | 16 | class OffsetMap2D { 17 | public: 18 | 19 | typedef Vec2i s_patch_type; 20 | typedef Vec2i t_patch_type; 21 | typedef Vec2i offset_type; 22 | 23 | OffsetMap2D(Size size) : m_data(size) {} 24 | 25 | inline offset_type to_offset(const s_patch_type &p, const t_patch_type &q) { 26 | return q - p; 27 | } 28 | 29 | inline t_patch_type from_offset(const s_patch_type &p, const offset_type &offset) { 30 | return p + offset; 31 | } 32 | 33 | inline const offset_type &operator()(const s_patch_type &p) const { 34 | return m_data(p); 35 | } 36 | 37 | inline offset_type &operator()(const s_patch_type &p) { 38 | return m_data(p); 39 | } 40 | 41 | inline const Mat_ &to_mat() const { 42 | return m_data; 43 | } 44 | 45 | 46 | private: 47 | Mat_ m_data; 48 | }; 49 | } 50 | } 51 | #endif //PATCH_MATCH_OPENCV_OFFSET_MAP_2D_H 52 | -------------------------------------------------------------------------------- /include/patch_match_opencv/patch_servers/base_patch_server.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 12/21/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_OPENCV_BASE_PATCH_SERVER_H 6 | #define PATCH_MATCH_OPENCV_BASE_PATCH_SERVER_H 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace pm { 14 | namespace opencv { 15 | using cv::Vec2i; 16 | using cv::Size; 17 | 18 | class BasePatchServer { 19 | public: 20 | typedef Vec2i patch_type; 21 | typedef int window_size_type; 22 | typedef std::array predecessors_type; 23 | 24 | BasePatchServer(Size size, int P) : 25 | m_size(size), P(P), half_p(P / 2) {} 26 | 27 | inline window_size_type get_max_window_size(const patch_type &c) { 28 | return std::max({m_size.height - half_p - c[0], 29 | m_size.width - half_p - c[1], 30 | c[0] - half_p + 1, c[1] - half_p + 1}); 31 | } 32 | 33 | template 34 | inline patch_type pick_random_in_window(RandomEngine &generator, 35 | const patch_type &c, 36 | window_size_type r) { 37 | std::uniform_int_distribution k_dist(c[0] - r, c[0] + r); 38 | std::uniform_int_distribution l_dist(c[1] - r, c[1] + r); 39 | return patch_type(k_dist(generator), l_dist(generator)); 40 | } 41 | 42 | inline predecessors_type get_regular_predecessors(const patch_type &p) const { 43 | return {p - Vec2i(1, 0), p - Vec2i(0, 1)}; 44 | }; 45 | 46 | inline predecessors_type get_reverse_predecessors(const patch_type &p) const { 47 | return {p + Vec2i(1, 0), p + Vec2i(0, 1)}; 48 | }; 49 | 50 | inline Size size() const { 51 | return m_size; 52 | } 53 | 54 | protected: 55 | 56 | inline bool _is_patch_inside_boundaries(const patch_type &p) const { 57 | return p[0] >= half_p && p[0] < m_size.height - half_p && 58 | p[1] >= half_p && p[1] < m_size.width - half_p; 59 | } 60 | 61 | Size m_size; 62 | int P, half_p; 63 | std::uniform_int_distribution i_dist, j_dist; 64 | }; 65 | } 66 | } 67 | #endif //PATCH_MATCH_OPENCV_BASE_PATCH_SERVER_H 68 | -------------------------------------------------------------------------------- /include/patch_match_opencv/patch_servers/masked_patches.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/15/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_MASKED_PATCHES_H 6 | #define PATCH_MATCH_MASKED_PATCHES_H 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "base_patch_server.h" 14 | 15 | namespace pm { 16 | namespace opencv { 17 | 18 | using cv::Vec2i; 19 | using cv::Mat_; 20 | using cv::Rect; 21 | using cv::Size; 22 | 23 | using std::vector; 24 | 25 | template 26 | class MaskedPatches : public BasePatchServer { 27 | public: 28 | 29 | typedef vector::const_iterator const_iterator; 30 | typedef vector::const_reverse_iterator const_reverse_iterator; 31 | 32 | MaskedPatches(const Mat_ mask, int P) : 33 | patches_mask(mask.size(), false), 34 | BasePatchServer(mask.size(), P) { 35 | int p_2 = P / 2; 36 | Predicate predicate; 37 | for (int i = p_2; i < mask.rows - p_2; i++) { 38 | for (int j = p_2; j < mask.cols - p_2; j++) { 39 | if (predicate(mask, i, j, P)) { 40 | patches.emplace_back(i, j); 41 | patches_mask(i, j) = true; 42 | } 43 | } 44 | } 45 | 46 | index_dist = std::uniform_int_distribution(0, patches.size() - 1); 47 | } 48 | 49 | inline const_iterator begin() const { 50 | return patches.begin(); 51 | } 52 | 53 | inline const_iterator end() const { 54 | return patches.end(); 55 | } 56 | 57 | inline const_reverse_iterator rbegin() const { 58 | return patches.rbegin(); 59 | } 60 | 61 | inline const_reverse_iterator rend() const { 62 | return patches.rend(); 63 | } 64 | 65 | inline bool contains_patch(const Vec2i &p) { 66 | return _is_patch_inside_boundaries(p) && 67 | patches_mask(p); 68 | } 69 | 70 | template 71 | inline Vec2i pick_random(RandomEngine &generator) { 72 | return patches[index_dist(generator)]; 73 | } 74 | 75 | private: 76 | Mat_ patches_mask; 77 | vector patches; 78 | std::uniform_int_distribution index_dist; 79 | }; 80 | 81 | struct AtLeastOneInPatch { 82 | bool operator()(const Mat_ &mask, int i, int j, int P) { 83 | int p_2 = P / 2; 84 | int n = cv::countNonZero(mask(Rect(j - p_2, i - p_2, P, P))); 85 | return n > 0; 86 | } 87 | }; 88 | 89 | struct AllInPatch { 90 | bool operator()(const Mat_ &mask, int i, int j, int P) { 91 | int p_2 = P / 2; 92 | int n = cv::countNonZero(mask(Rect(j - p_2, i - p_2, P, P))); 93 | return n == P * P; 94 | } 95 | }; 96 | 97 | typedef MaskedPatches PartiallyMaskedPatches; 98 | typedef MaskedPatches TotallyMaskedPatches; 99 | 100 | 101 | } 102 | } 103 | 104 | #endif //PATCH_MATCH_MASKED_PATCHES_H 105 | -------------------------------------------------------------------------------- /include/patch_match_opencv/patch_servers/pixel_iterators.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/22/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_PIXEL_ITERATORS_H 6 | #define PATCH_MATCH_PIXEL_ITERATORS_H 7 | 8 | #include 9 | #include 10 | 11 | 12 | namespace pm { 13 | namespace opencv { 14 | using cv::Vec2i; 15 | using cv::Size; 16 | 17 | class ConstPixelIterator : public boost::iterator_facade< 18 | ConstPixelIterator, Vec2i const, boost::forward_traversal_tag> { 19 | public: 20 | ConstPixelIterator(const Vec2i &start, const Vec2i &end) : 21 | m_current(start), m_start(start), m_end(end) {} 22 | 23 | private: 24 | friend class boost::iterator_core_access; 25 | 26 | inline void increment() { 27 | m_current[1]++; 28 | if (m_current[1] >= m_end[1]) { 29 | m_current[0]++; 30 | if (m_current[0] < m_end[0]) { 31 | m_current[1] = m_start[1]; 32 | } 33 | } 34 | } 35 | 36 | inline bool equal(const ConstPixelIterator &other) const { 37 | return m_current == other.m_current; 38 | } 39 | 40 | inline const Vec2i &dereference() const { 41 | return m_current; 42 | } 43 | 44 | Vec2i m_current, m_start, m_end; 45 | }; 46 | 47 | class ConstReversePixelIterator : public boost::iterator_facade< 48 | ConstReversePixelIterator, Vec2i const, boost::forward_traversal_tag> { 49 | public: 50 | ConstReversePixelIterator(const Vec2i &start, const Vec2i &end) : 51 | m_current(end - Vec2i(1, 1)), m_start(start), m_end(end) {} 52 | 53 | private: 54 | friend class boost::iterator_core_access; 55 | 56 | inline void increment() { 57 | m_current[1]--; 58 | if (m_current[1] < m_start[1]) { 59 | m_current[0]--; 60 | if (m_current[0] >= m_start[0]) { 61 | m_current[1] = m_end[1] - 1; 62 | } 63 | } 64 | } 65 | 66 | inline bool equal(const ConstReversePixelIterator &other) const { 67 | return m_current == other.m_current; 68 | } 69 | 70 | inline const Vec2i &dereference() const { 71 | return m_current; 72 | } 73 | 74 | Vec2i m_current, m_start, m_end; 75 | }; 76 | } 77 | } 78 | #endif //PATCH_MATCH_PIXEL_ITERATORS_H 79 | -------------------------------------------------------------------------------- /include/patch_match_opencv/patch_servers/whole_image_patches.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/22/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_WHOLE_IMAGE_PATCHES_H 6 | #define PATCH_MATCH_WHOLE_IMAGE_PATCHES_H 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include "pixel_iterators.h" 15 | #include "base_patch_server.h" 16 | 17 | namespace pm { 18 | namespace opencv { 19 | using cv::Vec2i; 20 | using cv::Size; 21 | 22 | class WholeImagePatches : public BasePatchServer { 23 | 24 | public: 25 | typedef ConstPixelIterator const_iterator; 26 | typedef ConstReversePixelIterator const_reverse_iterator; 27 | 28 | WholeImagePatches(const Size &size, int P) : 29 | m_begin(P / 2, P / 2), 30 | m_end(size.height - P / 2, size.width - P / 2), 31 | i_dist(m_begin[0], m_end[0] - 1), 32 | j_dist(m_begin[1], m_end[1] - 1), 33 | BasePatchServer(size, P) { 34 | 35 | } 36 | 37 | inline const_iterator begin() const { 38 | return const_iterator(m_begin, m_end); 39 | } 40 | 41 | inline const_iterator end() const { 42 | return const_iterator(m_end, m_end); 43 | } 44 | 45 | inline const_reverse_iterator rbegin() const { 46 | return const_reverse_iterator(m_begin, m_end); 47 | } 48 | 49 | inline const_reverse_iterator rend() const { 50 | return const_reverse_iterator(m_begin, m_begin); 51 | } 52 | 53 | template 54 | inline patch_type pick_random(RandomEngine &generator) { 55 | return patch_type(i_dist(generator), j_dist(generator)); 56 | } 57 | 58 | inline bool contains_patch(const patch_type &p) const { 59 | return _is_patch_inside_boundaries(p); 60 | } 61 | 62 | 63 | private: 64 | Vec2i m_begin, m_end; 65 | std::uniform_int_distribution i_dist, j_dist; 66 | }; 67 | } 68 | } 69 | 70 | #endif //PATCH_MATCH_WHOLE_IMAGE_PATCHES_H 71 | -------------------------------------------------------------------------------- /test/assets/masked_patches/l_shaped_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/masked_patches/l_shaped_mask.png -------------------------------------------------------------------------------- /test/assets/masked_patches/l_shaped_mask_partial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/masked_patches/l_shaped_mask_partial.png -------------------------------------------------------------------------------- /test/assets/masked_patches/l_shaped_mask_total.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/masked_patches/l_shaped_mask_total.png -------------------------------------------------------------------------------- /test/assets/patch_distances/pink_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/patch_distances/pink_orange.png -------------------------------------------------------------------------------- /test/assets/patch_matcher/lena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/patch_matcher/lena.png -------------------------------------------------------------------------------- /test/assets/patch_matcher/lena_32_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/patch_matcher/lena_32_a.png -------------------------------------------------------------------------------- /test/assets/patch_matcher/lena_32_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/patch_matcher/lena_32_b.png -------------------------------------------------------------------------------- /test/assets/patch_matcher/lena_offset_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoinewdg/patch-match-opencv/9de98f8b1ec408a03a9adf9bec080ec30488bd55/test/assets/patch_matcher/lena_offset_10.png -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/15/16. 3 | // 4 | 5 | #define CATCH_CONFIG_MAIN 6 | #include "catch.hpp" -------------------------------------------------------------------------------- /test/patch_match_opencv/patch_servers/test_masked_patches.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "catch.hpp" 4 | 5 | #include "patch_match_opencv/patch_servers/masked_patches.h" 6 | #include "test_utils.h" 7 | 8 | using namespace pm::opencv; 9 | 10 | using std::string; 11 | 12 | Mat_ load_mask(string name) { 13 | Mat_ im = load_test_asset("masked_patches/" + name + ".png", cv::IMREAD_GRAYSCALE); 14 | return im / 255; 15 | } 16 | 17 | 18 | TEST_CASE("Totally masked patches") { 19 | 20 | Mat_ l_shaped_mask = load_mask("l_shaped_mask"); 21 | Mat_ expected_mask = load_mask("l_shaped_mask_total"); 22 | TotallyMaskedPatches patches(l_shaped_mask, 5); 23 | 24 | 25 | SECTION("contains_patch") { 26 | for (int i = 0; i < expected_mask.rows; i++) { 27 | for (int j = 0; j < expected_mask.cols; j++) { 28 | if (expected_mask(i, j) != patches.contains_patch(Vec2i(i, j))) { 29 | FAIL("" << "Expected mask is " << expected_mask(i, j) 30 | << " while actual mask is " << patches.contains_patch(Vec2i(i, j)) 31 | << " at position " << Vec2i(i, j)); 32 | } 33 | } 34 | } 35 | 36 | REQUIRE_FALSE(patches.contains_patch(Vec2i(-1,0))); 37 | REQUIRE_FALSE(patches.contains_patch(Vec2i(0,-8))); 38 | REQUIRE_FALSE(patches.contains_patch(Vec2i(-9,-87))); 39 | REQUIRE_FALSE(patches.contains_patch(Vec2i(32,31))); 40 | REQUIRE_FALSE(patches.contains_patch(Vec2i(56,-7))); 41 | } 42 | 43 | SECTION("iterator") { 44 | Mat_ constructed_mask(l_shaped_mask.size(), false); 45 | for (auto p : patches) { 46 | constructed_mask(p) = true; 47 | } 48 | auto are_matrix_equal = true; 49 | for (int i = 0; i < expected_mask.rows; i++) { 50 | for (int j = 0; j < expected_mask.cols; j++) { 51 | if (expected_mask(i, j) != constructed_mask(i, j)) { 52 | are_matrix_equal = false; 53 | } 54 | } 55 | } 56 | REQUIRE(are_matrix_equal); 57 | 58 | } 59 | 60 | SECTION("reverse iterator") { 61 | Mat_ constructed_mask(l_shaped_mask.size(), false); 62 | auto it = patches.rbegin(); 63 | for (; it != patches.rend(); it++) { 64 | constructed_mask(*it) = true; 65 | } 66 | auto are_matrix_equal = true; 67 | for (int i = 0; i < expected_mask.rows; i++) { 68 | for (int j = 0; j < expected_mask.cols; j++) { 69 | if (expected_mask(i, j) != constructed_mask(i, j)) { 70 | are_matrix_equal = false; 71 | } 72 | } 73 | } 74 | REQUIRE(are_matrix_equal); 75 | 76 | } 77 | 78 | SECTION("size") { 79 | REQUIRE(patches.size() == Size(32, 32)); 80 | } 81 | 82 | SECTION("pick_random") { 83 | std::default_random_engine generator(448); 84 | for (int i = 0; i < 100; i++) { 85 | Vec2i p = patches.pick_random(generator); 86 | if (!patches.contains_patch(p)) { 87 | FAIL("Randomly generated patch " << p << " is not contained"); 88 | } 89 | } 90 | } 91 | } 92 | 93 | TEST_CASE("Partially masked patches") { 94 | 95 | Mat_ l_shaped_mask = load_mask("l_shaped_mask"); 96 | Mat_ expected_mask = load_mask("l_shaped_mask_partial"); 97 | PartiallyMaskedPatches patches(l_shaped_mask, 5); 98 | 99 | 100 | SECTION("contains_patch") { 101 | for (int i = 0; i < expected_mask.rows; i++) { 102 | for (int j = 0; j < expected_mask.cols; j++) { 103 | if (expected_mask(i, j) != patches.contains_patch(Vec2i(i, j))) { 104 | FAIL("" << "Expected mask is " << expected_mask(i, j) 105 | << " while actual mask is " << patches.contains_patch(Vec2i(i, j)) 106 | << " at position " << Vec2i(i, j)); 107 | } 108 | } 109 | } 110 | 111 | REQUIRE_FALSE(patches.contains_patch(Vec2i(-1,0))); 112 | REQUIRE_FALSE(patches.contains_patch(Vec2i(0,-8))); 113 | REQUIRE_FALSE(patches.contains_patch(Vec2i(-9,-87))); 114 | REQUIRE_FALSE(patches.contains_patch(Vec2i(32,31))); 115 | REQUIRE_FALSE(patches.contains_patch(Vec2i(56,-7))); 116 | } 117 | 118 | SECTION("iterator") { 119 | Mat_ constructed_mask(l_shaped_mask.size(), false); 120 | for (auto p : patches) { 121 | constructed_mask(p) = true; 122 | } 123 | auto are_matrix_equal = true; 124 | for (int i = 0; i < expected_mask.rows; i++) { 125 | for (int j = 0; j < expected_mask.cols; j++) { 126 | if (expected_mask(i, j) != constructed_mask(i, j)) { 127 | are_matrix_equal = false; 128 | } 129 | } 130 | } 131 | REQUIRE(are_matrix_equal); 132 | 133 | } 134 | 135 | SECTION("reverse iterator") { 136 | Mat_ constructed_mask(l_shaped_mask.size(), false); 137 | auto it = patches.rbegin(); 138 | for (; it != patches.rend(); it++) { 139 | constructed_mask(*it) = true; 140 | } 141 | auto are_matrix_equal = true; 142 | for (int i = 0; i < expected_mask.rows; i++) { 143 | for (int j = 0; j < expected_mask.cols; j++) { 144 | if (expected_mask(i, j) != constructed_mask(i, j)) { 145 | are_matrix_equal = false; 146 | } 147 | } 148 | } 149 | REQUIRE(are_matrix_equal); 150 | 151 | } 152 | 153 | SECTION("size") { 154 | REQUIRE(patches.size() == Size(32, 32)); 155 | } 156 | 157 | SECTION("pick_random") { 158 | std::default_random_engine generator(448); 159 | for (int i = 0; i < 100; i++) { 160 | Vec2i p = patches.pick_random(generator); 161 | if (!patches.contains_patch(p)) { 162 | FAIL("Randomly generated patch " << p << " is not contained"); 163 | } 164 | } 165 | } 166 | 167 | } -------------------------------------------------------------------------------- /test/patch_match_opencv/patch_servers/test_whole_image_patches.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | 3 | #include "patch_match_opencv/patch_servers/whole_image_patches.h" 4 | #include "test_utils.h" 5 | 6 | #include 7 | 8 | using namespace pm::opencv; 9 | 10 | using cv::Mat_; 11 | 12 | TEST_CASE("WholeImagePatches 8x10 with P=5") { 13 | 14 | WholeImagePatches patches(Size(8, 10), 5); 15 | 16 | SECTION("Forward iteration, contains_patch") { 17 | auto it = patches.begin(); 18 | for (int i = 2; i < 10 - 2; i++) { 19 | for (int j = 2; j < 8 - 2; j++) { 20 | if(*it != Vec2i(i, j) ){ 21 | FAIL("Unexpected patch at position "<< Vec2i(i,j)); 22 | } 23 | if(!patches.contains_patch(*it)){ 24 | FAIL("Patch server should contain " << Vec2i(i,j)); 25 | } 26 | it++; 27 | } 28 | } 29 | REQUIRE(it == patches.end()); 30 | } 31 | 32 | SECTION("Reverse iteration") { 33 | auto it = patches.rbegin(); 34 | for (int i = 10 - 2 - 1; i >= 2; i--) { 35 | for (int j = 8 - 2 - 1; j >= 2; j--) { 36 | if(*it != Vec2i(i, j) ){ 37 | FAIL("Unexpected patch at position "<< Vec2i(i,j)); 38 | } 39 | it++; 40 | } 41 | } 42 | REQUIRE(it == patches.rend()); 43 | } 44 | 45 | SECTION("size") { 46 | REQUIRE(patches.size() == Size(8, 10)); 47 | } 48 | 49 | SECTION("pick_random") { 50 | std::default_random_engine generator(9978); 51 | Mat_ n_picked(6, 4, 0); 52 | for (int i = 0; i < 100; i++) { 53 | Vec2i p = patches.pick_random(generator); 54 | n_picked(p - Vec2i(2, 2))++; 55 | if (!patches.contains_patch(p)) { 56 | FAIL("Randomly generated patch " << p << " is not contained"); 57 | } 58 | } 59 | 60 | // There are at most two unpicked patches after 100 iterations 61 | REQUIRE(24 - cv::countNonZero(n_picked) < 2); 62 | } 63 | 64 | SECTION("get_max_window_size") { 65 | REQUIRE(patches.get_max_window_size(Vec2i(2, 2)) == 6); 66 | REQUIRE(patches.get_max_window_size(Vec2i(5, 4)) == 4); 67 | } 68 | 69 | SECTION("pick_random_in_window") { 70 | std::default_random_engine generator(78410); 71 | for (int i = 0; i < 100; i++) { 72 | Vec2i p = patches.pick_random_in_window(generator, Vec2i(0, 0), 3); 73 | if (std::abs(p[0]) > 3 || std::abs(p[1]) > 3) { 74 | FAIL(p << " is outside the window"); 75 | } 76 | } 77 | } 78 | 79 | SECTION("get_regular_predecessors") { 80 | std::array expected; 81 | 82 | expected = {Vec2i(1, 2), Vec2i(2, 1)}; 83 | REQUIRE(patches.get_regular_predecessors(Vec2i(2, 2)) == expected); 84 | 85 | expected = {Vec2i(4, 4), Vec2i(5, 3)}; 86 | REQUIRE(patches.get_regular_predecessors(Vec2i(5, 4)) == expected); 87 | } 88 | 89 | SECTION("get_reverse_predecessors") { 90 | std::array expected; 91 | 92 | expected = {Vec2i(3, 2), Vec2i(2, 3)}; 93 | REQUIRE(patches.get_reverse_predecessors(Vec2i(2, 2)) == expected); 94 | 95 | expected = {Vec2i(6, 4), Vec2i(5, 5)}; 96 | REQUIRE(patches.get_reverse_predecessors(Vec2i(5, 4)) == expected); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /test/patch_match_opencv/test_patch_distances.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | 3 | #include "../../include/patch_match_opencv/distances/euclidian.h" 4 | #include "test_utils.h" 5 | 6 | using namespace pm::opencv; 7 | 8 | using cv::Vec3b; 9 | 10 | TEST_CASE("Euclidian squared distance Vec3b, int, P=5") { 11 | 12 | typedef EuclidianSquaredDistance<5, uchar, int, 3> DistanceType; 13 | Mat_ im = load_test_asset("patch_distances/pink_orange.png"); 14 | 15 | DistanceType distance(im, im); 16 | 17 | REQUIRE(distance(Vec2i(2, 2), Vec2i(2, 2)) == 0); 18 | REQUIRE(distance(Vec2i(2, 2), Vec2i(2, 4)) == 0); 19 | REQUIRE(distance(Vec2i(2, 2), Vec2i(2, 7)) == 0); 20 | 21 | REQUIRE(distance(Vec2i(2,2), Vec2i(3, 2)) == 423125); 22 | REQUIRE(distance(Vec2i(2,2), Vec2i(3, 4)) == 423125); 23 | REQUIRE(distance(Vec2i(2,2), Vec2i(3, 7)) == 423125); 24 | 25 | REQUIRE(distance(Vec2i(2,2), Vec2i(7, 2)) == 2115625); 26 | REQUIRE(distance(Vec2i(2,2), Vec2i(7, 4)) == 2115625); 27 | REQUIRE(distance(Vec2i(2,2), Vec2i(7, 7)) == 2115625); 28 | 29 | REQUIRE(distance(Vec2i(5,10), Vec2i(7,12)) == 729600); 30 | 31 | 32 | 33 | } -------------------------------------------------------------------------------- /test/patch_match_opencv/test_patch_matcher.cpp: -------------------------------------------------------------------------------- 1 | #include "catch.hpp" 2 | 3 | #include "iostream" 4 | #include "patch_match_core/patch_matcher.h" 5 | #include "patch_match_opencv/adapters.h" 6 | #include "test_utils.h" 7 | 8 | 9 | using namespace pm::core; 10 | using namespace pm::opencv; 11 | 12 | using cv::Vec3b; 13 | 14 | TEST_CASE("PatchMatcher on simple translation") { 15 | 16 | typedef EuclidianSquaredDistance<5, uchar, int, 3> DistanceType; 17 | typedef PatchMatcher> MatcherType; 19 | 20 | auto lena = load_test_asset("patch_matcher/lena.png"); 21 | auto lena_offset_10 = load_test_asset("patch_matcher/lena_offset_10.png"); 22 | WholeImagePatches patches(lena.size(), 5); 23 | WholeImagePatches patches_10(lena_offset_10.size(), 5); 24 | DistanceType distance(lena, lena_offset_10); 25 | 26 | 27 | SECTION("Converges in only one iteration if correct offset is manually set") { 28 | OffsetMap2D offset_map(patches.size()); 29 | DistanceMap2d distance_map(patches.size()); 30 | MatcherType matcher(patches, patches_10, distance, 31 | offset_map, distance_map, 32); 32 | 33 | matcher.initialize_offset_map_randomly(); 34 | matcher.set_offset(Vec2i(2, 2), Vec2i(0, 10)); 35 | matcher.iterate_n_times(1); 36 | 37 | 38 | Mat_ expected_offsets(508, 508, Vec2i(0, 10)); 39 | Mat_ expected_distances(508, 508, 0); 40 | 41 | Rect r(2, 2, 508, 508); 42 | require_matrix_equal(distance_map.to_mat()(r), expected_distances); 43 | require_matrix_equal(offset_map.to_mat()(r), expected_offsets); 44 | 45 | } 46 | 47 | SECTION("Converges in 5 iterations with random initialization") { 48 | 49 | OffsetMap2D offset_map(patches.size()); 50 | DistanceMap2d distance_map(patches.size()); 51 | MatcherType matcher(patches, patches_10, distance, 52 | offset_map, distance_map, 3245); 53 | 54 | matcher.initialize_offset_map_randomly(); 55 | matcher.iterate_n_times(5); 56 | 57 | 58 | Mat_ expected_offsets(508, 508, Vec2i(0, 10)); 59 | Mat_ expected_distances(508, 508, 0); 60 | 61 | Rect r(2, 2, 508, 508); 62 | require_matrix_equal(distance_map.to_mat()(r), expected_distances); 63 | require_matrix_equal(offset_map.to_mat()(r), expected_offsets); 64 | 65 | 66 | } 67 | 68 | } 69 | 70 | 71 | TEST_CASE("PatchMatcher compared to exhaustive search") { 72 | typedef EuclidianSquaredDistance<5, uchar, int, 3> DistanceType; 73 | typedef PatchMatcher> MatcherType; 75 | 76 | auto source = load_test_asset("patch_matcher/lena_32_a.png"); 77 | auto target = load_test_asset("patch_matcher/lena_32_b.png"); 78 | WholeImagePatches s_server(source.size(), 5); 79 | WholeImagePatches t_server(target.size(), 5); 80 | DistanceType distance(source, target); 81 | 82 | OffsetMap2D offset_map(source.size()); 83 | DistanceMap2d distance_map(target.size()); 84 | MatcherType matcher(s_server, t_server, distance, 85 | offset_map, distance_map, 7854); 86 | 87 | Mat_ exhaustive_offsets(source.size()); 88 | Mat_ exhaustive_distances(source.size(), std::numeric_limits::max()); 89 | 90 | int max_d = -1; 91 | for (auto p : s_server) { 92 | bool found = false; 93 | for (auto q : t_server) { 94 | int d = distance(p, q); 95 | if (d < exhaustive_distances(p)) { 96 | found = true; 97 | exhaustive_offsets(p) = q - p; 98 | exhaustive_distances(p) = d; 99 | } 100 | if (d > max_d) { 101 | max_d = d; 102 | } 103 | } 104 | } 105 | 106 | cv::Rect r(2, 2, 28, 28); 107 | matcher.initialize_offset_map_randomly(); 108 | 109 | 110 | auto diff = exhaustive_distances(r) - distance_map.to_mat()(r); 111 | auto initial_error = cv::norm(diff, cv::NORM_L2) / (28 * 28); 112 | 113 | matcher.iterate_n_times(5); 114 | diff = exhaustive_distances(r) - distance_map.to_mat()(r); 115 | auto new_error = cv::norm(diff, cv::NORM_L2) / (28 * 28); 116 | 117 | /* 118 | * This is an ad-hoc result, may be completely stupid. 119 | */ 120 | REQUIRE((initial_error / new_error) > 40); 121 | } 122 | -------------------------------------------------------------------------------- /test/test_utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by antoinewdg on 11/17/16. 3 | // 4 | 5 | #ifndef PATCH_MATCH_UTILS_H 6 | #define PATCH_MATCH_UTILS_H 7 | 8 | 9 | #include 10 | 11 | template 12 | inline cv::Mat_ load_test_asset(std::string name, int flag = cv::IMREAD_COLOR) { 13 | 14 | cv::Mat_ im = cv::imread("../test/assets/" + name, flag); 15 | return im; 16 | } 17 | 18 | inline cv::Mat_ load_mask_asset(std::string name) { 19 | cv::Mat_ im = cv::imread("../test/assets/" + name, cv::IMREAD_GRAYSCALE); 20 | return im / 255; 21 | } 22 | 23 | template 24 | inline void require_matrix_equal(const cv::Mat_ &a, const cv::Mat_ &b) { 25 | if (a.size() != b.size()) { 26 | FAIL("Matrices of different sizes " << a.size() << " " << b.size()); 27 | } 28 | for (int i = 0; i < a.rows; i++) { 29 | for (int j = 0; j < a.cols; j++) { 30 | if (a(i, j) != b(i, j)) { 31 | FAIL("Matrices are different at index (" << i << "," << j << "): " 32 | << a(i, j) << " != " << b(i, j)); 33 | } 34 | } 35 | } 36 | 37 | REQUIRE(true); 38 | }; 39 | 40 | #endif //PATCH_MATCH_UTILS_H 41 | --------------------------------------------------------------------------------