├── VersionInfo.rc.in ├── asserts ├── demo1.png ├── demo2.png └── opencv_path.png ├── .gitmodules ├── matcher ├── matcher.cpp ├── base_matcher │ ├── base_matcher.h │ └── base_matcher.cpp ├── CMakeLists.txt ├── matcher_ctype.cpp └── Pattern_Matching │ ├── PatternMatching.h │ └── PatternMatching.cpp ├── include ├── matcher.h └── template_matching.h ├── LICENSE ├── CMakeLists.txt ├── README.md ├── py_demo.py ├── demo.cpp └── .gitignore /VersionInfo.rc.in: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acai66/opencv_matching/HEAD/VersionInfo.rc.in -------------------------------------------------------------------------------- /asserts/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acai66/opencv_matching/HEAD/asserts/demo1.png -------------------------------------------------------------------------------- /asserts/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acai66/opencv_matching/HEAD/asserts/demo2.png -------------------------------------------------------------------------------- /asserts/opencv_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acai66/opencv_matching/HEAD/asserts/opencv_path.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdParty/spdlog"] 2 | path = 3rdParty/spdlog 3 | url = https://github.com/gabime/spdlog 4 | -------------------------------------------------------------------------------- /matcher/matcher.cpp: -------------------------------------------------------------------------------- 1 | #include "base_matcher/base_matcher.h" 2 | #include "Pattern_Matching/PatternMatching.h" 3 | 4 | #include 5 | 6 | namespace template_matching 7 | { 8 | auto logger_ = spdlog::stdout_color_mt("template_matching"); //spdlog::rotating_logger_mt("file_logger", "run.log", 1024 * 1024 * 5, 1); 9 | 10 | Matcher* GetMatcher(const MatcherParam& param) 11 | { 12 | MatcherParam paramCopy = param; 13 | BaseMatcher* matcher = nullptr; 14 | 15 | switch (paramCopy.matcherType) 16 | { 17 | case MatcherType::PATTERN: 18 | logger_->info("Initializing matcher for type: PATTERN"); 19 | matcher = new PatternMatcher(paramCopy); 20 | break; 21 | default: 22 | break; 23 | } 24 | 25 | if (matcher->isInited() == false) 26 | { 27 | delete matcher; 28 | matcher = nullptr; 29 | } 30 | 31 | return matcher; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /include/matcher.h: -------------------------------------------------------------------------------- 1 | #ifndef _TEMPLATE_MATCHER_H 2 | #define _TEMPLATE_MATCHER_H 3 | 4 | #include 5 | #include "template_matching.h" 6 | 7 | namespace template_matching 8 | { 9 | 10 | class LIB_API Matcher 11 | { 12 | public: 13 | ~Matcher() { } 14 | 15 | /** 匹配 16 | @param frame 输入图像. 17 | @param matchResults 匹配结果. 18 | */ 19 | virtual int match(const cv::Mat &frame, std::vector & matchResults) = 0; 20 | 21 | /** 设置模板 22 | @param templateImage 模板图像. 23 | */ 24 | virtual int setTemplate(const cv::Mat& templateImage) = 0; 25 | 26 | /** 绘制匹配结果 27 | @param frame 输入图像. 28 | @param matchResults 匹配结果. 29 | */ 30 | virtual void drawResult(const cv::Mat& frame, std::vector matchResults) = 0; 31 | 32 | /** 设置是否打印中间运行时间 33 | @param enabled 启用标志位. 34 | */ 35 | virtual void setMetricsTime(const bool& enabled) = 0; 36 | 37 | /** 获取是否打印中间运行时间标志位 38 | */ 39 | virtual bool getMetricsTime() = 0; 40 | 41 | protected: 42 | 43 | }; 44 | 45 | /** 获取匹配器 46 | @param param 配置参数. 47 | */ 48 | extern "C" LIB_API Matcher * GetMatcher(const template_matching::MatcherParam& param); 49 | 50 | } 51 | 52 | #endif -------------------------------------------------------------------------------- /matcher/base_matcher/base_matcher.h: -------------------------------------------------------------------------------- 1 | #ifndef _BASEMATCHER_H 2 | #define _BASEMATCHER_H 3 | 4 | #include "spdlog/spdlog.h" 5 | #include "spdlog/async.h" 6 | #include "spdlog/sinks/rotating_file_sink.h" 7 | #include "spdlog/sinks/stdout_color_sinks.h" 8 | #include 9 | #include 10 | #include "matcher.h" 11 | 12 | #include "omp.h" 13 | 14 | 15 | namespace template_matching 16 | { 17 | class BaseMatcher : public Matcher 18 | { 19 | public: 20 | BaseMatcher(); 21 | 22 | ~BaseMatcher(); 23 | 24 | // 判断是否初始化成功 25 | bool isInited(); 26 | 27 | virtual void drawResult(const cv::Mat& frame, std::vector matchResults); 28 | 29 | void setMetricsTime(const bool& enabled); 30 | 31 | bool getMetricsTime(); 32 | 33 | protected: 34 | 35 | // 初始化匹配器 36 | bool initMatcher(const MatcherParam& param); 37 | 38 | // 初始化完成标志位 39 | bool initFinishedFlag_ = false; 40 | 41 | // 匹配参数 42 | MatcherParam matchParam_; 43 | 44 | // template 45 | cv::Mat templateImage_; 46 | 47 | // 计时 48 | std::chrono::steady_clock::time_point startTime_, endTime_; 49 | std::chrono::duration timeUse_; 50 | 51 | // 评估时间 52 | bool metricsTime_ = false; 53 | 54 | std::shared_ptr logger_; 55 | 56 | 57 | private: 58 | 59 | 60 | }; 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, DennisLiu1993 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9 FATAL_ERROR) 2 | 3 | project(matching VERSION 1.0.0) 4 | 5 | if(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 6 | set(OpenCV_DIR ${PROJECT_SOURCE_DIR}/3rdParty/opencv) 7 | endif() 8 | 9 | find_package(OpenCV REQUIRED) 10 | include_directories(${OPENCV_INCLUDE_DIRS}) 11 | link_directories(${OPENCV_LIBRARY_DIRS}) 12 | 13 | set(CMAKE_CXX_STANDARD 17) 14 | 15 | find_package( OpenMP REQUIRED) 16 | if(OPENMP_FOUND) 17 | message("OPENMP FOUND") 18 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 19 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 20 | set(CMAKE_EXE_LINKER_FLAGS"${CMAKE_EXE_LINKER_FLAGS}${OpenMP_EXE_LINKER_FLAGS}") 21 | endif() 22 | 23 | if (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64") 24 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3") 25 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") 26 | endif() 27 | 28 | option(BUILD_SHARED_LIBS "Specifies the type of libraries (SHARED or STATIC) to build" ON) 29 | 30 | include_directories( 31 | ${PROJECT_SOURCE_DIR}/3rdParty/spdlog/include 32 | include 33 | matcher 34 | ) 35 | 36 | add_executable(demo "demo.cpp") 37 | target_link_libraries(demo ${OpenCV_LIBS}) 38 | if(UNIX) 39 | target_link_libraries(demo dl) 40 | endif() 41 | 42 | #安装规则 43 | install(TARGETS demo 44 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 45 | ) 46 | install(FILES 47 | "demo.cpp" 48 | DESTINATION ${CMAKE_INSTALL_PREFIX} 49 | ) 50 | 51 | add_subdirectory(matcher) 52 | -------------------------------------------------------------------------------- /include/template_matching.h: -------------------------------------------------------------------------------- 1 | #ifndef _TEMPLATE_MATCHING_DEFINE_H 2 | #define _TEMPLATE_MATCHING_DEFINE_H 3 | 4 | #pragma once 5 | 6 | #ifdef _WIN32 7 | #pragma warning(disable: 4251) 8 | #endif // _WIN32 9 | 10 | #ifdef LIB_SHARED_BUILD 11 | #ifdef _WIN32 12 | #ifdef LIB_EXPORTS 13 | #define LIB_API __declspec(dllexport) 14 | #else 15 | #define LIB_API __declspec(dllimport) 16 | #endif // MY_LIB_EXPORTS 17 | #else 18 | #define LIB_API 19 | #endif // _WIN32 20 | #else 21 | #define LIB_API 22 | #endif // MY_LIB_SHARED_BUILD 23 | 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace template_matching 33 | { 34 | // 匹配器类型 35 | enum LIB_API MatcherType { 36 | PATTERN = 0, 37 | 38 | }; 39 | 40 | // 匹配器参数 41 | struct LIB_API MatcherParam 42 | { 43 | // 匹配器类型 44 | MatcherType matcherType = MatcherType::PATTERN; 45 | 46 | // 最大匹配目标数量 47 | int maxCount = 200; 48 | 49 | // 匹配得分阈值 50 | double scoreThreshold = 0.5; 51 | 52 | // 重叠框去重iou阈值 53 | double iouThreshold = 0.0; 54 | 55 | // 匹配角度范围 56 | double angle = 0; 57 | 58 | // 顶层金字塔最小面积 59 | double minArea = 256; 60 | 61 | }; 62 | 63 | // 匹配结果 64 | struct LIB_API MatchResult 65 | { 66 | cv::Point2d LeftTop; // 左上角点 67 | cv::Point2d LeftBottom; // 左下角点 68 | cv::Point2d RightTop; // 右上角点 69 | cv::Point2d RightBottom; // 右下角点 70 | cv::Point2d Center; // 中心点 71 | 72 | double Angle; // 角度 73 | double Score; // 匹配得分 74 | }; 75 | 76 | } 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /matcher/base_matcher/base_matcher.cpp: -------------------------------------------------------------------------------- 1 | #include "base_matcher.h" 2 | 3 | namespace template_matching 4 | { 5 | 6 | BaseMatcher::BaseMatcher() 7 | { 8 | logger_ = spdlog::get("template_matching"); 9 | logger_->set_level(spdlog::level::info); 10 | 11 | } 12 | 13 | BaseMatcher::~BaseMatcher() 14 | { 15 | // std::cout << "~BaseMatcher()" << std::endl; 16 | } 17 | 18 | bool BaseMatcher::isInited() 19 | { 20 | return initFinishedFlag_; 21 | } 22 | 23 | bool BaseMatcher::initMatcher(const template_matching::MatcherParam& param) 24 | { 25 | matchParam_ = param; 26 | 27 | return true; 28 | } 29 | 30 | void BaseMatcher::setMetricsTime(const bool& enabled) 31 | { 32 | metricsTime_ = enabled; 33 | } 34 | 35 | bool BaseMatcher::getMetricsTime() 36 | { 37 | return metricsTime_; 38 | } 39 | 40 | void BaseMatcher::drawResult(const cv::Mat& frame, std::vector matchResults) 41 | { 42 | if (frame.empty()) 43 | { 44 | logger_->warn("image empty."); 45 | return; 46 | } 47 | 48 | cv::Mat drawFrame = frame.clone(); 49 | if (drawFrame.channels() == 1) 50 | { 51 | cv::cvtColor(drawFrame, drawFrame, cv::COLOR_GRAY2BGR); 52 | } 53 | 54 | for (int i = 0; i < matchResults.size(); i++) 55 | { 56 | cv::Point2i temp; 57 | std::vector pts; 58 | temp.x = std::round(matchResults[i].LeftTop.x); 59 | temp.y = std::round(matchResults[i].LeftTop.y); 60 | pts.push_back(temp); 61 | temp.x = std::round(matchResults[i].RightTop.x); 62 | temp.y = std::round(matchResults[i].RightTop.y); 63 | pts.push_back(temp); 64 | temp.x = std::round(matchResults[i].RightBottom.x); 65 | temp.y = std::round(matchResults[i].RightBottom.y); 66 | pts.push_back(temp); 67 | temp.x = std::round(matchResults[i].LeftBottom.x); 68 | temp.y = std::round(matchResults[i].LeftBottom.y); 69 | pts.push_back(temp); 70 | 71 | cv::polylines(drawFrame, pts, true, cv::Scalar(0, 255, 0), 1, cv::LINE_8); 72 | } 73 | 74 | cv::imwrite("demo.png", drawFrame); 75 | cv::imshow("demo", drawFrame); 76 | cv::waitKey(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /matcher/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND MATCHER_SOURCEFILES 2 | matcher.cpp 3 | ../include/matcher.h 4 | base_matcher/base_matcher.h 5 | base_matcher/base_matcher.cpp 6 | Pattern_Matching/PatternMatching.h 7 | Pattern_Matching/PatternMatching.cpp 8 | 9 | ) 10 | 11 | list(APPEND MATCHER_C_TYPE_SOURCEFILES 12 | matcher_ctype.cpp 13 | ../include/matcher.h 14 | 15 | ) 16 | 17 | if(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 18 | set(VERSIONINFO_RC "${CMAKE_BINARY_DIR}/VersionInfo.rc") 19 | configure_file("${CMAKE_SOURCE_DIR}/VersionInfo.rc.in" 20 | "${VERSIONINFO_RC}") 21 | if (BUILD_SHARED_LIBS) 22 | add_library(templatematching SHARED ${MATCHER_SOURCEFILES} ${VERSIONINFO_RC}) 23 | target_compile_definitions(templatematching PUBLIC -DLIB_SHARED_BUILD) 24 | target_compile_definitions(templatematching PRIVATE -DLIB_EXPORTS) 25 | 26 | add_library(templatematching_ctype SHARED ${MATCHER_C_TYPE_SOURCEFILES} ${VERSIONINFO_RC}) 27 | target_compile_definitions(templatematching_ctype PUBLIC -DLIB_SHARED_BUILD) 28 | target_compile_definitions(templatematching_ctype PRIVATE -DLIB_EXPORTS) 29 | else() 30 | add_library(templatematching STATIC ${MATCHER_SOURCEFILES} ${VERSIONINFO_RC}) 31 | add_library(templatematching_ctype STATIC ${MATCHER_C_TYPE_SOURCEFILES} ${VERSIONINFO_RC}) 32 | endif() 33 | else() 34 | if (BUILD_SHARED_LIBS) 35 | add_library(templatematching SHARED ${MATCHER_SOURCEFILES}) 36 | target_compile_definitions(templatematching PUBLIC -DLIB_SHARED_BUILD) 37 | target_compile_definitions(templatematching PRIVATE -DLIB_EXPORTS) 38 | 39 | add_library(templatematching_ctype SHARED ${MATCHER_C_TYPE_SOURCEFILES} ${VERSIONINFO_RC}) 40 | target_compile_definitions(templatematching_ctype PUBLIC -DLIB_SHARED_BUILD) 41 | target_compile_definitions(templatematching_ctype PRIVATE -DLIB_EXPORTS) 42 | else() 43 | add_library(templatematching STATIC ${MATCHER_SOURCEFILES}) 44 | add_library(templatematching_ctype STATIC ${MATCHER_C_TYPE_SOURCEFILES} ${VERSIONINFO_RC}) 45 | endif() 46 | endif() 47 | 48 | 49 | target_link_libraries(templatematching ${OpenCV_LIBS}) 50 | target_link_libraries(templatematching_ctype ${OpenCV_LIBS}) 51 | 52 | 53 | #安装规则 54 | install(TARGETS templatematching 55 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 56 | ) 57 | install(TARGETS templatematching_ctype 58 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 59 | ) 60 | install(FILES 61 | ${PROJECT_SOURCE_DIR}/include/matcher.h 62 | ${PROJECT_SOURCE_DIR}/include/template_matching.h 63 | TYPE INCLUDE 64 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 65 | 66 | if(WIN32) 67 | install(FILES 68 | ${PROJECT_SOURCE_DIR}/3rdParty/opencv/x64/vc16/bin/opencv_world490.dll 69 | TYPE BIN 70 | DESTINATION ${CMAKE_INSTALL_BINDIR}) 71 | else() 72 | 73 | endif() 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 模板匹配 2 | 3 | - 基于[Fastest Image Pattern Matching](https://github.com/DennisLiu1993/Fastest_Image_Pattern_Matching) 4 | 5 | ![demo1](asserts/demo1.png) 6 | 7 | ![demo2](asserts/demo2.png) 8 | 9 | ## 改进 10 | 11 | 1. 封装为更易用的库 12 | 2. 跨平台适配 13 | 14 | ## 计划 15 | 16 | - [ ] 优化代码结构 17 | - [x] 支持python 18 | 19 | ## 编译 20 | 21 | ### clone代码 22 | 23 | git克隆时同步克隆子模块 24 | 25 | ```shell 26 | git clone --recurse-submodules https://github.com/acai66/opencv_matching.git 27 | ``` 28 | 29 | ### 编译代码 30 | 31 | #### Windows 32 | 33 | 使用各种编译cmake的方法(vs2022、vs2019、vs2017、或cmake-gui)编译即可,演示使用vs2022编译,其余工具供参考。 34 | 35 | 编译演示视频:[B站链接](https://www.bilibili.com/video/BV1hu4m1F7D1) 36 | 37 | #### Linux 38 | 39 | 演示Ubuntu 22.04下编译,其他发行版类似 40 | 41 | 1. 安装依赖和编译工具 42 | 43 | ```shell 44 | sudo apt-get update 45 | sudo apt-get install libopencv-dev build-essential cmake 46 | ``` 47 | 48 | 2. 终端进入到项目根目录,创建build文件夹 49 | 50 | ```shell 51 | cd opencv_matching 52 | mkdir build 53 | cd build 54 | ``` 55 | 56 | 3. cmake构建 57 | 58 | ```shell 59 | cmake -DCMAKE_INSTALL_PREFIX=./install .. 60 | ``` 61 | 62 | 4. make编译 63 | 64 | ```shell 65 | make 66 | ``` 67 | 68 | 5. 安装 69 | 70 | ```shell 71 | make install 72 | ``` 73 | 74 | 成功后会在 `build` 目录下生成 `install` 文件夹,里面包含编译好的库(lib)和头文件(include), 以及 `demo` 可执行文件(bin). 75 | 76 | 目录结构如下: 77 | 78 | ```shell 79 | install 80 | ├── bin 81 | │   └── demo 82 | ├── include 83 | │   ├── matcher.h 84 | │   └── templatematching.h 85 | └── lib 86 | ├── libtemplatematching.so 87 | └── libtemplatematching_ctype.so 88 | ``` 89 | 90 | 到 `install/bin` 下运行 `demo` 时,需要确保 `libtemplatematching.so` 在运行目录下,或者将 `libtemplatematching.so` 放到系统库目录下。 91 | 92 | ```shell 93 | cd install/bin/ 94 | cp ../lib/libtemplatematching.so ./ 95 | ./demo 96 | ``` 97 | 98 | 99 | 100 | ### 注意事项 101 | 102 | - 作者机器上不同项目使用的opencv版本不同,所以没有把opencv添加到系统环境变量中,opencv放进了3rdParty文件夹下,目录结构参考如下: 103 | 104 | ![windows_opencv_path](asserts/opencv_path.png) 105 | 106 | - 如需修改opencv路径,可能需要修改如下信息,具体参考 `CMakeLists.txt` 和 `matcher/CMakeLists.txt` 文件: 107 | 108 | `CMakeLists.txt` 109 | 110 | ```cmake 111 | if(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") 112 | set(OpenCV_DIR ${PROJECT_SOURCE_DIR}/3rdParty/opencv) 113 | endif() 114 | ``` 115 | 116 | `matcher/CMakeLists.txt` 117 | 118 | ```cmake 119 | if(WIN32) 120 | install(FILES 121 | ${PROJECT_SOURCE_DIR}/3rdParty/opencv/x64/vc16/bin/opencv_world490.dll 122 | TYPE BIN 123 | DESTINATION ${CMAKE_INSTALL_BINDIR}) 124 | else() 125 | ``` 126 | 127 | ## 使用 128 | 129 | 将编译的库集成到其他项目中 130 | 131 | 编译完成后,可以在安装目录下找到 `templatematching.dll` 和 `templatematching_ctype.dll` 132 | 133 | ### C++ 134 | 135 | c++编译时只需要引入头文件即可,dll是在运行时加载的,只需要 `templatematching.dll`,详细调用参考 `demo.cpp` 136 | 137 | ### python 138 | 139 | python使用时需要将 `templatematching.dll` 和 `templatematching_ctype.dll` 放进运行目录,演示代码参考 `py_demo.py` 140 | -------------------------------------------------------------------------------- /py_demo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import ctypes 3 | import numpy as np 4 | import cv2 5 | 6 | # 定义MatchResult结构体 7 | class MatchResult(ctypes.Structure): 8 | _fields_ = [ 9 | ('leftTopX', ctypes.c_double), 10 | ('leftTopY', ctypes.c_double), 11 | ('leftBottomX', ctypes.c_double), 12 | ('leftBottomY', ctypes.c_double), 13 | ('rightTopX', ctypes.c_double), 14 | ('rightTopY', ctypes.c_double), 15 | ('rightBottomX', ctypes.c_double), 16 | ('rightBottomY', ctypes.c_double), 17 | ('centerX', ctypes.c_double), 18 | ('centerY', ctypes.c_double), 19 | ('angle', ctypes.c_double), 20 | ('score', ctypes.c_double) 21 | ] 22 | 23 | # 定义Matcher类 24 | class Matcher: 25 | def __init__(self, dll_path, maxCount, scoreThreshold, iouThreshold, angle, minArea): 26 | self.lib = ctypes.CDLL(dll_path) 27 | self.lib.matcher.argtypes = [ctypes.c_int, ctypes.c_float, ctypes.c_float, ctypes.c_float, ctypes.c_float] 28 | self.lib.matcher.restype = ctypes.c_void_p 29 | self.lib.setTemplate.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int, ctypes.c_int, ctypes.c_int] 30 | self.lib.match.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_ubyte), ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.POINTER(MatchResult), ctypes.c_int] 31 | 32 | if maxCount <= 0: 33 | raise ValueError("maxCount must be greater than 0") 34 | self.maxCount = maxCount 35 | self.scoreThreshold = scoreThreshold 36 | self.iouThreshold = iouThreshold 37 | self.angle = angle 38 | self.minArea = minArea 39 | 40 | self.matcher = self.lib.matcher(maxCount, scoreThreshold, iouThreshold, angle, minArea) 41 | 42 | self.results = (MatchResult * self.maxCount)() 43 | 44 | def set_template(self, image): 45 | height, width = image.shape[0], image.shape[1] 46 | channels = 1 47 | data = image.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)) 48 | return self.lib.setTemplate(self.matcher, data, width, height, channels) 49 | 50 | def match(self, image): 51 | if image.ndim == 3: 52 | if image.shape[2] == 3: 53 | image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 54 | elif image.shape[2] == 1: 55 | image = image[:, :, 0] 56 | else: 57 | raise ValueError("Invalid image shape") 58 | height, width = image.shape[0], image.shape[1] 59 | channels = 1 60 | data = image.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)) 61 | return self.lib.match(self.matcher, data, width, height, channels, self.results, self.maxCount) 62 | 63 | # 示例调用 64 | maxCount = 1 65 | scoreThreshold = 0.5 66 | iouThreshold = 0.4 67 | angle = 0 68 | minArea = 256 69 | 70 | dll_path = './templatematching_ctype.dll' # 模板匹配库路径 71 | 72 | # 创建Matcher对象 73 | matcher = Matcher(dll_path, maxCount, scoreThreshold, iouThreshold, angle, minArea) 74 | if matcher is None: 75 | print("Create Matcher failed") 76 | sys.exit(111) 77 | 78 | # 读取模板图像 79 | image = cv2.imread('image.png', cv2.IMREAD_GRAYSCALE) 80 | if image is None: 81 | print("Read image failed") 82 | sys.exit(111) 83 | 84 | # 设置模板 85 | matcher.set_template(image) 86 | 87 | # 读取摄像头 88 | cap = cv2.VideoCapture(0) 89 | if not cap.isOpened(): 90 | print("Open camera failed") 91 | sys.exit(112) 92 | 93 | while True: 94 | ret, frame = cap.read() 95 | if not ret: 96 | print("Read camera failed") 97 | break 98 | 99 | # 匹配 100 | matches_count = matcher.match(frame) 101 | 102 | if matches_count < 0: 103 | print("Match failed!") 104 | 105 | assert matches_count <= matcher.maxCount, "matches_count must be less than or equal to maxCount" 106 | 107 | # 显示结果 108 | for i in range(min(matches_count, matcher.maxCount)): 109 | result = matcher.results[i] 110 | if result.score > 0: 111 | cv2.polylines(frame, [np.array([[result.leftTopX, result.leftTopY], [result.leftBottomX, result.leftBottomY], [result.rightBottomX, result.rightBottomY], [result.rightTopX, result.rightTopY]], np.int32)], True, (0, 255, 0), 1) 112 | cv2.putText(frame, str(result.score), (int(result.leftTopX), int(result.leftTopY)), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 1) 113 | 114 | cv2.imshow('frame', frame) 115 | if cv2.waitKey(1) & 0xFF == ord('q'): 116 | break 117 | cap.release() 118 | cv2.destroyAllWindows() 119 | -------------------------------------------------------------------------------- /matcher/matcher_ctype.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #pragma warning(disable: 4251) 3 | #endif // _WIN32 4 | 5 | #ifdef LIB_SHARED_BUILD 6 | #ifdef _WIN32 7 | #ifdef LIB_EXPORTS 8 | #define LIB_API __declspec(dllexport) 9 | #else 10 | #define LIB_API __declspec(dllimport) 11 | #endif // MY_LIB_EXPORTS 12 | #else 13 | #define LIB_API 14 | #endif // _WIN32 15 | #else 16 | #define LIB_API 17 | #endif // MY_LIB_SHARED_BUILD 18 | 19 | #include 20 | 21 | #include "../include/matcher.h" 22 | #include "../include/template_matching.h" 23 | 24 | #include "matcher.h" 25 | #ifdef _WIN32 26 | #include "windows.h" 27 | #else 28 | #include 29 | #endif // _WIN32 30 | 31 | 32 | // 动态库函数指针 33 | typedef template_matching::Matcher* (*InitMD)(const template_matching::MatcherParam&); 34 | 35 | 36 | // 匹配结果 37 | struct LIB_API MatchResult 38 | { 39 | double leftTopX; 40 | double leftTopY; 41 | double leftBottomX; 42 | double leftBottomY; 43 | double rightTopX; 44 | double rightTopY; 45 | double rightBottomX; 46 | double rightBottomY; 47 | double centerX; 48 | double centerY; 49 | double angle; 50 | double score; 51 | }; 52 | 53 | 54 | class Matcher 55 | { 56 | public: 57 | Matcher(const template_matching::MatcherParam& param); 58 | ~Matcher(); 59 | virtual int match(const cv::Mat& frame, MatchResult* Results, int maxCount); 60 | virtual int setTemplate(const cv::Mat& frame); 61 | 62 | protected: 63 | 64 | 65 | private: 66 | 67 | #ifdef WIN32 68 | // windows 动态加载dll 69 | HINSTANCE _handle = nullptr; 70 | #else 71 | void* _handle = nullptr; 72 | #endif 73 | 74 | InitMD _myGetMatcher = nullptr; 75 | template_matching::Matcher* _matcher = nullptr; 76 | }; 77 | 78 | Matcher::Matcher(const template_matching::MatcherParam& param) 79 | { 80 | 81 | #ifdef WIN32 82 | // windows 动态加载dll 83 | _handle = LoadLibrary("templatematching.dll"); 84 | if (_handle == nullptr) 85 | { 86 | std::cerr << "Error : failed to load templatematching.dll!" << std::endl; 87 | return; 88 | } 89 | 90 | // 获取动态库内的函数 91 | _myGetMatcher = (InitMD)GetProcAddress(_handle, "GetMatcher"); 92 | #else 93 | // linux 动态加载dll 94 | _handle = dlopen("libtemplatematching.so", RTLD_LAZY); 95 | if (_handle == nullptr) 96 | { 97 | char *dlopenError = dlerror(); 98 | if (dlopenError != nullptr) 99 | { 100 | std::cerr << "Error : " << dlopenError << std::endl; 101 | } 102 | std::cerr << "Error : failed to load libtemplatematching.so!" << std::endl; 103 | return; 104 | } 105 | 106 | // 获取动态库内的函数 107 | _myGetMatcher = (InitMD)dlsym(_handle, "GetMatcher"); 108 | #endif 109 | 110 | if (_myGetMatcher == nullptr) 111 | { 112 | std::cerr << "Error : failed to load getDetector!" << std::endl; 113 | return; 114 | } 115 | 116 | _matcher = _myGetMatcher(param); 117 | _matcher->setMetricsTime(false); 118 | 119 | } 120 | 121 | Matcher::~Matcher() 122 | { 123 | if (_matcher != nullptr) 124 | { 125 | delete _matcher; 126 | } 127 | 128 | if (_handle != nullptr) 129 | { 130 | #ifdef _WIN32 131 | FreeLibrary(_handle); 132 | #else 133 | dlclose(_handle); 134 | #endif // _WIN32 135 | } 136 | } 137 | 138 | int Matcher::setTemplate(const cv::Mat& frame) 139 | { 140 | int returnSize = 0; 141 | if (_matcher) 142 | { 143 | returnSize = _matcher->setTemplate(frame); 144 | } 145 | else 146 | { 147 | returnSize = -4; 148 | } 149 | 150 | return returnSize; 151 | } 152 | 153 | 154 | int Matcher::match(const cv::Mat& frame, MatchResult* Results, int maxCount) 155 | { 156 | // 匹配结果 157 | std::vector matchResults; 158 | 159 | if (_matcher) 160 | { 161 | int returnSize = _matcher->match(frame, matchResults); 162 | //std::cout << "Lib: returnSize: " << returnSize << std::endl; 163 | if (returnSize < 0) 164 | { 165 | return returnSize; 166 | } 167 | 168 | returnSize = matchResults.size() < maxCount ? matchResults.size() : maxCount; 169 | 170 | 171 | 172 | for (int i = 0; i < returnSize; i++) 173 | { 174 | (Results + i)->leftTopX = matchResults[i].LeftTop.x; 175 | (Results + i)->leftTopY = matchResults[i].LeftTop.y; 176 | (Results + i)->leftBottomX = matchResults[i].LeftBottom.x; 177 | (Results + i)->leftBottomY = matchResults[i].LeftBottom.y; 178 | (Results + i)->rightTopX = matchResults[i].RightTop.x; 179 | (Results + i)->rightTopY = matchResults[i].RightTop.y; 180 | (Results + i)->rightBottomX = matchResults[i].RightBottom.x; 181 | (Results + i)->rightBottomY = matchResults[i].RightBottom.y; 182 | (Results + i)->centerX = matchResults[i].Center.x; 183 | (Results + i)->centerY = matchResults[i].Center.y; 184 | (Results + i)->angle = matchResults[i].Angle; 185 | (Results + i)->score = matchResults[i].Score; 186 | } 187 | 188 | return returnSize; 189 | } 190 | else 191 | { 192 | return -3; 193 | } 194 | } 195 | 196 | // 对外提供dll功能接口:初始化匹配器 197 | extern"C" LIB_API Matcher * matcher(int maxCount, float scoreThreshold, float iouThreshold, float angle, float minArea) 198 | { 199 | template_matching::MatcherParam param; 200 | param.matcherType = template_matching::MatcherType::PATTERN; 201 | param.maxCount = maxCount; 202 | param.scoreThreshold = scoreThreshold; 203 | param.iouThreshold = iouThreshold; 204 | param.angle = angle; 205 | param.minArea = minArea; 206 | 207 | Matcher* obj1 = new Matcher(param); 208 | 209 | return obj1; 210 | } 211 | 212 | // 对外提供dll功能接口:设置模板 213 | extern"C" LIB_API int setTemplate(Matcher * obj1, uchar * data, int width, int height, int channels) 214 | { 215 | cv::Mat img; 216 | 217 | if (channels == 1) 218 | { 219 | img = cv::Mat(cv::Size(width, height), CV_8UC1, data); 220 | } 221 | else 222 | { 223 | std::cerr << "Error : not support channels: " << channels << std::endl; 224 | return -1; 225 | } 226 | 227 | int returnSize = obj1->setTemplate(img); 228 | 229 | return returnSize; 230 | 231 | } 232 | 233 | // 对外提供dll功能接口:执行匹配 234 | extern"C" LIB_API int match(Matcher * obj1, uchar * data, int width, int height, int channels, MatchResult * Results, int maxCount) 235 | { 236 | cv::Mat img; 237 | 238 | if (channels == 1) 239 | { 240 | img = cv::Mat(cv::Size(width, height), CV_8UC1, data); 241 | } 242 | else 243 | { 244 | std::cerr << "Error : not support channels: " << channels << std::endl; 245 | return -1; 246 | } 247 | 248 | int returnSize = obj1->match(img, Results, maxCount); 249 | 250 | return returnSize; 251 | 252 | } 253 | -------------------------------------------------------------------------------- /demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // C++17 标准库中的文件系统库 4 | namespace fs = std::filesystem; 5 | 6 | #include "matcher.h" 7 | #ifdef _WIN32 8 | #include "windows.h" 9 | #else 10 | #include 11 | #endif // _WIN32 12 | 13 | 14 | // 动态库函数指针 15 | typedef template_matching::Matcher* (*InitMD)(const template_matching::MatcherParam&); 16 | 17 | cv::Rect box; 18 | bool drawing_box = false; 19 | bool change_template = false; 20 | 21 | // 鼠标回调函数,用于绘制 ROI 22 | void mouse_callback(int event, int x, int y, int, void*) 23 | { 24 | switch (event) 25 | { 26 | case cv::EVENT_MOUSEMOVE: 27 | if (drawing_box) 28 | { 29 | box.width = x - box.x; 30 | box.height = y - box.y; 31 | } 32 | break; 33 | case cv::EVENT_LBUTTONDOWN: 34 | drawing_box = true; 35 | box = cv::Rect(x, y, 0, 0); 36 | break; 37 | case cv::EVENT_LBUTTONUP: 38 | drawing_box = false; 39 | change_template = true; 40 | if (box.width < 0) 41 | { 42 | box.x += box.width; 43 | box.width *= -1; 44 | } 45 | if (box.height < 0) 46 | { 47 | box.y += box.height; 48 | box.height *= -1; 49 | } 50 | break; 51 | } 52 | } 53 | 54 | int main(int argc, char** argv) 55 | { 56 | // 匹配器参数 57 | template_matching::MatcherParam param; 58 | param.angle = 0; 59 | param.iouThreshold = 0; 60 | param.matcherType = template_matching::MatcherType::PATTERN; 61 | param.maxCount = 1; 62 | param.minArea = 256; 63 | param.scoreThreshold = 0.5; 64 | 65 | // 匹配结果 66 | std::vector matchResults; 67 | 68 | #ifdef _WIN32 69 | // windows 动态加载dll 70 | HINSTANCE handle = nullptr; 71 | handle = LoadLibrary("templatematching.dll"); 72 | if (handle == nullptr) 73 | { 74 | std::cerr << "Error : failed to load templatematching.dll!" << std::endl; 75 | return -2; 76 | } 77 | 78 | // 获取动态库内的函数 79 | InitMD myGetMatcher; 80 | myGetMatcher = (InitMD)GetProcAddress(handle, "GetMatcher"); 81 | #else 82 | // linux 动态加载dll 83 | void* handle = nullptr; 84 | handle = dlopen("libtemplatematching.so", RTLD_LAZY); 85 | if (handle == nullptr) 86 | { 87 | char *dlopenError = dlerror(); 88 | if (dlopenError != nullptr) 89 | { 90 | std::cerr << "Error : " << dlopenError << std::endl; 91 | } 92 | std::cerr << "Error : failed to load libtemplatematching.so!" << std::endl; 93 | return -2; 94 | } 95 | 96 | // 获取动态库内的函数 97 | InitMD myGetMatcher; 98 | myGetMatcher = (InitMD)dlsym(handle, "GetMatcher"); 99 | #endif // _WIN32 100 | // std::cout << "Load getDetector." << std::endl; 101 | 102 | // 初始化匹配器 103 | std::cout << "initiating..." << std::endl; 104 | template_matching::Matcher* matcher = myGetMatcher(param); 105 | std::cout << "initialized." << std::endl; 106 | 107 | if (matcher) 108 | { 109 | matcher->setMetricsTime(true); 110 | 111 | std::chrono::steady_clock::time_point startTime, endTime; 112 | std::chrono::duration timeUse; 113 | 114 | // 打开0号摄像头 115 | cv::VideoCapture cap(0); 116 | if (!cap.isOpened()) 117 | { 118 | std::cerr << "Error: failed to open camera." << std::endl; 119 | return -1; 120 | } 121 | // 设置摄像头分辨率 122 | //cap.set(cv::CAP_PROP_FRAME_WIDTH, 1920); 123 | //cap.set(cv::CAP_PROP_FRAME_HEIGHT, 1080); 124 | // 设置摄像头编码 125 | //cap.set(cv::CAP_PROP_FOURCC, cv::VideoWriter::fourcc('M', 'J', 'P', 'G')); 126 | 127 | cv::namedWindow("frame"); 128 | cv::setMouseCallback("frame", mouse_callback); 129 | 130 | // 读取摄像头图像 131 | cv::Mat frame, drawFrame, roi; 132 | while (true) 133 | { 134 | cap >> frame; 135 | if (frame.empty()) 136 | { 137 | std::cerr << "Error: failed to read frame." << std::endl; 138 | break; 139 | } 140 | 141 | cv::Mat drawFrame = frame.clone(); 142 | if (drawFrame.channels() == 1) 143 | { 144 | cv::cvtColor(drawFrame, drawFrame, cv::COLOR_GRAY2BGR); 145 | } 146 | 147 | if (drawing_box) 148 | cv::rectangle(drawFrame, box, cv::Scalar(0, 0, 255), 2); 149 | else if (box.area() > 0) 150 | { 151 | if (change_template) 152 | { 153 | //cv::rectangle(drawFrame, box, cv::Scalar(0, 255, 0), 2); 154 | roi = frame(box); 155 | //cv::imshow("ROI", roi); 156 | cv::cvtColor(roi, roi, cv::COLOR_BGR2GRAY); 157 | matcher->setTemplate(roi); 158 | change_template = false; 159 | } 160 | 161 | } 162 | 163 | startTime = std::chrono::steady_clock::now(); 164 | 165 | // 执行匹配,结果保存在 matchResults 166 | cv::Mat frame_gray; 167 | cv::cvtColor(frame, frame_gray, cv::COLOR_BGR2GRAY); 168 | matcher->match(frame_gray, matchResults); 169 | 170 | endTime = std::chrono::steady_clock::now(); 171 | timeUse = std::chrono::duration_cast>(endTime - startTime); 172 | std::cout << "match time: " << timeUse.count() << "s." << std::endl; 173 | 174 | // 可视化查看 175 | 176 | for (int i = 0; i < matchResults.size(); i++) 177 | { 178 | cv::Point2i temp; 179 | std::vector pts; 180 | temp.x = std::round(matchResults[i].LeftTop.x); 181 | temp.y = std::round(matchResults[i].LeftTop.y); 182 | pts.push_back(temp); 183 | temp.x = std::round(matchResults[i].RightTop.x); 184 | temp.y = std::round(matchResults[i].RightTop.y); 185 | pts.push_back(temp); 186 | temp.x = std::round(matchResults[i].RightBottom.x); 187 | temp.y = std::round(matchResults[i].RightBottom.y); 188 | pts.push_back(temp); 189 | temp.x = std::round(matchResults[i].LeftBottom.x); 190 | temp.y = std::round(matchResults[i].LeftBottom.y); 191 | pts.push_back(temp); 192 | 193 | cv::polylines(drawFrame, pts, true, cv::Scalar(0, 255, 0), 1, cv::LINE_8); 194 | } 195 | 196 | 197 | // 显示图像,按ESC或q退出 198 | cv::imshow("frame", drawFrame); 199 | char key = cv::waitKey(1); 200 | if (key == 27 || key == 'q' || key == 'Q') 201 | { 202 | break; 203 | } 204 | } 205 | 206 | cap.release(); 207 | cv::destroyAllWindows(); 208 | 209 | delete matcher; 210 | 211 | } 212 | else 213 | { 214 | std::cerr << "Error: failed to get matcher." << std::endl; 215 | } 216 | 217 | // 释放 windows 动态库 218 | if (handle != nullptr) 219 | { 220 | #ifdef _WIN32 221 | FreeLibrary(handle); 222 | #else 223 | dlclose(handle); 224 | #endif // _WIN32 225 | } 226 | 227 | return 0; 228 | } 229 | -------------------------------------------------------------------------------- /matcher/Pattern_Matching/PatternMatching.h: -------------------------------------------------------------------------------- 1 | #ifndef _PATTERNMATCHING_H 2 | #define _PATTERNMATCHING_H 3 | #pragma once 4 | 5 | #include "../base_matcher/base_matcher.h" 6 | #include "template_matching.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define VISION_TOLERANCE 0.0000001 13 | #define D2R (CV_PI / 180.0) 14 | #define R2D (180.0 / CV_PI) 15 | #define MATCH_CANDIDATE_NUM 5 16 | 17 | 18 | namespace template_matching 19 | { 20 | using namespace cv; 21 | using namespace std; 22 | 23 | struct s_TemplData 24 | { 25 | vector vecPyramid; 26 | vector vecTemplMean; 27 | vector vecTemplNorm; 28 | vector vecInvArea; 29 | vector vecResultEqual1; 30 | bool bIsPatternLearned; 31 | int iBorderColor; 32 | void clear() 33 | { 34 | vector().swap(vecPyramid); 35 | vector().swap(vecTemplNorm); 36 | vector().swap(vecInvArea); 37 | vector().swap(vecTemplMean); 38 | vector().swap(vecResultEqual1); 39 | } 40 | void resize(int iSize) 41 | { 42 | vecTemplMean.resize(iSize); 43 | vecTemplNorm.resize(iSize, 0); 44 | vecInvArea.resize(iSize, 1); 45 | vecResultEqual1.resize(iSize, false); 46 | } 47 | s_TemplData() 48 | { 49 | bIsPatternLearned = false; 50 | } 51 | }; 52 | struct s_MatchParameter 53 | { 54 | Point2d pt; 55 | double dMatchScore; 56 | double dMatchAngle; 57 | //Mat matRotatedSrc; 58 | Rect rectRoi; 59 | double dAngleStart; 60 | double dAngleEnd; 61 | RotatedRect rectR; 62 | Rect rectBounding; 63 | bool bDelete; 64 | 65 | double vecResult[3][3];//for subpixel 66 | int iMaxScoreIndex;//for subpixel 67 | bool bPosOnBorder; 68 | Point2d ptSubPixel; 69 | double dNewAngle; 70 | 71 | s_MatchParameter(Point2f ptMinMax, double dScore, double dAngle)//, Mat matRotatedSrc = Mat ()) 72 | { 73 | pt = ptMinMax; 74 | dMatchScore = dScore; 75 | dMatchAngle = dAngle; 76 | 77 | bDelete = false; 78 | dNewAngle = 0.0; 79 | 80 | bPosOnBorder = false; 81 | } 82 | s_MatchParameter() 83 | { 84 | double dMatchScore = 0; 85 | double dMatchAngle = 0; 86 | } 87 | ~s_MatchParameter() 88 | { 89 | 90 | } 91 | }; 92 | struct s_BlockMax 93 | { 94 | struct Block 95 | { 96 | Rect rect; 97 | double dMax; 98 | Point ptMaxLoc; 99 | Block() 100 | {} 101 | Block(Rect rect_, double dMax_, Point ptMaxLoc_) 102 | { 103 | rect = rect_; 104 | dMax = dMax_; 105 | ptMaxLoc = ptMaxLoc_; 106 | } 107 | }; 108 | s_BlockMax() 109 | {} 110 | vector vecBlock; 111 | Mat matSrc; 112 | s_BlockMax(Mat matSrc_, Size sizeTemplate) 113 | { 114 | matSrc = matSrc_; 115 | //將matSrc 拆成數個block,分別計算最大值 116 | int iBlockW = sizeTemplate.width * 2; 117 | int iBlockH = sizeTemplate.height * 2; 118 | 119 | int iCol = matSrc.cols / iBlockW; 120 | bool bHResidue = matSrc.cols % iBlockW != 0; 121 | 122 | int iRow = matSrc.rows / iBlockH; 123 | bool bVResidue = matSrc.rows % iBlockH != 0; 124 | 125 | if (iCol == 0 || iRow == 0) 126 | { 127 | vecBlock.clear(); 128 | return; 129 | } 130 | 131 | vecBlock.resize(iCol * iRow); 132 | int iCount = 0; 133 | for (int y = 0; y < iRow; y++) 134 | { 135 | for (int x = 0; x < iCol; x++) 136 | { 137 | Rect rectBlock(x * iBlockW, y * iBlockH, iBlockW, iBlockH); 138 | vecBlock[iCount].rect = rectBlock; 139 | minMaxLoc(matSrc(rectBlock), 0, &vecBlock[iCount].dMax, 0, &vecBlock[iCount].ptMaxLoc); 140 | vecBlock[iCount].ptMaxLoc += rectBlock.tl(); 141 | iCount++; 142 | } 143 | } 144 | if (bHResidue && bVResidue) 145 | { 146 | Rect rectRight(iCol * iBlockW, 0, matSrc.cols - iCol * iBlockW, matSrc.rows); 147 | Block blockRight; 148 | blockRight.rect = rectRight; 149 | minMaxLoc(matSrc(rectRight), 0, &blockRight.dMax, 0, &blockRight.ptMaxLoc); 150 | blockRight.ptMaxLoc += rectRight.tl(); 151 | vecBlock.push_back(blockRight); 152 | 153 | Rect rectBottom(0, iRow * iBlockH, iCol * iBlockW, matSrc.rows - iRow * iBlockH); 154 | Block blockBottom; 155 | blockBottom.rect = rectBottom; 156 | minMaxLoc(matSrc(rectBottom), 0, &blockBottom.dMax, 0, &blockBottom.ptMaxLoc); 157 | blockBottom.ptMaxLoc += rectBottom.tl(); 158 | vecBlock.push_back(blockBottom); 159 | } 160 | else if (bHResidue) 161 | { 162 | Rect rectRight(iCol * iBlockW, 0, matSrc.cols - iCol * iBlockW, matSrc.rows); 163 | Block blockRight; 164 | blockRight.rect = rectRight; 165 | minMaxLoc(matSrc(rectRight), 0, &blockRight.dMax, 0, &blockRight.ptMaxLoc); 166 | blockRight.ptMaxLoc += rectRight.tl(); 167 | vecBlock.push_back(blockRight); 168 | } 169 | else 170 | { 171 | Rect rectBottom(0, iRow * iBlockH, matSrc.cols, matSrc.rows - iRow * iBlockH); 172 | Block blockBottom; 173 | blockBottom.rect = rectBottom; 174 | minMaxLoc(matSrc(rectBottom), 0, &blockBottom.dMax, 0, &blockBottom.ptMaxLoc); 175 | blockBottom.ptMaxLoc += rectBottom.tl(); 176 | vecBlock.push_back(blockBottom); 177 | } 178 | } 179 | void UpdateMax(Rect rectIgnore) 180 | { 181 | if (vecBlock.size() == 0) 182 | return; 183 | //找出所有跟rectIgnore交集的block 184 | int iSize = vecBlock.size(); 185 | for (int i = 0; i < iSize; i++) 186 | { 187 | Rect rectIntersec = rectIgnore & vecBlock[i].rect; 188 | //無交集 189 | if (rectIntersec.width == 0 && rectIntersec.height == 0) 190 | continue; 191 | //有交集,更新極值和極值位置 192 | minMaxLoc(matSrc(vecBlock[i].rect), 0, &vecBlock[i].dMax, 0, &vecBlock[i].ptMaxLoc); 193 | vecBlock[i].ptMaxLoc += vecBlock[i].rect.tl(); 194 | } 195 | } 196 | void GetMaxValueLoc(double& dMax, Point& ptMaxLoc) 197 | { 198 | int iSize = vecBlock.size(); 199 | if (iSize == 0) 200 | { 201 | minMaxLoc(matSrc, 0, &dMax, 0, &ptMaxLoc); 202 | return; 203 | } 204 | //從block中找最大值 205 | int iIndex = 0; 206 | dMax = vecBlock[0].dMax; 207 | for (int i = 1; i < iSize; i++) 208 | { 209 | if (vecBlock[i].dMax >= dMax) 210 | { 211 | iIndex = i; 212 | dMax = vecBlock[i].dMax; 213 | } 214 | } 215 | ptMaxLoc = vecBlock[iIndex].ptMaxLoc; 216 | } 217 | }; 218 | 219 | class PatternMatcher : public BaseMatcher 220 | { 221 | public: 222 | PatternMatcher(const template_matching::MatcherParam& param); 223 | ~PatternMatcher(); 224 | virtual int match(const cv::Mat & frame, std::vector &matchResults) override; 225 | 226 | virtual int setTemplate(const cv::Mat& templateImage) override; 227 | 228 | 229 | protected: 230 | 231 | 232 | private: 233 | s_TemplData m_TemplData; 234 | bool m_bDebugMode = false; 235 | bool m_bSubPixel = true; 236 | bool m_bStopLayer1 = false; 237 | 238 | }; 239 | 240 | 241 | } 242 | 243 | #endif 244 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cmake 2 | CMakeLists.txt.user 3 | CMakeCache.txt 4 | CMakeFiles 5 | CMakeScripts 6 | Testing 7 | Makefile 8 | cmake_install.cmake 9 | install_manifest.txt 10 | compile_commands.json 11 | CTestTestfile.cmake 12 | _deps 13 | 14 | # custom 15 | 3rdParty/opencv/ 16 | 3rdParty/mipp/ 17 | .ipynb_checkpoints/ 18 | CMakeSettings.json 19 | 20 | # Cmake builds 21 | build 22 | # VS cmake builds 23 | out 24 | # VS Code 25 | .vscode 26 | 27 | # Prerequisites 28 | *.d 29 | 30 | # Compiled Object files 31 | *.slo 32 | *.lo 33 | *.o 34 | *.obj 35 | 36 | # Precompiled Headers 37 | *.gch 38 | *.pch 39 | 40 | # Compiled Dynamic libraries 41 | *.so 42 | *.dylib 43 | *.dll 44 | 45 | # Fortran module files 46 | *.mod 47 | *.smod 48 | 49 | # Compiled Static libraries 50 | *.lai 51 | *.la 52 | *.a 53 | *.lib 54 | 55 | # Executables 56 | *.exe 57 | *.out 58 | *.app 59 | 60 | ## Ignore Visual Studio temporary files, build results, and 61 | ## files generated by popular Visual Studio add-ons. 62 | ## 63 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 64 | 65 | # User-specific files 66 | *.rsuser 67 | *.suo 68 | *.user 69 | *.userosscache 70 | *.sln.docstates 71 | 72 | # User-specific files (MonoDevelop/Xamarin Studio) 73 | *.userprefs 74 | 75 | # Mono auto generated files 76 | mono_crash.* 77 | 78 | # Build results 79 | [Dd]ebug/ 80 | [Dd]ebugPublic/ 81 | [Rr]elease/ 82 | [Rr]eleases/ 83 | x64/ 84 | x86/ 85 | [Ww][Ii][Nn]32/ 86 | [Aa][Rr][Mm]/ 87 | [Aa][Rr][Mm]64/ 88 | bld/ 89 | [Bb]in/ 90 | [Oo]bj/ 91 | [Ll]og/ 92 | [Ll]ogs/ 93 | 94 | # Visual Studio 2015/2017 cache/options directory 95 | .vs/ 96 | # Uncomment if you have tasks that create the project's static files in wwwroot 97 | #wwwroot/ 98 | 99 | # Visual Studio 2017 auto generated files 100 | Generated\ Files/ 101 | 102 | # MSTest test Results 103 | [Tt]est[Rr]esult*/ 104 | [Bb]uild[Ll]og.* 105 | 106 | # NUnit 107 | *.VisualState.xml 108 | TestResult.xml 109 | nunit-*.xml 110 | 111 | # Build Results of an ATL Project 112 | [Dd]ebugPS/ 113 | [Rr]eleasePS/ 114 | dlldata.c 115 | 116 | # Benchmark Results 117 | BenchmarkDotNet.Artifacts/ 118 | 119 | # .NET Core 120 | project.lock.json 121 | project.fragment.lock.json 122 | artifacts/ 123 | 124 | # ASP.NET Scaffolding 125 | ScaffoldingReadMe.txt 126 | 127 | # StyleCop 128 | StyleCopReport.xml 129 | 130 | # Files built by Visual Studio 131 | *_i.c 132 | *_p.c 133 | *_h.h 134 | *.ilk 135 | *.meta 136 | *.obj 137 | *.iobj 138 | *.pch 139 | *.pdb 140 | *.ipdb 141 | *.pgc 142 | *.pgd 143 | *.rsp 144 | *.sbr 145 | *.tlb 146 | *.tli 147 | *.tlh 148 | *.tmp 149 | *.tmp_proj 150 | *_wpftmp.csproj 151 | *.log 152 | *.tlog 153 | *.vspscc 154 | *.vssscc 155 | .builds 156 | *.pidb 157 | *.svclog 158 | *.scc 159 | 160 | # Chutzpah Test files 161 | _Chutzpah* 162 | 163 | # Visual C++ cache files 164 | ipch/ 165 | *.aps 166 | *.ncb 167 | *.opendb 168 | *.opensdf 169 | *.sdf 170 | *.cachefile 171 | *.VC.db 172 | *.VC.VC.opendb 173 | 174 | # Visual Studio profiler 175 | *.psess 176 | *.vsp 177 | *.vspx 178 | *.sap 179 | 180 | # Visual Studio Trace Files 181 | *.e2e 182 | 183 | # TFS 2012 Local Workspace 184 | $tf/ 185 | 186 | # Guidance Automation Toolkit 187 | *.gpState 188 | 189 | # ReSharper is a .NET coding add-in 190 | _ReSharper*/ 191 | *.[Rr]e[Ss]harper 192 | *.DotSettings.user 193 | 194 | # TeamCity is a build add-in 195 | _TeamCity* 196 | 197 | # DotCover is a Code Coverage Tool 198 | *.dotCover 199 | 200 | # AxoCover is a Code Coverage Tool 201 | .axoCover/* 202 | !.axoCover/settings.json 203 | 204 | # Coverlet is a free, cross platform Code Coverage Tool 205 | coverage*.json 206 | coverage*.xml 207 | coverage*.info 208 | 209 | # Visual Studio code coverage results 210 | *.coverage 211 | *.coveragexml 212 | 213 | # NCrunch 214 | _NCrunch_* 215 | .*crunch*.local.xml 216 | nCrunchTemp_* 217 | 218 | # MightyMoose 219 | *.mm.* 220 | AutoTest.Net/ 221 | 222 | # Web workbench (sass) 223 | .sass-cache/ 224 | 225 | # Installshield output folder 226 | [Ee]xpress/ 227 | 228 | # DocProject is a documentation generator add-in 229 | DocProject/buildhelp/ 230 | DocProject/Help/*.HxT 231 | DocProject/Help/*.HxC 232 | DocProject/Help/*.hhc 233 | DocProject/Help/*.hhk 234 | DocProject/Help/*.hhp 235 | DocProject/Help/Html2 236 | DocProject/Help/html 237 | 238 | # Click-Once directory 239 | publish/ 240 | 241 | # Publish Web Output 242 | *.[Pp]ublish.xml 243 | *.azurePubxml 244 | # Note: Comment the next line if you want to checkin your web deploy settings, 245 | # but database connection strings (with potential passwords) will be unencrypted 246 | *.pubxml 247 | *.publishproj 248 | 249 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 250 | # checkin your Azure Web App publish settings, but sensitive information contained 251 | # in these scripts will be unencrypted 252 | PublishScripts/ 253 | 254 | # NuGet Packages 255 | *.nupkg 256 | # NuGet Symbol Packages 257 | *.snupkg 258 | # The packages folder can be ignored because of Package Restore 259 | **/[Pp]ackages/* 260 | # except build/, which is used as an MSBuild target. 261 | !**/[Pp]ackages/build/ 262 | # Uncomment if necessary however generally it will be regenerated when needed 263 | #!**/[Pp]ackages/repositories.config 264 | # NuGet v3's project.json files produces more ignorable files 265 | *.nuget.props 266 | *.nuget.targets 267 | 268 | # Microsoft Azure Build Output 269 | csx/ 270 | *.build.csdef 271 | 272 | # Microsoft Azure Emulator 273 | ecf/ 274 | rcf/ 275 | 276 | # Windows Store app package directories and files 277 | AppPackages/ 278 | BundleArtifacts/ 279 | Package.StoreAssociation.xml 280 | _pkginfo.txt 281 | *.appx 282 | *.appxbundle 283 | *.appxupload 284 | 285 | # Visual Studio cache files 286 | # files ending in .cache can be ignored 287 | *.[Cc]ache 288 | # but keep track of directories ending in .cache 289 | !?*.[Cc]ache/ 290 | 291 | # Others 292 | ClientBin/ 293 | ~$* 294 | *~ 295 | *.dbmdl 296 | *.dbproj.schemaview 297 | *.jfm 298 | *.pfx 299 | *.publishsettings 300 | orleans.codegen.cs 301 | 302 | # Including strong name files can present a security risk 303 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 304 | #*.snk 305 | 306 | # Since there are multiple workflows, uncomment next line to ignore bower_components 307 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 308 | #bower_components/ 309 | 310 | # RIA/Silverlight projects 311 | Generated_Code/ 312 | 313 | # Backup & report files from converting an old project file 314 | # to a newer Visual Studio version. Backup files are not needed, 315 | # because we have git ;-) 316 | _UpgradeReport_Files/ 317 | Backup*/ 318 | UpgradeLog*.XML 319 | UpgradeLog*.htm 320 | ServiceFabricBackup/ 321 | *.rptproj.bak 322 | 323 | # SQL Server files 324 | *.mdf 325 | *.ldf 326 | *.ndf 327 | 328 | # Business Intelligence projects 329 | *.rdl.data 330 | *.bim.layout 331 | *.bim_*.settings 332 | *.rptproj.rsuser 333 | *- [Bb]ackup.rdl 334 | *- [Bb]ackup ([0-9]).rdl 335 | *- [Bb]ackup ([0-9][0-9]).rdl 336 | 337 | # Microsoft Fakes 338 | FakesAssemblies/ 339 | 340 | # GhostDoc plugin setting file 341 | *.GhostDoc.xml 342 | 343 | # Node.js Tools for Visual Studio 344 | .ntvs_analysis.dat 345 | node_modules/ 346 | 347 | # Visual Studio 6 build log 348 | *.plg 349 | 350 | # Visual Studio 6 workspace options file 351 | *.opt 352 | 353 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 354 | *.vbw 355 | 356 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 357 | *.vbp 358 | 359 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 360 | *.dsw 361 | *.dsp 362 | 363 | # Visual Studio 6 technical files 364 | *.ncb 365 | *.aps 366 | 367 | # Visual Studio LightSwitch build output 368 | **/*.HTMLClient/GeneratedArtifacts 369 | **/*.DesktopClient/GeneratedArtifacts 370 | **/*.DesktopClient/ModelManifest.xml 371 | **/*.Server/GeneratedArtifacts 372 | **/*.Server/ModelManifest.xml 373 | _Pvt_Extensions 374 | 375 | # Paket dependency manager 376 | .paket/paket.exe 377 | paket-files/ 378 | 379 | # FAKE - F# Make 380 | .fake/ 381 | 382 | # CodeRush personal settings 383 | .cr/personal 384 | 385 | # Python Tools for Visual Studio (PTVS) 386 | __pycache__/ 387 | *.pyc 388 | 389 | # Cake - Uncomment if you are using it 390 | # tools/** 391 | # !tools/packages.config 392 | 393 | # Tabs Studio 394 | *.tss 395 | 396 | # Telerik's JustMock configuration file 397 | *.jmconfig 398 | 399 | # BizTalk build output 400 | *.btp.cs 401 | *.btm.cs 402 | *.odx.cs 403 | *.xsd.cs 404 | 405 | # OpenCover UI analysis results 406 | OpenCover/ 407 | 408 | # Azure Stream Analytics local run output 409 | ASALocalRun/ 410 | 411 | # MSBuild Binary and Structured Log 412 | *.binlog 413 | 414 | # NVidia Nsight GPU debugger configuration file 415 | *.nvuser 416 | 417 | # MFractors (Xamarin productivity tool) working folder 418 | .mfractor/ 419 | 420 | # Local History for Visual Studio 421 | .localhistory/ 422 | 423 | # Visual Studio History (VSHistory) files 424 | .vshistory/ 425 | 426 | # BeatPulse healthcheck temp database 427 | healthchecksdb 428 | 429 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 430 | MigrationBackup/ 431 | 432 | # Ionide (cross platform F# VS Code tools) working folder 433 | .ionide/ 434 | 435 | # Fody - auto-generated XML schema 436 | FodyWeavers.xsd 437 | 438 | # VS Code files for those working on multiple tools 439 | .vscode/* 440 | !.vscode/settings.json 441 | !.vscode/tasks.json 442 | !.vscode/launch.json 443 | !.vscode/extensions.json 444 | *.code-workspace 445 | 446 | # Local History for Visual Studio Code 447 | .history/ 448 | 449 | # Windows Installer files from build outputs 450 | *.cab 451 | *.msi 452 | *.msix 453 | *.msm 454 | *.msp 455 | 456 | # JetBrains Rider 457 | *.sln.iml -------------------------------------------------------------------------------- /matcher/Pattern_Matching/PatternMatching.cpp: -------------------------------------------------------------------------------- 1 | #include "PatternMatching.h" 2 | 3 | #ifdef __aarch64__ 4 | #include 5 | #endif 6 | 7 | 8 | namespace template_matching { 9 | 10 | int GetTopLayer(Mat* matTempl, int iMinDstLength) 11 | { 12 | int iTopLayer = 0; 13 | int iMinReduceArea = iMinDstLength * iMinDstLength; 14 | int iArea = matTempl->cols * matTempl->rows; 15 | while (iArea > iMinReduceArea) 16 | { 17 | iArea /= 4; 18 | iTopLayer++; 19 | } 20 | return iTopLayer; 21 | } 22 | 23 | void LearnPattern(s_TemplData& m_TemplData, Mat& m_matDst, double& m_iMinReduceArea) 24 | { 25 | m_TemplData.clear(); 26 | 27 | int iTopLayer = GetTopLayer(&m_matDst, (int)sqrt((double)m_iMinReduceArea)); 28 | buildPyramid(m_matDst, m_TemplData.vecPyramid, iTopLayer); 29 | s_TemplData* templData = &m_TemplData; 30 | templData->iBorderColor = mean(m_matDst).val[0] < 128 ? 255 : 0; 31 | int iSize = templData->vecPyramid.size(); 32 | templData->resize(iSize); 33 | 34 | for (int i = 0; i < iSize; i++) 35 | { 36 | double invArea = 1. / ((double)templData->vecPyramid[i].rows * templData->vecPyramid[i].cols); 37 | Scalar templMean, templSdv; 38 | double templNorm = 0, templSum2 = 0; 39 | 40 | meanStdDev(templData->vecPyramid[i], templMean, templSdv); 41 | templNorm = templSdv[0] * templSdv[0] + templSdv[1] * templSdv[1] + templSdv[2] * templSdv[2] + templSdv[3] * templSdv[3]; 42 | 43 | if (templNorm < DBL_EPSILON) 44 | { 45 | templData->vecResultEqual1[i] = true; 46 | } 47 | templSum2 = templNorm + templMean[0] * templMean[0] + templMean[1] * templMean[1] + templMean[2] * templMean[2] + templMean[3] * templMean[3]; 48 | 49 | 50 | templSum2 /= invArea; 51 | templNorm = std::sqrt(templNorm); 52 | templNorm /= std::sqrt(invArea); // care of accuracy here 53 | 54 | 55 | templData->vecInvArea[i] = invArea; 56 | templData->vecTemplMean[i] = templMean; 57 | templData->vecTemplNorm[i] = templNorm; 58 | } 59 | templData->bIsPatternLearned = true; 60 | } 61 | 62 | Point2f ptRotatePt2f(Point2f ptInput, Point2f ptOrg, double dAngle) 63 | { 64 | double dWidth = ptOrg.x * 2; 65 | double dHeight = ptOrg.y * 2; 66 | double dY1 = dHeight - ptInput.y, dY2 = dHeight - ptOrg.y; 67 | 68 | double dX = (ptInput.x - ptOrg.x) * cos(dAngle) - (dY1 - ptOrg.y) * sin(dAngle) + ptOrg.x; 69 | double dY = (ptInput.x - ptOrg.x) * sin(dAngle) + (dY1 - ptOrg.y) * cos(dAngle) + dY2; 70 | 71 | dY = -dY + dHeight; 72 | return Point2f((float)dX, (float)dY); 73 | } 74 | 75 | Size GetBestRotationSize(Size sizeSrc, Size sizeDst, double dRAngle) 76 | { 77 | double dRAngle_radian = dRAngle * D2R; 78 | Point ptLT(0, 0), ptLB(0, sizeSrc.height - 1), ptRB(sizeSrc.width - 1, sizeSrc.height - 1), ptRT(sizeSrc.width - 1, 0); 79 | Point2f ptCenter((sizeSrc.width - 1) / 2.0f, (sizeSrc.height - 1) / 2.0f); 80 | Point2f ptLT_R = ptRotatePt2f(Point2f(ptLT), ptCenter, dRAngle_radian); 81 | Point2f ptLB_R = ptRotatePt2f(Point2f(ptLB), ptCenter, dRAngle_radian); 82 | Point2f ptRB_R = ptRotatePt2f(Point2f(ptRB), ptCenter, dRAngle_radian); 83 | Point2f ptRT_R = ptRotatePt2f(Point2f(ptRT), ptCenter, dRAngle_radian); 84 | 85 | float fTopY = max(max(ptLT_R.y, ptLB_R.y), max(ptRB_R.y, ptRT_R.y)); 86 | float fBottomY = min(min(ptLT_R.y, ptLB_R.y), min(ptRB_R.y, ptRT_R.y)); 87 | float fRightX = max(max(ptLT_R.x, ptLB_R.x), max(ptRB_R.x, ptRT_R.x)); 88 | float fLeftX = min(min(ptLT_R.x, ptLB_R.x), min(ptRB_R.x, ptRT_R.x)); 89 | 90 | if (dRAngle > 360) 91 | dRAngle -= 360; 92 | else if (dRAngle < 0) 93 | dRAngle += 360; 94 | 95 | if (fabs(fabs(dRAngle) - 90) < VISION_TOLERANCE || fabs(fabs(dRAngle) - 270) < VISION_TOLERANCE) 96 | { 97 | return Size(sizeSrc.height, sizeSrc.width); 98 | } 99 | else if (fabs(dRAngle) < VISION_TOLERANCE || fabs(fabs(dRAngle) - 180) < VISION_TOLERANCE) 100 | { 101 | return sizeSrc; 102 | } 103 | 104 | double dAngle = dRAngle; 105 | 106 | if (dAngle > 0 && dAngle < 90) 107 | { 108 | ; 109 | } 110 | else if (dAngle > 90 && dAngle < 180) 111 | { 112 | dAngle -= 90; 113 | } 114 | else if (dAngle > 180 && dAngle < 270) 115 | { 116 | dAngle -= 180; 117 | } 118 | else if (dAngle > 270 && dAngle < 360) 119 | { 120 | dAngle -= 270; 121 | } 122 | else//Debug 123 | { 124 | cout << "Unkown" << endl;; 125 | } 126 | 127 | float fH1 = sizeDst.width * sin(dAngle * D2R) * cos(dAngle * D2R); 128 | float fH2 = sizeDst.height * sin(dAngle * D2R) * cos(dAngle * D2R); 129 | 130 | int iHalfHeight = (int)ceil(fTopY - ptCenter.y - fH1); 131 | int iHalfWidth = (int)ceil(fRightX - ptCenter.x - fH2); 132 | 133 | Size sizeRet(iHalfWidth * 2, iHalfHeight * 2); 134 | 135 | bool bWrongSize = (sizeDst.width < sizeRet.width && sizeDst.height > sizeRet.height) 136 | || (sizeDst.width > sizeRet.width && sizeDst.height < sizeRet.height 137 | || sizeDst.area() > sizeRet.area()); 138 | if (bWrongSize) 139 | sizeRet = Size(int(fRightX - fLeftX + 0.5), int(fTopY - fBottomY + 0.5)); 140 | 141 | return sizeRet; 142 | } 143 | 144 | #ifdef __aarch64__ 145 | /* 146 | inline int32_t vaddvq_s32(int32x4_t v) { 147 | int32x2_t tmp = vpadd_s32(vget_low_s32(v), vget_high_s32(v)); 148 | return vget_lane_s32(vpadd_s32(tmp, tmp), 0); 149 | } 150 | 151 | inline int32_t IM_Conv_SIMD(uint8_t* pCharKernel, uint8_t* pCharConv, int iLength) { 152 | const int iBlockSize = 16, Block = iLength / iBlockSize; 153 | int32x4_t SumV = vdupq_n_s32(0); 154 | uint8x16_t Zero = vdupq_n_u8(0); 155 | 156 | for (int Y = 0; Y < Block * iBlockSize; Y += iBlockSize) { 157 | uint8x16_t SrcK = vld1q_u8(pCharKernel + Y); 158 | uint8x16_t SrcC = vld1q_u8(pCharConv + Y); 159 | int16x8_t SrcK_L = vmovl_u8(vget_low_u8(SrcK)); 160 | int16x8_t SrcK_H = vmovl_u8(vget_high_u8(SrcK)); 161 | int16x8_t SrcC_L = vmovl_u8(vget_low_u8(SrcC)); 162 | int16x8_t SrcC_H = vmovl_u8(vget_high_u8(SrcC)); 163 | int32x4_t SumT = vaddq_s32(vmull_s16(vget_low_s16(SrcK_L), vget_low_s16(SrcC_L)), vmull_s16(vget_low_s16(SrcK_H), vget_low_s16(SrcC_H))); 164 | SumV = vaddq_s32(SumV, SumT); 165 | } 166 | 167 | int32_t Sum = vaddvq_s32(SumV); 168 | 169 | for (int Y = Block * iBlockSize; Y < iLength; Y++) { 170 | Sum += pCharKernel[Y] * pCharConv[Y]; 171 | } 172 | 173 | return Sum; 174 | } 175 | */ 176 | inline uint32_t IM_Conv_SIMD(uint8_t* pCharKernel, uint8_t* pCharConv, int iLength) { 177 | uint32_t Sum = 0; 178 | 179 | for (int Y = 0; Y < iLength; Y++) { 180 | Sum += pCharKernel[Y] * pCharConv[Y]; 181 | } 182 | 183 | return Sum; 184 | } 185 | #elif defined(__i386__) || defined(__x86_64__) || defined(_M_X64) 186 | //From ImageShop 187 | // 4個有符號的32位的數據相加的和。 188 | inline int _mm_hsum_epi32(__m128i V) // V3 V2 V1 V0 189 | { 190 | // 實測這個速度要快些,_mm_extract_epi32最慢。 191 | __m128i T = _mm_add_epi32(V, _mm_srli_si128(V, 8)); // V3+V1 V2+V0 V1 V0 192 | T = _mm_add_epi32(T, _mm_srli_si128(T, 4)); // V3+V1+V2+V0 V2+V0+V1 V1+V0 V0 193 | return _mm_cvtsi128_si32(T); // 提取低位 194 | } 195 | // 基於SSE的字節數據的乘法。 196 | // 需要卷積的核矩陣。 197 | // 卷積矩陣。 198 | // 矩陣所有元素的長度。 199 | inline int IM_Conv_SIMD(unsigned char* pCharKernel, unsigned char* pCharConv, int iLength) 200 | { 201 | const int iBlockSize = 16, Block = iLength / iBlockSize; 202 | __m128i SumV = _mm_setzero_si128(); 203 | __m128i Zero = _mm_setzero_si128(); 204 | for (int Y = 0; Y < Block * iBlockSize; Y += iBlockSize) 205 | { 206 | __m128i SrcK = _mm_loadu_si128((__m128i*)(pCharKernel + Y)); 207 | __m128i SrcC = _mm_loadu_si128((__m128i*)(pCharConv + Y)); 208 | __m128i SrcK_L = _mm_unpacklo_epi8(SrcK, Zero); 209 | __m128i SrcK_H = _mm_unpackhi_epi8(SrcK, Zero); 210 | __m128i SrcC_L = _mm_unpacklo_epi8(SrcC, Zero); 211 | __m128i SrcC_H = _mm_unpackhi_epi8(SrcC, Zero); 212 | __m128i SumT = _mm_add_epi32(_mm_madd_epi16(SrcK_L, SrcC_L), _mm_madd_epi16(SrcK_H, SrcC_H)); 213 | SumV = _mm_add_epi32(SumV, SumT); 214 | } 215 | int Sum = _mm_hsum_epi32(SumV); 216 | for (int Y = Block * iBlockSize; Y < iLength; Y++) 217 | { 218 | Sum += pCharKernel[Y] * pCharConv[Y]; 219 | } 220 | return Sum; 221 | } 222 | #else 223 | // Other architecture 224 | #endif 225 | 226 | 227 | void CCOEFF_Denominator(cv::Mat& matSrc, s_TemplData* pTemplData, cv::Mat& matResult, int iLayer) 228 | { 229 | if (pTemplData->vecResultEqual1[iLayer]) 230 | { 231 | matResult = Scalar::all(1); 232 | return; 233 | } 234 | double* q0 = 0, * q1 = 0, * q2 = 0, * q3 = 0; 235 | 236 | Mat sum, sqsum; 237 | integral(matSrc, sum, sqsum, CV_64F); 238 | 239 | q0 = (double*)sqsum.data; 240 | q1 = q0 + pTemplData->vecPyramid[iLayer].cols; 241 | q2 = (double*)(sqsum.data + pTemplData->vecPyramid[iLayer].rows * sqsum.step); 242 | q3 = q2 + pTemplData->vecPyramid[iLayer].cols; 243 | 244 | double* p0 = (double*)sum.data; 245 | double* p1 = p0 + pTemplData->vecPyramid[iLayer].cols; 246 | double* p2 = (double*)(sum.data + pTemplData->vecPyramid[iLayer].rows * sum.step); 247 | double* p3 = p2 + pTemplData->vecPyramid[iLayer].cols; 248 | 249 | int sumstep = sum.data ? (int)(sum.step / sizeof(double)) : 0; 250 | int sqstep = sqsum.data ? (int)(sqsum.step / sizeof(double)) : 0; 251 | 252 | // 253 | double dTemplMean0 = pTemplData->vecTemplMean[iLayer][0]; 254 | double dTemplNorm = pTemplData->vecTemplNorm[iLayer]; 255 | double dInvArea = pTemplData->vecInvArea[iLayer]; 256 | // 257 | 258 | int i, j; 259 | for (i = 0; i < matResult.rows; i++) 260 | { 261 | float* rrow = matResult.ptr(i); 262 | int idx = i * sumstep; 263 | int idx2 = i * sqstep; 264 | 265 | for (j = 0; j < matResult.cols; j += 1, idx += 1, idx2 += 1) 266 | { 267 | double num = rrow[j], t; 268 | double wndMean2 = 0, wndSum2 = 0; 269 | 270 | t = p0[idx] - p1[idx] - p2[idx] + p3[idx]; 271 | wndMean2 += t * t; 272 | num -= t * dTemplMean0; 273 | wndMean2 *= dInvArea; 274 | 275 | 276 | t = q0[idx2] - q1[idx2] - q2[idx2] + q3[idx2]; 277 | wndSum2 += t; 278 | 279 | 280 | //t = std::sqrt (MAX (wndSum2 - wndMean2, 0)) * dTemplNorm; 281 | 282 | double diff2 = MAX(wndSum2 - wndMean2, 0); 283 | if (diff2 <= std::min(0.5, 10 * FLT_EPSILON * wndSum2)) 284 | t = 0; // avoid rounding errors 285 | else 286 | t = std::sqrt(diff2) * dTemplNorm; 287 | 288 | if (fabs(num) < t) 289 | num /= t; 290 | else if (fabs(num) < t * 1.125) 291 | num = num > 0 ? 1 : -1; 292 | else 293 | num = 0; 294 | 295 | rrow[j] = (float)num; 296 | } 297 | } 298 | } 299 | 300 | void MatchTemplate(cv::Mat& matSrc, s_TemplData* pTemplData, cv::Mat& matResult, int iLayer, bool bUseSIMD) 301 | { 302 | if (bUseSIMD) 303 | { 304 | //From ImageShop 305 | matResult.create(matSrc.rows - pTemplData->vecPyramid[iLayer].rows + 1, 306 | matSrc.cols - pTemplData->vecPyramid[iLayer].cols + 1, CV_32FC1); 307 | matResult.setTo(0); 308 | cv::Mat& matTemplate = pTemplData->vecPyramid[iLayer]; 309 | 310 | int t_r_end = matTemplate.rows, t_r = 0; 311 | for (int r = 0; r < matResult.rows; r++) 312 | { 313 | float* r_matResult = matResult.ptr(r); 314 | uchar* r_source = matSrc.ptr(r); 315 | uchar* r_template, * r_sub_source; 316 | for (int c = 0; c < matResult.cols; ++c, ++r_matResult, ++r_source) 317 | { 318 | r_template = matTemplate.ptr(); 319 | r_sub_source = r_source; 320 | for (t_r = 0; t_r < t_r_end; ++t_r, r_sub_source += matSrc.cols, r_template += matTemplate.cols) 321 | { 322 | *r_matResult = *r_matResult + IM_Conv_SIMD(r_template, r_sub_source, matTemplate.cols); 323 | } 324 | } 325 | } 326 | //From ImageShop 327 | } 328 | else 329 | matchTemplate(matSrc, pTemplData->vecPyramid[iLayer], matResult, CV_TM_CCORR); 330 | 331 | /*Mat diff; 332 | absdiff(matResult, matResult, diff); 333 | double dMaxValue; 334 | minMaxLoc(diff, 0, &dMaxValue, 0,0);*/ 335 | CCOEFF_Denominator(matSrc, pTemplData, matResult, iLayer); 336 | } 337 | 338 | Point GetNextMaxLoc(Mat& matResult, Point ptMaxLoc, Size sizeTemplate, double& dMaxValue, double dMaxOverlap) 339 | { 340 | //比對到的區域完全不重疊 : +-一個樣板寬高 341 | //int iStartX = ptMaxLoc.x - iTemplateW; 342 | //int iStartY = ptMaxLoc.y - iTemplateH; 343 | //int iEndX = ptMaxLoc.x + iTemplateW; 344 | 345 | //int iEndY = ptMaxLoc.y + iTemplateH; 346 | ////塗黑 347 | //rectangle (matResult, Rect (iStartX, iStartY, 2 * iTemplateW * (1-dMaxOverlap * 2), 2 * iTemplateH * (1-dMaxOverlap * 2)), Scalar (dMinValue), CV_FILLED); 348 | ////得到下一個最大值 349 | //Point ptNewMaxLoc; 350 | //minMaxLoc (matResult, 0, &dMaxValue, 0, &ptNewMaxLoc); 351 | //return ptNewMaxLoc; 352 | 353 | //比對到的區域需考慮重疊比例 354 | int iStartX = ptMaxLoc.x - sizeTemplate.width * (1 - dMaxOverlap); 355 | int iStartY = ptMaxLoc.y - sizeTemplate.height * (1 - dMaxOverlap); 356 | //塗黑 357 | rectangle(matResult, Rect(iStartX, iStartY, 2 * sizeTemplate.width * (1 - dMaxOverlap), 2 * sizeTemplate.height * (1 - dMaxOverlap)), Scalar(-1), CV_FILLED); 358 | //得到下一個最大值 359 | Point ptNewMaxLoc; 360 | minMaxLoc(matResult, 0, &dMaxValue, 0, &ptNewMaxLoc); 361 | return ptNewMaxLoc; 362 | } 363 | 364 | Point GetNextMaxLoc(Mat& matResult, Point ptMaxLoc, Size sizeTemplate, double& dMaxValue, double dMaxOverlap, s_BlockMax& blockMax) 365 | { 366 | //比對到的區域需考慮重疊比例 367 | int iStartX = int(ptMaxLoc.x - sizeTemplate.width * (1 - dMaxOverlap)); 368 | int iStartY = int(ptMaxLoc.y - sizeTemplate.height * (1 - dMaxOverlap)); 369 | Rect rectIgnore(iStartX, iStartY, int(2 * sizeTemplate.width * (1 - dMaxOverlap)) 370 | , int(2 * sizeTemplate.height * (1 - dMaxOverlap))); 371 | //塗黑 372 | rectangle(matResult, rectIgnore, Scalar(-1), CV_FILLED); 373 | blockMax.UpdateMax(rectIgnore); 374 | Point ptReturn; 375 | blockMax.GetMaxValueLoc(dMaxValue, ptReturn); 376 | return ptReturn; 377 | } 378 | 379 | bool compareScoreBig2Small(const s_MatchParameter& lhs, const s_MatchParameter& rhs) { return lhs.dMatchScore > rhs.dMatchScore; } 380 | bool comparePtWithAngle(const pair lhs, const pair rhs) { return lhs.second < rhs.second; } 381 | 382 | void GetRotatedROI(Mat& matSrc, Size size, Point2f ptLT, double dAngle, Mat& matROI) 383 | { 384 | double dAngle_radian = dAngle * D2R; 385 | Point2f ptC((matSrc.cols - 1) / 2.0f, (matSrc.rows - 1) / 2.0f); 386 | Point2f ptLT_rotate = ptRotatePt2f(ptLT, ptC, dAngle_radian); 387 | Size sizePadding(size.width + 6, size.height + 6); 388 | 389 | 390 | Mat rMat = getRotationMatrix2D(ptC, dAngle, 1); 391 | rMat.at(0, 2) -= ptLT_rotate.x - 3; 392 | rMat.at(1, 2) -= ptLT_rotate.y - 3; 393 | //平移旋轉矩陣(0, 2) (1, 2)的減,為旋轉後的圖形偏移,-= ptLT_rotate.x - 3 代表旋轉後的圖形往-X方向移動ptLT_rotate.x - 3 394 | //Debug 395 | 396 | //Debug 397 | warpAffine(matSrc, matROI, rMat, sizePadding); 398 | } 399 | 400 | bool SubPixEsimation(vector* vec, double* dNewX, double* dNewY, double* dNewAngle, double dAngleStep, int iMaxScoreIndex) 401 | { 402 | //Az=S, (A.T)Az=(A.T)s, z = ((A.T)A).inv (A.T)s 403 | 404 | Mat matA(27, 10, CV_64F); 405 | Mat matZ(10, 1, CV_64F); 406 | Mat matS(27, 1, CV_64F); 407 | 408 | double dX_maxScore = (*vec)[iMaxScoreIndex].pt.x; 409 | double dY_maxScore = (*vec)[iMaxScoreIndex].pt.y; 410 | double dTheata_maxScore = (*vec)[iMaxScoreIndex].dMatchAngle; 411 | int iRow = 0; 412 | /*for (int x = -1; x <= 1; x++) 413 | { 414 | for (int y = -1; y <= 1; y++) 415 | { 416 | for (int theta = 0; theta <= 2; theta++) 417 | {*/ 418 | for (int theta = 0; theta <= 2; theta++) 419 | { 420 | for (int y = -1; y <= 1; y++) 421 | { 422 | for (int x = -1; x <= 1; x++) 423 | { 424 | //xx yy tt xy xt yt x y t 1 425 | //0 1 2 3 4 5 6 7 8 9 426 | double dX = dX_maxScore + x; 427 | double dY = dY_maxScore + y; 428 | //double dT = (*vec)[theta].dMatchAngle + (theta - 1) * dAngleStep; 429 | double dT = (dTheata_maxScore + (theta - 1) * dAngleStep) * D2R; 430 | matA.at(iRow, 0) = dX * dX; 431 | matA.at(iRow, 1) = dY * dY; 432 | matA.at(iRow, 2) = dT * dT; 433 | matA.at(iRow, 3) = dX * dY; 434 | matA.at(iRow, 4) = dX * dT; 435 | matA.at(iRow, 5) = dY * dT; 436 | matA.at(iRow, 6) = dX; 437 | matA.at(iRow, 7) = dY; 438 | matA.at(iRow, 8) = dT; 439 | matA.at(iRow, 9) = 1.0; 440 | matS.at(iRow, 0) = (*vec)[iMaxScoreIndex + (theta - 1)].vecResult[x + 1][y + 1]; 441 | iRow++; 442 | #ifdef _DEBUG 443 | /*string str = format ("%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f", dValueA[0], dValueA[1], dValueA[2], dValueA[3], dValueA[4], dValueA[5], dValueA[6], dValueA[7], dValueA[8], dValueA[9]); 444 | fileA << str << endl; 445 | str = format ("%.6f", dValueS[iRow]); 446 | fileS << str << endl;*/ 447 | #endif 448 | } 449 | } 450 | } 451 | //求解Z矩陣,得到k0~k9 452 | //[ x* ] = [ 2k0 k3 k4 ]-1 [ -k6 ] 453 | //| y* | = | k3 2k1 k5 | | -k7 | 454 | //[ t* ] = [ k4 k5 2k2 ] [ -k8 ] 455 | 456 | //solve (matA, matS, matZ, DECOMP_SVD); 457 | matZ = (matA.t() * matA).inv() * matA.t() * matS; 458 | Mat matZ_t; 459 | transpose(matZ, matZ_t); 460 | double* dZ = matZ_t.ptr(0); 461 | Mat matK1 = (Mat_(3, 3) << 462 | (2 * dZ[0]), dZ[3], dZ[4], 463 | dZ[3], (2 * dZ[1]), dZ[5], 464 | dZ[4], dZ[5], (2 * dZ[2])); 465 | Mat matK2 = (Mat_(3, 1) << -dZ[6], -dZ[7], -dZ[8]); 466 | Mat matDelta = matK1.inv() * matK2; 467 | 468 | *dNewX = matDelta.at(0, 0); 469 | *dNewY = matDelta.at(1, 0); 470 | *dNewAngle = matDelta.at(2, 0) * R2D; 471 | return true; 472 | } 473 | 474 | void FilterWithScore(vector* vec, double dScore) 475 | { 476 | std::sort(vec->begin(), vec->end(), compareScoreBig2Small); 477 | int iSize = vec->size(), iIndexDelete = iSize + 1; 478 | for (int i = 0; i < iSize; i++) 479 | { 480 | if ((*vec)[i].dMatchScore < dScore) 481 | { 482 | iIndexDelete = i; 483 | break; 484 | } 485 | } 486 | if (iIndexDelete == iSize + 1)//沒有任何元素小於dScore 487 | return; 488 | vec->erase(vec->begin() + iIndexDelete, vec->end()); 489 | return; 490 | } 491 | 492 | void SortPtWithCenter(vector& vecSort) 493 | { 494 | int iSize = (int)vecSort.size(); 495 | Point2f ptCenter; 496 | for (int i = 0; i < iSize; i++) 497 | ptCenter += vecSort[i]; 498 | ptCenter /= iSize; 499 | 500 | Point2f vecX(1, 0); 501 | 502 | vector> vecPtAngle(iSize); 503 | for (int i = 0; i < iSize; i++) 504 | { 505 | vecPtAngle[i].first = vecSort[i];//pt 506 | Point2f vec1(vecSort[i].x - ptCenter.x, vecSort[i].y - ptCenter.y); 507 | float fNormVec1 = vec1.x * vec1.x + vec1.y * vec1.y; 508 | float fDot = vec1.x; 509 | 510 | if (vec1.y < 0)//若點在中心的上方 511 | { 512 | vecPtAngle[i].second = acos(fDot / fNormVec1) * R2D; 513 | } 514 | else if (vec1.y > 0)//下方 515 | { 516 | vecPtAngle[i].second = 360 - acos(fDot / fNormVec1) * R2D; 517 | } 518 | else//點與中心在相同Y 519 | { 520 | if (vec1.x - ptCenter.x > 0) 521 | vecPtAngle[i].second = 0; 522 | else 523 | vecPtAngle[i].second = 180; 524 | } 525 | 526 | } 527 | std::sort(vecPtAngle.begin(), vecPtAngle.end(), comparePtWithAngle); 528 | for (int i = 0; i < iSize; i++) 529 | vecSort[i] = vecPtAngle[i].first; 530 | } 531 | 532 | cv::RotatedRect RotatedRect2(const Point2f& _point1, const Point2f& _point2, const Point2f& _point3) 533 | { 534 | Point2f _center = 0.5f * (_point1 + _point3); 535 | Vec2f vecs[2]; 536 | vecs[0] = Vec2f(_point1 - _point2); 537 | vecs[1] = Vec2f(_point2 - _point3); 538 | double x = std::max(norm(_point1), std::max(norm(_point2), norm(_point3))); 539 | double a = std::min(norm(vecs[0]), norm(vecs[1])); 540 | // check that given sides are perpendicular 541 | //CV_Assert(std::fabs(vecs[0].ddot(vecs[1])) * a <= FLT_EPSILON * 9 * x * (norm(vecs[0]) * norm(vecs[1]))); 542 | 543 | // wd_i stores which vector (0,1) or (1,2) will make the width 544 | // One of them will definitely have slope within -1 to 1 545 | int wd_i = 0; 546 | if (std::fabs(vecs[1][1]) < std::fabs(vecs[1][0])) wd_i = 1; 547 | int ht_i = (wd_i + 1) % 2; 548 | 549 | float _angle = std::atan(vecs[wd_i][1] / vecs[wd_i][0]) * 180.0f / (float)CV_PI; 550 | float _width = (float)norm(vecs[wd_i]); 551 | float _height = (float)norm(vecs[ht_i]); 552 | 553 | return cv::RotatedRect(_center, Size2f(_width, _height), _angle); 554 | } 555 | 556 | void FilterWithRotatedRect(vector* vec, int iMethod, double dMaxOverLap) 557 | { 558 | int iMatchSize = (int)vec->size(); 559 | RotatedRect rect1, rect2; 560 | for (int i = 0; i < iMatchSize - 1; i++) 561 | { 562 | if (vec->at(i).bDelete) 563 | continue; 564 | for (int j = i + 1; j < iMatchSize; j++) 565 | { 566 | if (vec->at(j).bDelete) 567 | continue; 568 | rect1 = vec->at(i).rectR; 569 | rect2 = vec->at(j).rectR; 570 | vector vecInterSec; 571 | int iInterSecType = rotatedRectangleIntersection(rect1, rect2, vecInterSec); 572 | if (iInterSecType == INTERSECT_NONE)//無交集 573 | continue; 574 | else if (iInterSecType == INTERSECT_FULL) //一個矩形包覆另一個 575 | { 576 | int iDeleteIndex; 577 | if (iMethod == CV_TM_SQDIFF) 578 | iDeleteIndex = (vec->at(i).dMatchScore <= vec->at(j).dMatchScore) ? j : i; 579 | else 580 | iDeleteIndex = (vec->at(i).dMatchScore >= vec->at(j).dMatchScore) ? j : i; 581 | vec->at(iDeleteIndex).bDelete = true; 582 | } 583 | else//交點 > 0 584 | { 585 | if (vecInterSec.size() < 3)//一個或兩個交點 586 | continue; 587 | else 588 | { 589 | int iDeleteIndex; 590 | //求面積與交疊比例 591 | //SortPtWithCenter(vecInterSec); 592 | vector order_pts; 593 | cv::convexHull(cv::Mat(vecInterSec), order_pts, true); 594 | double dArea = contourArea(order_pts); 595 | double dRatio = dArea / rect1.size.area(); 596 | //若大於最大交疊比例,選分數高的 597 | if (dRatio > dMaxOverLap) 598 | { 599 | if (iMethod == CV_TM_SQDIFF) 600 | iDeleteIndex = (vec->at(i).dMatchScore <= vec->at(j).dMatchScore) ? j : i; 601 | else 602 | iDeleteIndex = (vec->at(i).dMatchScore >= vec->at(j).dMatchScore) ? j : i; 603 | vec->at(iDeleteIndex).bDelete = true; 604 | } 605 | } 606 | } 607 | } 608 | } 609 | vector::iterator it; 610 | for (it = vec->begin(); it != vec->end();) 611 | { 612 | if ((*it).bDelete) 613 | it = vec->erase(it); 614 | else 615 | ++it; 616 | } 617 | } 618 | 619 | PatternMatcher::PatternMatcher(const MatcherParam& param) 620 | { 621 | initFinishedFlag_ = false; 622 | 623 | initFinishedFlag_ = initMatcher(param); 624 | if (initFinishedFlag_ == false) 625 | { 626 | logger_->info("Failed to init matcher!"); 627 | return; 628 | } 629 | 630 | logger_->info("成功!"); 631 | 632 | initFinishedFlag_ = true; 633 | } 634 | 635 | PatternMatcher::~PatternMatcher() 636 | { 637 | 638 | } 639 | 640 | int PatternMatcher::match(const cv::Mat& image, std::vector& matchResults) 641 | { 642 | matchResults.clear(); 643 | if (image.empty()) 644 | return -1; 645 | if (templateImage_.empty() || templateImage_.channels() != 1) 646 | { 647 | logger_->error("image empty or channels != 1."); 648 | return -5; 649 | } 650 | if ((templateImage_.cols < image.cols && templateImage_.rows > image.rows) || (templateImage_.cols > image.cols && templateImage_.rows < image.rows)) 651 | return -2; 652 | if (templateImage_.size().area() > image.size().area()) 653 | return -3; 654 | if (!m_TemplData.bIsPatternLearned) 655 | return -4; 656 | 657 | double d1 = clock(); 658 | //決定金字塔層數 總共為1 + iLayer層 659 | int iTopLayer = GetTopLayer(&templateImage_, (int)sqrt((double)matchParam_.minArea)); 660 | //建立金字塔 661 | vector vecMatSrcPyr; 662 | buildPyramid(image, vecMatSrcPyr, iTopLayer); 663 | 664 | s_TemplData* pTemplData = &m_TemplData; 665 | 666 | //第一階段以最頂層找出大致角度與ROI 667 | double dAngleStep = atan(2.0 / max(pTemplData->vecPyramid[iTopLayer].cols, pTemplData->vecPyramid[iTopLayer].rows)) * R2D; 668 | //double dAngleStep = atan(2.0 / max(pTemplData->vecPyramid[0].cols, pTemplData->vecPyramid[0].rows)) * R2D; 669 | 670 | vector vecAngles; 671 | 672 | if (matchParam_.angle < VISION_TOLERANCE) 673 | vecAngles.push_back(0.0); 674 | else 675 | { 676 | for (double dAngle = 0; dAngle < matchParam_.angle + dAngleStep; dAngle += dAngleStep) 677 | vecAngles.push_back(dAngle); 678 | for (double dAngle = -dAngleStep; dAngle > -matchParam_.angle - dAngleStep; dAngle -= dAngleStep) 679 | vecAngles.push_back(dAngle); 680 | } 681 | 682 | 683 | int iTopSrcW = vecMatSrcPyr[iTopLayer].cols, iTopSrcH = vecMatSrcPyr[iTopLayer].rows; 684 | Point2f ptCenter((iTopSrcW - 1) / 2.0f, (iTopSrcH - 1) / 2.0f); 685 | 686 | int iSize = (int)vecAngles.size(); 687 | //vector vecMatchParameter (iSize * (m_iMaxPos + MATCH_CANDIDATE_NUM)); 688 | vector vecMatchParameter; 689 | //Caculate lowest score at every layer 690 | vector vecLayerScore(iTopLayer + 1, matchParam_.scoreThreshold); 691 | for (int iLayer = 1; iLayer <= iTopLayer; iLayer++) 692 | vecLayerScore[iLayer] = vecLayerScore[iLayer - 1] * 0.9; 693 | 694 | Size sizePat = pTemplData->vecPyramid[iTopLayer].size(); 695 | bool bCalMaxByBlock = (vecMatSrcPyr[iTopLayer].size().area() / sizePat.area() > 500) && matchParam_.maxCount > 10; 696 | for (int i = 0; i < iSize; i++) 697 | { 698 | Mat matRotatedSrc, matR = getRotationMatrix2D(ptCenter, vecAngles[i], 1); 699 | Mat matResult; 700 | Point ptMaxLoc; 701 | double dValue, dMaxVal; 702 | double dRotate = clock(); 703 | Size sizeBest = GetBestRotationSize(vecMatSrcPyr[iTopLayer].size(), pTemplData->vecPyramid[iTopLayer].size(), vecAngles[i]); 704 | 705 | float fTranslationX = (sizeBest.width - 1) / 2.0f - ptCenter.x; 706 | float fTranslationY = (sizeBest.height - 1) / 2.0f - ptCenter.y; 707 | matR.at(0, 2) += fTranslationX; 708 | matR.at(1, 2) += fTranslationY; 709 | warpAffine(vecMatSrcPyr[iTopLayer], matRotatedSrc, matR, sizeBest, INTER_LINEAR, BORDER_CONSTANT, Scalar(pTemplData->iBorderColor)); 710 | 711 | MatchTemplate(matRotatedSrc, pTemplData, matResult, iTopLayer, false); 712 | 713 | if (bCalMaxByBlock) 714 | { 715 | s_BlockMax blockMax(matResult, pTemplData->vecPyramid[iTopLayer].size()); 716 | blockMax.GetMaxValueLoc(dMaxVal, ptMaxLoc); 717 | if (dMaxVal < vecLayerScore[iTopLayer]) 718 | continue; 719 | vecMatchParameter.push_back(s_MatchParameter(Point2f(ptMaxLoc.x - fTranslationX, ptMaxLoc.y - fTranslationY), dMaxVal, vecAngles[i])); 720 | for (int j = 0; j < matchParam_.maxCount + MATCH_CANDIDATE_NUM - 1; j++) 721 | { 722 | ptMaxLoc = GetNextMaxLoc(matResult, ptMaxLoc, pTemplData->vecPyramid[iTopLayer].size(), dValue, matchParam_.iouThreshold, blockMax); 723 | if (dValue < vecLayerScore[iTopLayer]) 724 | break; 725 | vecMatchParameter.push_back(s_MatchParameter(Point2f(ptMaxLoc.x - fTranslationX, ptMaxLoc.y - fTranslationY), dValue, vecAngles[i])); 726 | } 727 | } 728 | else 729 | { 730 | minMaxLoc(matResult, 0, &dMaxVal, 0, &ptMaxLoc); 731 | if (dMaxVal < vecLayerScore[iTopLayer]) 732 | continue; 733 | vecMatchParameter.push_back(s_MatchParameter(Point2f(ptMaxLoc.x - fTranslationX, ptMaxLoc.y - fTranslationY), dMaxVal, vecAngles[i])); 734 | for (int j = 0; j < matchParam_.maxCount + MATCH_CANDIDATE_NUM - 1; j++) 735 | { 736 | ptMaxLoc = GetNextMaxLoc(matResult, ptMaxLoc, pTemplData->vecPyramid[iTopLayer].size(), dValue, matchParam_.iouThreshold); 737 | if (dValue < vecLayerScore[iTopLayer]) 738 | break; 739 | vecMatchParameter.push_back(s_MatchParameter(Point2f(ptMaxLoc.x - fTranslationX, ptMaxLoc.y - fTranslationY), dValue, vecAngles[i])); 740 | } 741 | } 742 | } 743 | std::sort(vecMatchParameter.begin(), vecMatchParameter.end(), compareScoreBig2Small); 744 | 745 | 746 | int iMatchSize = (int)vecMatchParameter.size(); 747 | int iDstW = pTemplData->vecPyramid[iTopLayer].cols, iDstH = pTemplData->vecPyramid[iTopLayer].rows; 748 | 749 | //顯示第一層結果 750 | if (m_bDebugMode) 751 | { 752 | int iDebugScale = 2; 753 | 754 | Mat matShow, matResize; 755 | resize(vecMatSrcPyr[iTopLayer], matResize, vecMatSrcPyr[iTopLayer].size() * iDebugScale); 756 | cvtColor(matResize, matShow, CV_GRAY2BGR); 757 | string str = format("Toplayer, Candidate:%d", iMatchSize); 758 | vector vec; 759 | for (int i = 0; i < iMatchSize; i++) 760 | { 761 | Point2f ptLT, ptRT, ptRB, ptLB; 762 | double dRAngle = -vecMatchParameter[i].dMatchAngle * D2R; 763 | ptLT = ptRotatePt2f(vecMatchParameter[i].pt, ptCenter, dRAngle); 764 | ptRT = Point2f(ptLT.x + iDstW * (float)cos(dRAngle), ptLT.y - iDstW * (float)sin(dRAngle)); 765 | ptLB = Point2f(ptLT.x + iDstH * (float)sin(dRAngle), ptLT.y + iDstH * (float)cos(dRAngle)); 766 | ptRB = Point2f(ptRT.x + iDstH * (float)sin(dRAngle), ptRT.y + iDstH * (float)cos(dRAngle)); 767 | line(matShow, ptLT * iDebugScale, ptLB * iDebugScale, Scalar(0, 255, 0)); 768 | line(matShow, ptLB * iDebugScale, ptRB * iDebugScale, Scalar(0, 255, 0)); 769 | line(matShow, ptRB * iDebugScale, ptRT * iDebugScale, Scalar(0, 255, 0)); 770 | line(matShow, ptRT * iDebugScale, ptLT * iDebugScale, Scalar(0, 255, 0)); 771 | circle(matShow, ptLT * iDebugScale, 1, Scalar(0, 0, 255)); 772 | vec.push_back(ptLT * iDebugScale); 773 | vec.push_back(ptRT * iDebugScale); 774 | vec.push_back(ptLB * iDebugScale); 775 | vec.push_back(ptRB * iDebugScale); 776 | 777 | string strText = format("%d", i); 778 | cv::putText(matShow, strText, ptLT * iDebugScale, FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0)); 779 | } 780 | cvNamedWindow(str.c_str(), 0x10000000); 781 | Rect rectShow = boundingRect(vec); 782 | cv::imshow(str, matShow);// (rectShow)); 783 | cv::waitKey(); 784 | //moveWindow (str, 0, 0); 785 | } 786 | //顯示第一層結果 787 | 788 | //第一階段結束 789 | bool bSubPixelEstimation = m_bSubPixel; 790 | int iStopLayer = m_bStopLayer1 ? 1 : 0; //设置为1时:粗匹配,牺牲精度提升速度。 791 | //int iSearchSize = min (m_iMaxPos + MATCH_CANDIDATE_NUM, (int)vecMatchParameter.size ());//可能不需要搜尋到全部 太浪費時間 792 | vector vecAllResult; 793 | for (int i = 0; i < (int)vecMatchParameter.size(); i++) 794 | //for (int i = 0; i < iSearchSize; i++) 795 | { 796 | double dRAngle = -vecMatchParameter[i].dMatchAngle * D2R; 797 | Point2f ptLT = ptRotatePt2f(vecMatchParameter[i].pt, ptCenter, dRAngle); 798 | 799 | double dAngleStep = atan(2.0 / max(iDstW, iDstH)) * R2D;//min改為max 800 | vecMatchParameter[i].dAngleStart = vecMatchParameter[i].dMatchAngle - dAngleStep; 801 | vecMatchParameter[i].dAngleEnd = vecMatchParameter[i].dMatchAngle + dAngleStep; 802 | 803 | if (iTopLayer <= iStopLayer) 804 | { 805 | vecMatchParameter[i].pt = Point2d(ptLT * ((iTopLayer == 0) ? 1 : 2)); 806 | vecAllResult.push_back(vecMatchParameter[i]); 807 | } 808 | else 809 | { 810 | for (int iLayer = iTopLayer - 1; iLayer >= iStopLayer; iLayer--) 811 | { 812 | //搜尋角度 813 | dAngleStep = atan(2.0 / max(pTemplData->vecPyramid[iLayer].cols, pTemplData->vecPyramid[iLayer].rows)) * R2D;//min改為max 814 | vector vecAngles; 815 | //double dAngleS = vecMatchParameter[i].dAngleStart, dAngleE = vecMatchParameter[i].dAngleEnd; 816 | double dMatchedAngle = vecMatchParameter[i].dMatchAngle; 817 | if (matchParam_.angle) 818 | { 819 | for (int i = -2; i <= 2; i++) 820 | vecAngles.push_back(dMatchedAngle + dAngleStep * i); 821 | } 822 | else 823 | { 824 | if (matchParam_.angle < VISION_TOLERANCE) 825 | vecAngles.push_back(0.0); 826 | else 827 | for (int i = -2; i <= 2; i++) 828 | vecAngles.push_back(dMatchedAngle + dAngleStep * i); 829 | } 830 | Point2f ptSrcCenter((vecMatSrcPyr[iLayer].cols - 1) / 2.0f, (vecMatSrcPyr[iLayer].rows - 1) / 2.0f); 831 | iSize = (int)vecAngles.size(); 832 | vector vecNewMatchParameter(iSize); 833 | int iMaxScoreIndex = 0; 834 | double dBigValue = -1; 835 | for (int j = 0; j < iSize; j++) 836 | { 837 | Mat matResult, matRotatedSrc; 838 | double dMaxValue = 0; 839 | Point ptMaxLoc; 840 | GetRotatedROI(vecMatSrcPyr[iLayer], pTemplData->vecPyramid[iLayer].size(), ptLT * 2, vecAngles[j], matRotatedSrc); 841 | 842 | MatchTemplate(matRotatedSrc, pTemplData, matResult, iLayer, true); 843 | //matchTemplate (matRotatedSrc, pTemplData->vecPyramid[iLayer], matResult, CV_TM_CCOEFF_NORMED); 844 | minMaxLoc(matResult, 0, &dMaxValue, 0, &ptMaxLoc); 845 | vecNewMatchParameter[j] = s_MatchParameter(ptMaxLoc, dMaxValue, vecAngles[j]); 846 | 847 | if (vecNewMatchParameter[j].dMatchScore > dBigValue) 848 | { 849 | iMaxScoreIndex = j; 850 | dBigValue = vecNewMatchParameter[j].dMatchScore; 851 | } 852 | //次像素估計 853 | if (ptMaxLoc.x == 0 || ptMaxLoc.y == 0 || ptMaxLoc.x == matResult.cols - 1 || ptMaxLoc.y == matResult.rows - 1) 854 | vecNewMatchParameter[j].bPosOnBorder = true; 855 | if (!vecNewMatchParameter[j].bPosOnBorder) 856 | { 857 | for (int y = -1; y <= 1; y++) 858 | for (int x = -1; x <= 1; x++) 859 | vecNewMatchParameter[j].vecResult[x + 1][y + 1] = matResult.at(ptMaxLoc + Point(x, y)); 860 | } 861 | //次像素估計 862 | } 863 | if (vecNewMatchParameter[iMaxScoreIndex].dMatchScore < vecLayerScore[iLayer]) 864 | break; 865 | //次像素估計 866 | if (bSubPixelEstimation 867 | && iLayer == 0 868 | && (!vecNewMatchParameter[iMaxScoreIndex].bPosOnBorder) 869 | && iMaxScoreIndex != 0 870 | && iMaxScoreIndex != 2) 871 | { 872 | double dNewX = 0, dNewY = 0, dNewAngle = 0; 873 | SubPixEsimation(&vecNewMatchParameter, &dNewX, &dNewY, &dNewAngle, dAngleStep, iMaxScoreIndex); 874 | vecNewMatchParameter[iMaxScoreIndex].pt = Point2d(dNewX, dNewY); 875 | vecNewMatchParameter[iMaxScoreIndex].dMatchAngle = dNewAngle; 876 | } 877 | //次像素估計 878 | 879 | double dNewMatchAngle = vecNewMatchParameter[iMaxScoreIndex].dMatchAngle; 880 | 881 | //讓坐標系回到旋轉時(GetRotatedROI)的(0, 0) 882 | Point2f ptPaddingLT = ptRotatePt2f(ptLT * 2, ptSrcCenter, dNewMatchAngle * D2R) - Point2f(3, 3); 883 | Point2f pt(vecNewMatchParameter[iMaxScoreIndex].pt.x + ptPaddingLT.x, vecNewMatchParameter[iMaxScoreIndex].pt.y + ptPaddingLT.y); 884 | //再旋轉 885 | pt = ptRotatePt2f(pt, ptSrcCenter, -dNewMatchAngle * D2R); 886 | 887 | if (iLayer == iStopLayer) 888 | { 889 | vecNewMatchParameter[iMaxScoreIndex].pt = pt * (iStopLayer == 0 ? 1 : 2); 890 | vecAllResult.push_back(vecNewMatchParameter[iMaxScoreIndex]); 891 | } 892 | else 893 | { 894 | //更新MatchAngle ptLT 895 | vecMatchParameter[i].dMatchAngle = dNewMatchAngle; 896 | vecMatchParameter[i].dAngleStart = vecMatchParameter[i].dMatchAngle - dAngleStep / 2; 897 | vecMatchParameter[i].dAngleEnd = vecMatchParameter[i].dMatchAngle + dAngleStep / 2; 898 | ptLT = pt; 899 | } 900 | } 901 | 902 | } 903 | } 904 | FilterWithScore(&vecAllResult, matchParam_.scoreThreshold); 905 | 906 | //最後濾掉重疊 907 | iDstW = pTemplData->vecPyramid[iStopLayer].cols * (iStopLayer == 0 ? 1 : 2); 908 | iDstH = pTemplData->vecPyramid[iStopLayer].rows * (iStopLayer == 0 ? 1 : 2); 909 | 910 | for (int i = 0; i < (int)vecAllResult.size(); i++) 911 | { 912 | Point2f ptLT, ptRT, ptRB, ptLB; 913 | double dRAngle = -vecAllResult[i].dMatchAngle * D2R; 914 | ptLT = vecAllResult[i].pt; 915 | ptRT = Point2f(ptLT.x + iDstW * (float)cos(dRAngle), ptLT.y - iDstW * (float)sin(dRAngle)); 916 | ptLB = Point2f(ptLT.x + iDstH * (float)sin(dRAngle), ptLT.y + iDstH * (float)cos(dRAngle)); 917 | ptRB = Point2f(ptRT.x + iDstH * (float)sin(dRAngle), ptRT.y + iDstH * (float)cos(dRAngle)); 918 | //紀錄旋轉矩形 919 | vecAllResult[i].rectR = RotatedRect2(ptLT, ptRT, ptRB); 920 | } 921 | FilterWithRotatedRect(&vecAllResult, CV_TM_CCOEFF_NORMED, matchParam_.iouThreshold); 922 | //最後濾掉重疊 923 | 924 | //根據分數排序 925 | std::sort(vecAllResult.begin(), vecAllResult.end(), compareScoreBig2Small); 926 | 927 | iMatchSize = (int)vecAllResult.size(); 928 | if (vecAllResult.size() == 0) 929 | return false; 930 | int iW = pTemplData->vecPyramid[0].cols, iH = pTemplData->vecPyramid[0].rows; 931 | 932 | for (int i = 0; i < iMatchSize; i++) 933 | { 934 | MatchResult sstm; 935 | double dRAngle = -vecAllResult[i].dMatchAngle * D2R; 936 | 937 | sstm.LeftTop = vecAllResult[i].pt; 938 | 939 | sstm.RightTop = Point2d(sstm.LeftTop.x + iW * cos(dRAngle), sstm.LeftTop.y - iW * sin(dRAngle)); 940 | sstm.LeftBottom = Point2d(sstm.LeftTop.x + iH * sin(dRAngle), sstm.LeftTop.y + iH * cos(dRAngle)); 941 | sstm.RightBottom = Point2d(sstm.RightTop.x + iH * sin(dRAngle), sstm.RightTop.y + iH * cos(dRAngle)); 942 | sstm.Center = Point2d((sstm.LeftTop.x + sstm.RightTop.x + sstm.RightBottom.x + sstm.LeftBottom.x) / 4, (sstm.LeftTop.y + sstm.RightTop.y + sstm.RightBottom.y + sstm.LeftBottom.y) / 4); 943 | sstm.Angle = -vecAllResult[i].dMatchAngle; 944 | sstm.Score = vecAllResult[i].dMatchScore; 945 | 946 | if (sstm.Angle < -180) 947 | sstm.Angle += 360; 948 | if (sstm.Angle > 180) 949 | sstm.Angle -= 360; 950 | matchResults.push_back(sstm); 951 | 952 | //std::cout << "x: " << sstm.Center.x << ", y: " << sstm.Center.y << endl; 953 | 954 | 955 | 956 | //Test Subpixel 957 | /*Point2d ptLT = vecAllResult[i].ptSubPixel; 958 | Point2d ptRT = Point2d (sstm.ptLT.x + iW * cos (dRAngle), sstm.ptLT.y - iW * sin (dRAngle)); 959 | Point2d ptLB = Point2d (sstm.ptLT.x + iH * sin (dRAngle), sstm.ptLT.y + iH * cos (dRAngle)); 960 | Point2d ptRB = Point2d (sstm.ptRT.x + iH * sin (dRAngle), sstm.ptRT.y + iH * cos (dRAngle)); 961 | Point2d ptCenter = Point2d ((sstm.ptLT.x + sstm.ptRT.x + sstm.ptRB.x + sstm.ptLB.x) / 4, (sstm.ptLT.y + sstm.ptRT.y + sstm.ptRB.y + sstm.ptLB.y) / 4); 962 | CString strDiff;strDiff.Format (L"Diff(x, y):%.3f, %.3f", ptCenter.x - sstm.ptCenter.x, ptCenter.y - sstm.ptCenter.y); 963 | AfxMessageBox (strDiff);*/ 964 | //Test Subpixel 965 | if (i + 1 == matchParam_.maxCount) 966 | break; 967 | } 968 | 969 | return (int)matchResults.size(); 970 | } 971 | 972 | int PatternMatcher::setTemplate(const cv::Mat& templateImage) 973 | { 974 | if (templateImage.empty()) 975 | return -1; 976 | if (templateImage.channels() > 1) 977 | return -2; 978 | m_TemplData.bIsPatternLearned = false; 979 | 980 | templateImage_ = templateImage.clone(); 981 | LearnPattern(m_TemplData, templateImage_, matchParam_.minArea); 982 | 983 | return 0; 984 | 985 | } 986 | 987 | } 988 | --------------------------------------------------------------------------------