70 Commits
v1.0 ... main

Author SHA1 Message Date
64298e45eb static check 2025-05-08 17:56:22 +08:00
93bef37422 fix: mismatch faction sign 2025-03-27 13:43:37 +08:00
58cdd2facf add cmd 2025-03-25 17:56:02 +08:00
a756fc1bf2 unroll 2025-03-10 09:10:32 +08:00
0f2391b255 omp optim 2025-03-02 12:47:11 +08:00
8448b07e17 update readme 2025-02-27 21:04:58 +08:00
b2652072ef misc 2025-02-26 17:35:52 +08:00
972dcf7cb3 small fix 2025-02-22 17:21:42 +08:00
d0b8a6f3a2 refactor intrinsics api 2025-02-10 21:33:38 +08:00
80e63e7637 set valid opencv version 2025-01-21 21:02:02 +08:00
8a975e19c9 add todo 2025-01-21 20:48:01 +08:00
45971ed070 update readme 2024-12-27 22:05:26 +08:00
094ada9337 misc 2024-12-23 09:05:27 +08:00
d07f070668 add pos subpixel 2024-12-22 21:01:38 +08:00
a3b1b64e9a fix: image size 2024-12-01 17:57:15 +08:00
d1e4abfc71 misc 2024-11-03 21:12:39 +08:00
cff37c2f08 fix: invalid size write 2024-11-03 20:51:33 +08:00
440c938b22 last miss fix 2024-10-08 21:08:15 +08:00
b6e2d9aba5 unified simd size 2024-10-08 20:39:30 +08:00
4a2f97e354 fix arm64 2024-09-27 21:11:19 +08:00
4b8ec4359c optimal cache miss 2024-09-22 17:20:24 +08:00
d451b46535 misc 2024-09-21 09:16:15 +08:00
09017f0bc0 let integral as standalone 2024-09-20 22:50:25 +08:00
eea1b6320b spilt vector and scalar code 2024-09-20 22:42:46 +08:00
b4cdaff8d1 misc 2024-09-20 17:55:29 +08:00
6109ff388a update README.md 2024-09-19 22:47:25 +08:00
98acd6efbd misc 2024-09-19 15:50:57 +08:00
92b09a51c7 misc 2024-09-19 11:28:20 +08:00
7f5f665af2 misc 2024-09-19 11:20:33 +08:00
44cf2a0526 fix integral 2024-09-19 10:32:26 +08:00
df8267b60d simd integral image 2024-09-18 22:31:51 +08:00
01a20a18f1 static check 2024-09-18 19:53:27 +08:00
087ab9bc17 misc 2024-09-18 18:10:35 +08:00
d7bf2ad384 no copy 2024-09-18 15:45:03 +08:00
2c20dc2006 add openmp support 2024-09-18 11:21:33 +08:00
57ae2c8704 update doc 2024-09-13 20:54:55 +08:00
8e7227f74b demo: match start top level 2024-09-13 20:39:50 +08:00
c72b709178 add LICENSE 2024-09-13 20:28:04 +08:00
b5523f8491 replace call cv::Mat::ptr 2024-09-13 17:55:58 +08:00
f43f78f45f add readme 2024-09-12 21:07:31 +08:00
15372e0b60 add sample image 2024-09-12 21:06:50 +08:00
3d315445ab remove unused 2024-09-12 19:58:50 +08:00
d03818a63c special optim for arm 2024-09-12 08:36:55 +08:00
ed14c0e6f4 product hand write 2024-09-11 22:15:47 +08:00
fa20b52b85 serialize to file 2024-09-11 17:53:31 +08:00
7a88dad291 misc 2024-09-11 17:52:38 +08:00
dd5de6f6f1 misc 2024-09-11 14:30:31 +08:00
8050b8612a 🎈 perf(match): less reduce sum 2024-09-11 10:21:56 +08:00
3a689e900f revert 2024-09-06 21:19:58 +08:00
b8918b123d static check 2024-09-06 14:35:08 +08:00
1c67371cc9 fix type 2024-09-05 13:57:41 +08:00
422c12d691 impl shift 2024-09-05 11:21:55 +08:00
bece4f138f misc 2024-09-04 18:14:09 +08:00
75098c811c remove unused 2024-09-04 14:57:09 +08:00
a52deadc30 misc 2024-09-04 09:15:30 +08:00
ef1556c538 build config 2024-09-04 09:12:58 +08:00
92160ee1d9 misc 2024-09-04 09:06:42 +08:00
29fb7b001c static check 2024-09-03 22:53:25 +08:00
d85d4f7443 misc 2024-09-03 18:05:13 +08:00
13cbf06f46 aligned model at training 2024-09-03 17:03:42 +08:00
a5c8049af1 aligned model at deserialization 2024-09-03 15:27:48 +08:00
371b926fb0 cleanr name 2024-09-02 17:57:10 +08:00
3409395778 misc 2024-09-01 17:54:02 +08:00
36392b3e34 format check 2024-08-31 18:21:00 +08:00
726a55725a fix doc 2024-08-30 22:00:56 +08:00
338c1dcf96 static check 2024-08-30 21:53:42 +08:00
6dd78fbe21 support rect roi 2024-08-30 20:57:12 +08:00
665559d8ca export api 2024-08-29 15:20:48 +08:00
8758d7801d misc 2024-08-27 10:36:22 +08:00
fc4f04b9be add loongarch simd(lsx) 2024-08-25 20:59:55 +08:00
19 changed files with 1126 additions and 181 deletions

View File

@ -1,17 +1,60 @@
cmake_minimum_required(VERSION 3.12)
project(match)
find_package(OpenCV REQUIRED)
add_executable(${PROJECT_NAME}
main.cpp
find_package(OpenCV 4.8 REQUIRED)
option(ENABLE_OPENMP "enable openmp" OFF)
if(ENABLE_OPENMP)
# find OpenMP
find_package(OpenMP REQUIRED)
if(OPENMP_FOUND)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
endif(OPENMP_FOUND)
endif(ENABLE_OPENMP)
#==============================================================
#library
#==============================================================
add_library(algo SHARED
grayMatch.h
grayMatch.cpp
serialize.cpp
privateType.h
apiExport.h
integral.h
integral.cpp
)
target_include_directories(algo PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(algo ${OpenCV_LIBRARIES} $<$<BOOL:${OPENMP_FOUND}>:OpenMP::OpenMP_CXX>)
target_compile_options(algo PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /external:W0>
$<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>: -fPIC -fvisibility=hidden -Wl,--exclude-libs,ALL -Wall -Wextra -Wpedantic -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -Werror>
$<$<AND:$<CXX_COMPILER_ID:Clang>,$<STREQUAL:${CMAKE_SYSTEM_NAME},Windows>>:/W4 /WX /external:W0>
#$<$<AND:$<CXX_COMPILER_ID:Clang>,$<STREQUAL:${CMAKE_SYSTEM_NAME},Darwin>>: -fPIC -fvisibility=hidden -Wall -Wextra -Wpedantic -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -Werror>
$<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},loongarch64>:-mlsx>
)
target_compile_definitions(algo PUBLIC API_EXPORTS
$<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},loongarch64>:CV_LSX>
)
message("Arch:${CMAKE_SYSTEM_PROCESSOR}")
#==============================================================
#exe
#==============================================================
add_executable(${PROJECT_NAME}
main.cpp
)
target_include_directories(${PROJECT_NAME} PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBRARIES})
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBRARIES} algo)
target_compile_options(${PROJECT_NAME} PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /external:W0>
$<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>:-fPIC -fvisibility=hidden -Wall -Wextra -Wpedantic -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -Werror>
$<$<STREQUAL:${CMAKE_SYSTEM_NAME},Linux>: -fPIC -fvisibility=hidden -Wall -Wextra -Wpedantic -Wmisleading-indentation -Wunused -Wuninitialized -Wshadow -Wconversion -Werror>
$<$<AND:$<CXX_COMPILER_ID:Clang>,$<STREQUAL:${CMAKE_SYSTEM_NAME},Windows>>:/W4 /WX /external:W0>
)
target_compile_definitions(${PROJECT_NAME} PRIVATE IMG_DIR="${CMAKE_CURRENT_SOURCE_DIR}/img")

24
LICENSE Normal file
View File

@ -0,0 +1,24 @@
BSD 2-Clause License
Copyright (c) 2024, SurfaceMan
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# Template match with gray model(ncc)
## Note: branch [feature-rotate-model](https://github.com/SurfaceMan/gray_match/tree/feature-rotate-model) method 2x faster at matching!
## highlights:
1. original code based [Fastest_Image_Pattern_Matching](https://github.com/DennisLiu1993/Fastest_Image_Pattern_Matching), you can check out tag [v1.0](https://github.com/SurfaceMan/gray_match/releases/tag/v1.0) for more details.
2. refactor simd match process with opencv [Universal intrinsics](https://docs.opencv.org/4.x/df/d91/group__core__hal__intrin.html), have be tested on x86_64(sse),arm(neon),LoongArch(lsx).
3. support model save/load as binary file
4. provide pure c interface
5. support openmp
6. position with 3x3 subpixel interpolation
## usage:
all you need can be found in [main.cpp](main.cpp)
## gallery:
![sample](img/result.png)

1
TODO.md Normal file
View File

@ -0,0 +1 @@
1. opencv intrinsics api [changed](https://github.com/opencv/opencv/pull/24371) since 4.9, need refactor

31
apiExport.h Normal file
View File

@ -0,0 +1,31 @@
#pragma once
#if defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
#define API_EXPORT __declspec(dllexport)
#define API_IMPORT __declspec(dllimport)
#define API_LOCAL
#elif defined(linux) || defined(__linux) || defined(__linux__)
#define API_EXPORT __attribute__((visibility("default")))
#define API_IMPORT __attribute__((visibility("default")))
#define API_LOCAL __attribute__((visibility("hidden")))
#elif defined(__APPLE__)
#define API_EXPORT __attribute__((visibility("default")))
#define API_IMPORT __attribute__((visibility("default")))
#define API_LOCAL __attribute__((visibility("hidden")))
#else
#define API_EXPORT
#define API_IMPORT
#define API_LOCAL
#endif
#ifdef __cplusplus
#define API_DEMANGLED extern "C"
#else
#define API_DEMANGLED
#endif
#ifdef API_EXPORTS
#define API_PUBLIC API_DEMANGLED API_EXPORT
#else
#define API_PUBLIC API_DEMANGLED API_IMPORT
#endif

View File

@ -1,6 +1,8 @@
#include "grayMatch.h"
#include "privateType.h"
#include <opencv2/core/hal/intrin.hpp>
#include <opencv2/opencv.hpp>
#include <utility>
constexpr int MIN_AREA = 256;
@ -8,37 +10,6 @@ constexpr double TOLERANCE = 0.0000001;
constexpr int CANDIDATE = 5;
constexpr double INVALID = -1.;
struct Model {
std::vector<cv::Mat> pyramids;
std::vector<cv::Scalar> mean;
std::vector<double> normal;
std::vector<double> invArea;
std::vector<bool> equal1;
uchar borderColor = 0;
void clear() {
pyramids.clear();
normal.clear();
invArea.clear();
mean.clear();
equal1.clear();
}
void resize(const std::size_t size) {
normal.resize(size);
invArea.resize(size);
mean.resize(size);
equal1.resize(size);
}
void reserve(const std::size_t size) {
normal.reserve(size);
invArea.reserve(size);
mean.reserve(size);
equal1.reserve(size);
}
};
struct BlockMax {
struct Block {
cv::Rect rect;
@ -170,6 +141,15 @@ int computeLayers(const int width, const int height, const int minArea) {
return layer;
}
inline cv::Point2d transform(const cv::Point2d &point, const cv::Mat &rotate) {
const auto ptr = rotate.ptr<double>();
auto x = point.x * ptr[ 0 ] + point.y * ptr[ 1 ] + ptr[ 2 ];
auto y = point.x * ptr[ 3 ] + point.y * ptr[ 4 ] + ptr[ 5 ];
return {x, y};
}
cv::Size computeRotationSize(const cv::Size &dstSize, const cv::Size &templateSize, double angle,
const cv::Mat &rotate) {
if (angle > 360) {
@ -186,34 +166,20 @@ cv::Size computeRotationSize(const cv::Size &dstSize, const cv::Size &templateSi
return dstSize;
}
const std::vector<cv::Point2d> points{
{0, 0},
{static_cast<double>(dstSize.width) - 1, 0},
{0, static_cast<double>(dstSize.height) - 1},
{static_cast<double>(dstSize.width) - 1, static_cast<double>(dstSize.height) - 1}};
std::vector<cv::Point2d> trans;
cv::transform(points, trans, rotate);
const std::vector<cv::Point2d> pt{
transform({0, 0}, rotate), transform({static_cast<double>(dstSize.width) - 1, 0}, rotate),
transform({0, static_cast<double>(dstSize.height) - 1}, rotate),
transform({static_cast<double>(dstSize.width) - 1, static_cast<double>(dstSize.height) - 1},
rotate)};
cv::Point2d min = trans[ 0 ];
cv::Point2d max = trans[ 0 ];
for (const auto &point : trans) {
if (point.x < min.x) {
min.x = point.x;
}
if (point.y < min.y) {
min.y = point.y;
}
if (point.x > max.x) {
max.x = point.x;
}
if (point.y > max.y) {
max.y = point.y;
}
}
cv::Point2d min;
cv::Point2d max;
min.x = std::min(std::min(std::min(pt[ 0 ].x, pt[ 1 ].x), pt[ 2 ].x), pt[ 3 ].x);
min.y = std::min(std::min(std::min(pt[ 0 ].y, pt[ 1 ].y), pt[ 2 ].y), pt[ 3 ].y);
max.x = std::max(std::max(std::max(pt[ 0 ].x, pt[ 1 ].x), pt[ 2 ].x), pt[ 3 ].x);
max.y = std::max(std::max(std::max(pt[ 0 ].y, pt[ 1 ].y), pt[ 2 ].y), pt[ 3 ].y);
if (angle > 0 && angle < 90) {
;
} else if (angle > 90 && angle < 180) {
angle -= 90;
} else if (angle > 180 && angle < 270) {
@ -237,15 +203,74 @@ cv::Size computeRotationSize(const cv::Size &dstSize, const cv::Size &templateSi
(templateSize.width > size.width && templateSize.height < size.height) ||
templateSize.area() > size.area();
if (wrongSize) {
size = {static_cast<int>(max.x - min.x + 0.5), static_cast<int>(max.y - min.y + 0.5)};
size = {static_cast<int>(lround(max.x - min.x + 0.5)),
static_cast<int>(lround(max.y - min.y + 0.5))};
}
return size;
}
void coeffDenominator(const cv::Mat &src, const cv::Size &templateSize, cv::Mat &result,
const double mean, const double normal, const double invArea,
const bool equal1) {
#ifdef CV_SIMD
#include "integral.h"
void matchTemplateSimd(const cv::Mat &src, const cv::Mat &templateImg, cv::Mat &result) {
result.create(src.size() - templateImg.size() + cv::Size(1, 1), CV_32FC1);
auto *resultStart = result.ptr<float>();
const auto resultStep = result.step[ 0 ] / result.step[ 1 ];
const auto *srcStart = src.ptr<uchar>();
const auto srcStep = src.step[ 0 ];
const auto *temStart = templateImg.ptr<uchar>();
const auto temStep = templateImg.step[ 0 ];
std::vector<cv::v_uint32> tmp(result.cols, cv::v_setzero_u32());
for (int y = 0; y < result.rows; y++) {
auto *resultPtr = resultStart + resultStep * y;
for (int templateRow = 0; templateRow < templateImg.rows; templateRow++) {
auto *temPtr = temStart + temStep * templateRow;
for (int i = 0; i < templateImg.cols; i += simdSize(cv::v_uint8)) {
auto vTem = cv::v_load_aligned(temPtr + i);
auto *srcPtr = srcStart + srcStep * (y + templateRow) + i;
int x = 0;
for (int n = 0; n < result.cols / 4; n++) {
auto vSrc = cv::v_load(srcPtr + x);
tmp[ x ] = cv::v_add(tmp[ x ], cv::v_dotprod_expand_fast(vSrc, vTem));
x++;
vSrc = cv::v_load(srcPtr + x);
tmp[ x ] = cv::v_add(tmp[ x ], cv::v_dotprod_expand_fast(vSrc, vTem));
x++;
vSrc = cv::v_load(srcPtr + x);
tmp[ x ] = cv::v_add(tmp[ x ], cv::v_dotprod_expand_fast(vSrc, vTem));
x++;
vSrc = cv::v_load(srcPtr + x);
tmp[ x ] = cv::v_add(tmp[ x ], cv::v_dotprod_expand_fast(vSrc, vTem));
x++;
}
for (; x < result.cols; x++) {
auto vSrc = cv::v_load(srcPtr + x);
tmp[ x ] = cv::v_add(tmp[ x ], cv::v_dotprod_expand_fast(vSrc, vTem));
}
}
}
for (int x = 0; x < result.cols; x++) {
const auto sum = cv::v_reduce_sum(tmp[ x ]);
tmp[ x ] = cv::v_setzero_u32();
resultPtr[ x ] = static_cast<float>(sum);
}
}
}
#endif
void ccoeffDenominator(const cv::Mat &src, const cv::Size &templateSize, cv::Mat &result,
const double mean, const double normal, const double invArea,
const bool equal1) {
if (equal1) {
result = cv::Scalar::all(1);
return;
@ -253,43 +278,44 @@ void coeffDenominator(const cv::Mat &src, const cv::Size &templateSize, cv::Mat
cv::Mat sum;
cv::Mat sqSum;
#ifdef CV_SIMD
integralSimd(src, sum, sqSum);
#else
cv::integral(src, sum, sqSum, CV_64F);
#endif
const auto *q0 = sqSum.ptr<double>(0);
const auto *q0 = reinterpret_cast<double *>(sqSum.data);
const auto *q1 = q0 + templateSize.width;
const auto *q2 = sqSum.ptr<double>(templateSize.height);
const auto *q2 = reinterpret_cast<double *>(sqSum.data) + sqSum.step1() * templateSize.height;
const auto *q3 = q2 + templateSize.width;
const auto *p0 = sum.ptr<double>(0);
const auto *p0 = reinterpret_cast<double *>(sum.data);
const auto *p1 = p0 + templateSize.width;
const auto *p2 = sum.ptr<double>(templateSize.height);
const auto *p2 = reinterpret_cast<double *>(sum.data) + sum.step1() * templateSize.height;
const auto *p3 = p2 + templateSize.width;
const auto step = sum.step / sizeof(double);
const auto step = sum.step / sizeof(double);
auto *resultStartPtr = reinterpret_cast<float *>(result.data);
for (int y = 0; y < result.rows; y++) {
auto *scorePtr = result.ptr<float>(y);
auto *scorePtr = resultStartPtr + y * result.step1();
auto idx = y * step;
for (int x = 0; x < result.cols; x++, idx++) {
auto &score = scorePtr[ x ];
const auto partSum = p0[ idx ] - p1[ idx ] - p2[ idx ] + p3[ idx ];
auto partMean = partSum * partSum;
const auto num = score - partSum * mean;
partMean *= invArea;
const auto numerator = score - partSum * mean;
auto partSum2 = q0[ idx ] - q1[ idx ] - q2[ idx ] + q3[ idx ];
const auto partSqSum = q0[ idx ] - q1[ idx ] - q2[ idx ] + q3[ idx ];
auto partSqNormal = partSqSum - partSum * partSum * invArea;
const auto diff = std::max(partSum2 - partMean, 0.);
if (diff <= std::min(0.5, 10 * FLT_EPSILON * partSum2)) {
partSum2 = 0;
} else {
partSum2 = sqrt(diff) * normal;
}
const auto diff = std::max(partSqNormal, 0.);
const double denominator =
diff <= std::min(0.5, 10 * FLT_EPSILON * partSqSum) ? 0 : sqrt(diff) * normal;
if (abs(num) < partSum2) {
score = static_cast<float>(num / partSum2);
} else if (abs(num) < partSum2 * 1.125) {
score = num > 0.f ? 1.f : -1.f;
if (abs(numerator) < denominator) {
score = static_cast<float>(numerator / denominator);
} else if (abs(numerator) < denominator * 1.125) {
score = numerator > 0.f ? 1.f : -1.f;
} else {
score = 0;
}
@ -297,48 +323,14 @@ void coeffDenominator(const cv::Mat &src, const cv::Size &templateSize, cv::Mat
}
}
#ifdef CV_SIMD
float convSimd(const uchar *kernel, const uchar *src, const int kernelWidth) {
const auto blockSize = cv::VTraits<cv::v_uint8>::vlanes();
auto vSum = cv::vx_setall_u32(0);
int i = 0;
for (; i < kernelWidth - blockSize; i += blockSize) {
vSum += cv::v_dotprod_expand(cv::v_load(kernel + i), cv::v_load(src + i));
}
auto sum = cv::v_reduce_sum(vSum);
for (; i < kernelWidth; i++) {
sum += kernel[ i ] * src[ i ];
}
return static_cast<float>(sum);
}
void matchTemplateSimd(const cv::Mat &src, const cv::Mat &templateImg, cv::Mat &result) {
result = cv::Mat::zeros(src.size() - templateImg.size() + cv::Size(1, 1), CV_32FC1);
for (int y = 0; y < result.rows; y++) {
auto *resultPtr = result.ptr<float>(y);
for (int x = 0; x < result.cols; x++) {
auto &score = resultPtr[ x ];
for (int templateRow = 0; templateRow < templateImg.rows; templateRow++) {
auto *srcPtr = src.ptr<uchar>(y + templateRow) + x;
auto *temPtr = templateImg.ptr<uchar>(templateRow);
score += convSimd(temPtr, srcPtr, templateImg.cols);
}
}
}
}
#endif
void matchTemplate(cv::Mat &src, cv::Mat &result, const Model *model, int level) {
#ifdef CV_SIMD
matchTemplateSimd(src, model->pyramids[ level ], result);
#else
cv::matchTemplate(src, model->pyramids[ level ], result, cv::TM_CCORR);
#endif
coeffDenominator(src, model->pyramids[ level ].size(), result, model->mean[ level ][ 0 ],
model->normal[ level ], model->invArea[ level ], model->equal1[ level ]);
ccoeffDenominator(src, model->pyramids[ level ].size(), result, model->mean[ level ][ 0 ],
model->normal[ level ], model->invArea[ level ], model->equal1[ level ] == 1);
}
void nextMaxLoc(const cv::Point &pos, const cv::Size templateSize, const double maxOverlap,
@ -372,17 +364,26 @@ void nextMaxLoc(cv::Mat &score, const cv::Point &pos, const cv::Size templateSiz
cv::minMaxLoc(score, nullptr, &maxScore, nullptr, &maxPos);
}
inline cv::Point2d transform(const cv::Point2d &point, const cv::Mat &rotate) {
inline cv::Mat getRotationMatrix2D(const cv::Point2f &center, double angle) {
angle *= CV_PI / 180;
const double alpha = std::cos(angle);
const double beta = std::sin(angle);
cv::Mat rotate(2, 3, CV_64FC1);
const auto ptr = rotate.ptr<double>();
ptr[ 0 ] = alpha;
ptr[ 1 ] = beta;
ptr[ 2 ] = (1 - alpha) * center.x - beta * center.y;
ptr[ 3 ] = -beta;
ptr[ 4 ] = alpha;
ptr[ 5 ] = beta * center.x + (1 - alpha) * center.y;
auto x = point.x * ptr[ 0 ] + point.y * ptr[ 1 ] + ptr[ 2 ];
auto y = point.x * ptr[ 3 ] + point.y * ptr[ 4 ] + ptr[ 5 ];
return {x, y};
return rotate;
}
inline cv::Point2d transform(const cv::Point2d &point, const cv::Point &center, double angle) {
const auto rotate = cv::getRotationMatrix2D(center, angle, 1.);
inline cv::Point2d transform(const cv::Point2d &point, const cv::Point &center,
const double angle) {
const auto rotate = getRotationMatrix2D(center, angle);
return transform(point, rotate);
}
@ -392,14 +393,13 @@ inline cv::Point2d sizeCenter(const cv::Size &size) {
}
void cropRotatedRoi(const cv::Mat &src, const cv::Size &templateSize, const cv::Point2d topLeft,
const cv::Mat &rotate, cv::Mat &roi) {
cv::Mat &rotate, cv::Mat &roi) {
const auto point = transform(topLeft, rotate);
const cv::Size paddingSize(templateSize.width + 6, templateSize.height + 6);
auto rt = rotate;
rt.at<double>(0, 2) -= point.x - 3;
rt.at<double>(1, 2) -= point.y - 3;
rotate.at<double>(0, 2) -= point.x - 3;
rotate.at<double>(1, 2) -= point.y - 3;
cv::warpAffine(src, roi, rt, paddingSize);
cv::warpAffine(src, roi, rotate, paddingSize);
}
void filterOverlap(std::vector<Candidate> &candidates, const std::vector<cv::RotatedRect> &rects,
@ -436,8 +436,9 @@ void filterOverlap(std::vector<Candidate> &candidates, const std::vector<cv::Rot
continue;
}
const auto area = cv::contourArea(points);
const auto overlap = area / rect.size.area();
const auto area = cv::contourArea(points);
const auto overlap =
area / static_cast<double>(rect.size.width * rect.size.height);
if (overlap > maxOverlap) {
(candidate.score > refCandidate.score ? refCandidate.score
: candidate.score) = INVALID;
@ -454,8 +455,8 @@ Model *trainModel(const cv::Mat &src, int level) {
return nullptr;
}
if (level < 0) {
// level must greater than 1
if (level <= 0) {
// level must greater than 0
level = computeLayers(src.size().width, src.size().height, MIN_AREA);
}
@ -468,11 +469,24 @@ Model *trainModel(const cv::Mat &src, int level) {
auto *result = new Model;
Model &model = *result;
cv::buildPyramid(src, model.pyramids, level);
model.borderColor = cv::mean(src).val[ 0 ] < 128 ? 255 : 0;
model.reserve(model.pyramids.size());
for (const auto &pyramid : model.pyramids) {
std::vector<cv::Mat> pyramids;
cv::buildPyramid(src, pyramids, level);
model.borderColor = cv::mean(src).val[ 0 ] < 128 ? 255 : 0;
model.reserve(pyramids.size());
for (const auto &pyramid : pyramids) {
int alignedWidth = static_cast<int>(cv::alignSize(pyramid.cols, simdSize(cv::v_uint8)));
auto img = cv::Mat::zeros(pyramid.rows, alignedWidth, CV_8UC1);
cv::Mat sub = img(cv::Rect(0, 0, pyramid.cols, pyramid.rows));
for (int y = 0; y < pyramid.rows; y++) {
auto *dstPtr = sub.ptr<uchar>(y);
auto *srcPtr = pyramid.ptr<uchar>(y);
memcpy(dstPtr, srcPtr, pyramid.cols);
}
model.pyramids.push_back(sub);
auto invArea = 1. / pyramid.size().area();
cv::Scalar mean;
@ -497,6 +511,9 @@ inline double sizeAngleStep(const cv::Size &size) {
return atan(2. / std::max(size.width, size.height)) * 180. / CV_PI;
}
#pragma omp declare reduction(combine : std::vector<Candidate> : omp_out.insert( \
omp_out.end(), omp_in.begin(), omp_in.end()))
std::vector<Candidate> matchTopLevel(const cv::Mat &dstTop, double startAngle, double spanAngle,
double maxOverlap, double minScore, int maxCount,
const Model *model, int level) {
@ -506,12 +523,13 @@ std::vector<Candidate> matchTopLevel(const cv::Mat &dstTop, double startAngle, d
auto angleStep = sizeAngleStep(templateTop.size());
cv::Point2d center = sizeCenter(dstTop.size());
const auto topScoreThreshold = minScore * pow(0.9, level);
bool calMaxByBlock = (dstTop.size().area() / templateTop.size().area() > 500) && maxCount > 10;
bool calMaxByBlock = dstTop.size().area() / templateTop.size().area() > 500 && maxCount > 10;
const auto count = static_cast<int>(spanAngle / angleStep) + 1;
#pragma omp parallel for reduction(combine : candidates)
for (int i = 0; i < count; i++) {
const auto angle = startAngle + angleStep * i;
auto rotate = cv::getRotationMatrix2D(center, angle, 1.);
auto rotate = getRotationMatrix2D(center, angle);
auto size = computeRotationSize(dstTop.size(), templateTop.size(), angle, rotate);
auto tx = (size.width - 1) / 2. - center.x;
@ -569,13 +587,57 @@ std::vector<Candidate> matchTopLevel(const cv::Mat &dstTop, double startAngle, d
return candidates;
}
cv::Point2f computeSubpixel(const cv::Mat &score) {
cv::Point2f result(0, 0);
auto *mag = score.ptr<float>();
const auto gx = (-mag[ 0 ] + mag[ 2 ] - mag[ 3 ] + mag[ 5 ] - mag[ 6 ] + mag[ 8 ]) / 3.0f;
const auto gy = (mag[ 6 ] + mag[ 7 ] + mag[ 8 ] - mag[ 0 ] - mag[ 1 ] - mag[ 2 ]) / 3.0f;
const auto gxx = (mag[ 0 ] - 2.0f * mag[ 1 ] + mag[ 2 ] + mag[ 3 ] - 2.0f * mag[ 4 ] +
mag[ 5 ] + mag[ 6 ] - 2.0f * mag[ 7 ] + mag[ 8 ]) /
6.0f;
const auto gxy = (-mag[ 0 ] + mag[ 2 ] + mag[ 6 ] - mag[ 8 ]) / 2.0f;
const auto gyy = (mag[ 0 ] + mag[ 1 ] + mag[ 2 ] - 2.0f * (mag[ 3 ] + mag[ 4 ] + mag[ 5 ]) +
mag[ 6 ] + mag[ 7 ] + mag[ 8 ]) /
6.0f;
cv::Mat hessian(2, 2, CV_32FC1);
hessian.at<float>(0, 0) = gxx;
hessian.at<float>(0, 1) = gxy;
hessian.at<float>(1, 0) = gxy;
hessian.at<float>(1, 1) = gyy;
cv::Mat eigenvalue;
cv::Mat eigenvector;
cv::eigen(hessian, eigenvalue, eigenvector);
float nx, ny;
if (fabs(eigenvalue.at<float>(0, 0)) >= fabs(eigenvalue.at<float>(1, 0))) {
nx = eigenvector.at<float>(0, 0);
ny = eigenvector.at<float>(0, 1);
} else {
nx = eigenvector.at<float>(1, 0);
ny = eigenvector.at<float>(1, 1);
}
const auto denominator = gxx * nx * nx + 2 * gxy * nx * ny + gyy * ny * ny;
if (denominator != 0.0) {
const auto t = -(gx * nx + gy * ny) / denominator;
result.x = t * nx;
result.y = t * ny;
}
return result;
}
std::vector<Candidate> matchDownLevel(const std::vector<cv::Mat> &pyramids,
const std::vector<Candidate> &candidates, double minScore,
int subpixel, const Model *model, int level) {
std::vector<Candidate> levelMatched;
auto count = static_cast<int>(candidates.size());
for (const auto &candidate : candidates) {
auto pose = candidate;
#pragma omp parallel for reduction(combine : levelMatched) schedule(dynamic)
for (int index = 0; index < count; index++) {
auto pose = candidates[ index ];
bool matched = true;
for (int currentLevel = level - 1; currentLevel >= 0; currentLevel--) {
const auto &currentTemplateImg = model->pyramids[ currentLevel ];
@ -597,7 +659,7 @@ std::vector<Candidate> matchDownLevel(const std::vector<cv::Mat> &pyramids,
cv::Mat newScoreRect;
for (int i = -1; i <= 1; i++) {
auto angle = pose.angle + i * currentAngleStep;
auto rotate = cv::getRotationMatrix2D(center, angle, 1.);
auto rotate = getRotationMatrix2D(center, angle);
cv::Mat roi;
cropRotatedRoi(currentDstImg, tmpSize, topLeft, rotate, roi);
@ -629,7 +691,9 @@ std::vector<Candidate> matchDownLevel(const std::vector<cv::Mat> &pyramids,
}
if (!newScoreRect.empty()) {
// TODO subpixel
auto offset = computeSubpixel(newScoreRect);
newCandidate.pos.x += offset.x;
newCandidate.pos.y += offset.y;
}
// back to
@ -700,7 +764,7 @@ std::vector<Pose> matchModel(const cv::Mat &dst, const Model *model, int level,
for (const auto &candidate : levelMatched) {
std::vector<cv::Point2f> points{topRight + cv::Point2f(candidate.pos),
bottomRight + cv::Point2f(candidate.pos)};
auto rotate = cv::getRotationMatrix2D(candidate.pos, -candidate.angle, 1.);
auto rotate = getRotationMatrix2D(candidate.pos, -candidate.angle);
std::vector<cv::Point2f> rotatedPoints;
cv::transform(points, rotatedPoints, rotate);
@ -731,3 +795,128 @@ std::vector<Pose> matchModel(const cv::Mat &dst, const Model *model, int level,
return result;
}
Model_t trainModel(const unsigned char *data, int width, int height, int channels, int bytesPerLine,
int roiLeft, int roiTop, int roiWidth, int roiHeight, int levelNum) {
if ((1 != channels && 3 != channels && 4 != channels) || nullptr == data) {
return nullptr;
}
const auto type = channels == 1 ? CV_8UC1 : channels == 3 ? CV_8UC3 : CV_8UC4;
const cv::Mat img(cv::Size(width, height), type, const_cast<unsigned char *>(data),
bytesPerLine);
cv::Mat src;
if (1 == channels) {
src = img;
} else {
cv::cvtColor(img, src, channels == 3 ? cv::COLOR_RGB2GRAY : cv::COLOR_RGBA2GRAY);
}
const cv::Rect rect(roiLeft, roiTop, roiWidth, roiHeight);
const cv::Rect imageRect(0, 0, width, height);
const auto roi = rect & imageRect;
if (roi.empty()) {
return nullptr;
}
return trainModel(src(roi), levelNum);
}
void matchModel(const unsigned char *data, int width, int height, int channels, int bytesPerLine,
int roiLeft, int roiTop, int roiWidth, int roiHeight, Model *const model,
int *count, Pose *poses, int level, double startAngle, double spanAngle,
double maxOverlap, double minScore, int subpixel) {
if (nullptr == count) {
return;
}
if (nullptr == poses || nullptr == data) {
*count = 0;
return;
}
if (1 != channels && 3 != channels && 4 != channels) {
*count = 0;
return;
}
const auto type = channels == 1 ? CV_8UC1 : channels == 3 ? CV_8UC3 : CV_8UC4;
const cv::Mat img(cv::Size(width, height), type, const_cast<unsigned char *>(data),
bytesPerLine);
cv::Mat dst;
if (1 == channels) {
dst = img;
} else {
cv::cvtColor(img, dst, channels == 3 ? cv::COLOR_RGB2GRAY : cv::COLOR_RGBA2GRAY);
}
const cv::Rect rect(roiLeft, roiTop, roiWidth, roiHeight);
const cv::Rect imageRect(0, 0, width, height);
const auto roi = rect & imageRect;
if (roi.empty()) {
*count = 0;
return;
}
const auto result = matchModel(dst(roi), model, level, startAngle, spanAngle, maxOverlap,
minScore, *count, subpixel);
const auto size = std::min(*count, static_cast<int>(result.size()));
for (int i = 0; i < size; i++) {
const auto &pose = result[ i ];
poses[ i ] = {pose.x + static_cast<float>(roi.x), pose.y + static_cast<float>(roi.y),
pose.angle, pose.score};
}
*count = size;
}
void freeModel(Model_t *model) {
if (nullptr == model || nullptr == *model) {
return;
}
delete *model;
*model = nullptr;
}
int modelLevel(Model *const model) {
if (nullptr == model) {
return 0;
}
return static_cast<int>(model->pyramids.size());
}
void modelImage(Model *const model, int level, unsigned char *data, int length, int *width,
int *height, int *channels) {
if (nullptr == model) {
return;
}
if (level < 0 || level > static_cast<int>(model->pyramids.size() - 1)) {
return;
}
const auto &img = model->pyramids[ level ];
if (nullptr != width) {
*width = img.cols;
}
if (nullptr != height) {
*height = img.rows;
}
if (nullptr != channels) {
*channels = img.channels();
}
if (nullptr == data || length < img.cols * img.rows * img.channels()) {
return;
}
for (int y = 0; y < img.rows; y++) {
const auto *ptr = img.ptr<unsigned char>(y);
memcpy(data + y * img.cols, ptr, img.cols);
}
}

View File

@ -1,10 +1,12 @@
#ifndef GRAY_MATCH_H
#define GRAY_MATCH_H
#include <opencv2/opencv.hpp>
#include "apiExport.h"
struct Model;
using Model_t = Model *;
struct Pose {
float x;
float y;
@ -12,14 +14,93 @@ struct Pose {
float score;
};
Model *trainModel(const cv::Mat &src, int level);
/**
* @brief train match model
* @param data image data
* @param width image width
* @param height image height
* @param channels image channels 1(gray)/3(rgb)/4(rgba)
* @param bytesPerLine bytes per line
* @param roiLeft rectangle roi left
* @param roiTop rectangle roi top
* @param roiWidth rectangle roi width
* @param roiHeight rectangle roi height
* @param levelNum pyramid levels (> 0:user setting,-1:auto)
* @return
*/
API_PUBLIC Model_t trainModel(const unsigned char *data, int width, int height, int channels,
int bytesPerLine, int roiLeft, int roiTop, int roiWidth,
int roiHeight, int levelNum);
/**
* @brief match model
* @param data image data
* @param width image width
* @param height image height
* @param channels image channels 1(gray)/3(rgb)/4(rgba)
* @param bytesPerLine bytes per line
* @param roiLeft rectangle roi left
* @param roiTop rectangle roi top
* @param roiWidth rectangle roi width
* @param roiHeight rectangle roi height
* @param model trained model
* @param count in(max detect count)/out(found count)
* @param poses pose array inited with size not less than count
* @param level match start at which level (level>=0 && level<modelLevel-1,-1:auto)
* @param startAngle rotation start angle
* @param spanAngle rotation angle range
* @param maxOverlap overlap threshold
* @param minScore minimum matched score
* @param subpixel compute subpixel result
* @return
*/
API_PUBLIC void matchModel(const unsigned char *data, int width, int height, int channels,
int bytesPerLine, int roiLeft, int roiTop, int roiWidth, int roiHeight,
Model_t model, int *count, Pose *poses, int level, double startAngle,
double spanAngle, double maxOverlap, double minScore, int subpixel);
std::vector<Pose> matchModel(const cv::Mat &dst, const Model *model, int level, double startAngle,
double spanAngle, double maxOverlap, double minScore, int maxCount,
int subpixel);
/**
* @brief get trained model levels
* @param model
* @return pyramid level
*/
API_PUBLIC int modelLevel(Model_t model);
void serialize(Model *model, int &size, uint8_t *buffer);
/**
* @brief get trained model image
* @param model
* @param level pyramid level index(level>=0 && level<modelLevel-1)
* @param data image data buffer(need allocated), can input nullptr to query width/height/channels
* @param length buffer length not less than width*height*channels
* @param width image width, can input nullptr
* @param height image height, can input nullptr
* @param channels image channels, can input nullptr
* @return
*/
API_PUBLIC void modelImage(Model_t model, int level, unsigned char *data, int length, int *width,
int *height, int *channels);
Model *deserialize(int size, uint8_t *buffer);
/**
* @brief free model
* @param model
* @return
*/
API_PUBLIC void freeModel(Model_t *model);
#endif // GRAY_MATCH_H
/**
* @brief serialize model to buffer
* @param model
* @param buffer need allocated, can input nullptr to query size
* @param size in(buffer size)/out(written size)
* @return true(success)false(failed)
*/
API_PUBLIC bool serialize(Model_t model, unsigned char *buffer, int *size);
/**
* @brief deserialize model
* @param buffer
* @param size buffer size
* @return model
*/
API_PUBLIC Model_t deserialize(unsigned char *buffer, int size);
#endif // GRAY_MATCH_H

BIN
img/3.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
img/h.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 KiB

BIN
img/i.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 KiB

BIN
img/j.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 KiB

BIN
img/k.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 KiB

BIN
img/l.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 KiB

BIN
img/result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

180
integral.cpp Normal file
View File

@ -0,0 +1,180 @@
#include "integral.h"
#include "privateType.h"
#include <opencv2/core/hal/intrin.hpp>
inline void expand(const cv::v_int32 &src, cv::v_float64 &low, cv::v_float64 &high) {
low = cv::v_cvt_f64(src);
high = cv::v_cvt_f64_high(src);
}
inline void integralSum(const cv::v_uint16 &src, double *dst, const double *prevDst,
cv::v_uint32 &pre) {
auto sum = cv::v_add(src, cv::v_rotate_left<1>(src));
sum = cv::v_add(sum, cv::v_rotate_left<2>(sum));
sum = cv::v_add(sum, cv::v_rotate_left<4>(sum));
cv::v_uint32 v1;
cv::v_uint32 v2;
cv::v_expand(sum, v1, v2);
v1 = cv::v_add(v1, pre);
v2 = cv::v_add(v2, pre);
pre = cv::v_setall_u32(cv::v_extract_n<simdSize(cv::v_uint32) - 1>(v2));
cv::v_float64 v3;
cv::v_float64 v4;
expand(cv::v_reinterpret_as_s32(v1), v3, v4);
cv::v_store(dst, cv::v_add(v3, cv::v_load(prevDst)));
cv::v_store(dst + simdSize(cv::v_float64),
cv::v_add(v4, cv::v_load(prevDst + simdSize(cv::v_float64))));
expand(cv::v_reinterpret_as_s32(v2), v3, v4);
cv::v_store(dst + simdSize(cv::v_float64) * 2,
cv::v_add(v3, cv::v_load(prevDst + simdSize(cv::v_float64) * 2)));
cv::v_store(dst + simdSize(cv::v_float64) * 3,
cv::v_add(v4, cv::v_load(prevDst + simdSize(cv::v_float64) * 3)));
}
inline void integralSqSum(cv::v_uint16 &src, double *dst, double *prevDst, cv::v_uint32 &pre) {
cv::v_uint32 v1;
cv::v_uint32 v2;
cv::v_expand(src, v1, v2);
{
auto shift1 = cv::v_rotate_left<1>(src);
cv::v_uint32 v3;
cv::v_uint32 v4;
cv::v_expand(shift1, v3, v4);
v1 = cv::v_add(v1, v3);
v2 = cv::v_add(v2, v4);
v4 = cv::v_extract<2>(v1, v2);
v2 = cv::v_add(v2, v4);
v3 = cv::v_rotate_left<2>(v1);
v1 = cv::v_add(v1, v3);
v1 = cv::v_add(v1, pre);
v2 = cv::v_add(v2, v1);
pre = cv::v_setall_u32(cv::v_extract_n<simdSize(cv::v_uint32) - 1>(v2));
}
cv::v_float64 v3;
cv::v_float64 v4;
expand(cv::v_reinterpret_as_s32(v1), v3, v4);
cv::v_store(dst, cv::v_add(v3, cv::v_load(prevDst)));
cv::v_store(dst + simdSize(cv::v_float64),
cv::v_add(v4, cv::v_load(prevDst + simdSize(cv::v_float64))));
expand(cv::v_reinterpret_as_s32(v2), v3, v4);
cv::v_store(dst + simdSize(cv::v_float64) * 2,
cv::v_add(v3, cv::v_load(prevDst + simdSize(cv::v_float64) * 2)));
cv::v_store(dst + simdSize(cv::v_float64) * 3,
cv::v_add(v4, cv::v_load(prevDst + simdSize(cv::v_float64) * 3)));
}
/*
inline void integralSqSum(cv::v_uint32 &src, double *dst, double *prevDst, cv::v_uint32 &pre) {
src += cv::v_rotate_left<1>(src);
src += cv::v_rotate_left<2>(src);
src += pre;
pre = cv::v_setall_u32(cv::v_extract_n<simdSize(cv::v_uint32) - 1>(src));
cv::v_float64 v1;
cv::v_float64 v2;
expand(cv::v_reinterpret_as_s32(src), v1, v2);
cv::v_store(dst, v1 + cv::v_load(prevDst));
cv::v_store(dst + simdSize(cv::v_float64), v2 + cv::v_load(prevDst +
simdSize(cv::v_float64)));
}
inline void integralSqSum(cv::v_uint16 &src, double *dst, double *prevDst, cv::v_uint32 &pre) {
cv::v_uint32 v1;
cv::v_uint32 v2;
cv::v_expand(src, v1, v2);
integralSqSum(v1, dst, prevDst, pre);
integralSqSum(v2, dst + simdSize(cv::v_uint32), prevDst + simdSize(cv::v_uint32),
pre);
}
*/
inline void integralSum(const cv::v_uint16 &v1, const cv::v_uint16 &v2, double *dst,
const double *prevDst, cv::v_uint32 &pre) {
integralSum(v1, dst, prevDst, pre);
integralSum(v2, dst + simdSize(cv::v_uint16), prevDst + simdSize(cv::v_uint16), pre);
}
inline void integralSqSum(cv::v_uint16 &v1, cv::v_uint16 &v2, double *dst, double *prevDst,
cv::v_uint32 &pre) {
v1 = cv::v_mul_wrap(v1, v1);
v2 = cv::v_mul_wrap(v2, v2);
integralSqSum(v1, dst, prevDst, pre);
integralSqSum(v2, dst + simdSize(cv::v_uint16), prevDst + simdSize(cv::v_uint16), pre);
}
void integralSimd(const cv::Mat &src, cv::Mat &sum, cv::Mat &sqSum) {
const auto size = src.size() + cv::Size(1, 1);
sum.create(size, CV_64FC1);
sqSum.create(size, CV_64FC1);
memset(sum.data, 0, sum.step[ 0 ]);
memset(sqSum.data, 0, sqSum.step[ 0 ]);
const auto *srcStart = src.data;
const auto srcStep = src.step[ 0 ];
auto *sumStart = reinterpret_cast<double *>(sum.data) + sum.step1() + 1;
const auto sumStep = sum.step[ 0 ] / sum.step[ 1 ];
auto *sqSumStart = reinterpret_cast<double *>(sqSum.data) + sqSum.step1() + 1;
const auto sqSumStep = sqSum.step[ 0 ] / sqSum.step[ 1 ];
const auto end = size.width - simdSize(cv::v_uint8);
for (int y = 0; y < src.rows; y++) {
auto *srcPtr = srcStart + srcStep * y;
auto *sumPtr = sumStart + sumStep * y;
const auto *preSumPtr = sumStart + sumStep * (y - 1);
sumPtr[ -1 ] = 0;
cv::v_uint32 prevSum = cv::vx_setzero_u32();
for (int x = 0; x < end; x += simdSize(cv::v_uint8)) {
cv::v_uint16 v1;
cv::v_uint16 v2;
cv::v_expand(cv::v_load(srcPtr + x), v1, v2);
integralSum(v1, v2, sumPtr + x, preSumPtr + x, prevSum);
}
}
for (int y = 0; y < src.rows; y++) {
auto *srcPtr = srcStart + srcStep * y;
auto *sqSumPtr = sqSumStart + sqSumStep * y;
auto *preSqSumPtr = sqSumStart + sqSumStep * (y - 1);
sqSumPtr[ -1 ] = 0;
cv::v_uint32 prevSqSum = cv::vx_setzero_u32();
for (int x = 0; x < end; x += simdSize(cv::v_uint8)) {
cv::v_uint16 v1;
cv::v_uint16 v2;
cv::v_expand(cv::v_load(srcPtr + x), v1, v2);
integralSqSum(v1, v2, sqSumPtr + x, preSqSumPtr + x, prevSqSum);
}
}
const auto start = src.cols - src.cols % simdSize(cv::v_uint8);
for (int y = 0; y < src.rows; y++) {
auto *srcPtr = srcStart + srcStep * y;
auto *sumPtr = sumStart + sumStep * y;
auto *sqSumPtr = sqSumStart + sqSumStep * y;
const auto *preSumPtr = sumStart + sumStep * (y - 1);
const auto *preSqSumPtr = sqSumStart + sqSumStep * (y - 1);
for (int x = start; x < src.cols; x++) {
const auto val = srcPtr[ x ];
const auto sqVal = val * val;
sumPtr[ x ] = sumPtr[ x - 1 ] + val + preSumPtr[ x ] - preSumPtr[ x - 1 ];
sqSumPtr[ x ] = sqSumPtr[ x - 1 ] + sqVal + preSqSumPtr[ x ] - preSqSumPtr[ x - 1 ];
}
}
}

8
integral.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef INTEGRAL_H
#define INTEGRAL_H
#include <opencv2/opencv.hpp>
void integralSimd(const cv::Mat &src, cv::Mat &sum, cv::Mat &sqSum);
#endif // INTEGRAL_H

149
main.cpp
View File

@ -1,42 +1,141 @@
#include "grayMatch.h"
#include <fstream>
#include <iostream>
#include <opencv2/core/utility.hpp>
#include <opencv2/opencv.hpp>
int main() {
auto src =
cv::imread("C:/Users/qiuyong/Desktop/test/template/model3.bmp", cv::IMREAD_GRAYSCALE);
auto dst =
cv::imread("C:/Users/qiuyong/Desktop/test/template/model3_src1.bmp", cv::IMREAD_GRAYSCALE);
int main(int argc, const char *argv[]) {
const std::string keys = "{model m || model image}"
"{scene s || scene image}"
"{view v || view result}"
"{threshold t | 0.7 | match minium score}"
"{bench b || match benchmark}"
"{help h || print this help}";
auto t0 = cv::getTickCount();
auto model = trainModel(src, -1);
auto t1 = cv::getTickCount();
auto poses = matchModel(dst, model, -1, 0, 360, 0, 0.5, 70, 1);
auto t2 = cv::getTickCount();
cv::CommandLineParser cmd(argc, argv, keys);
if (!cmd.check()) {
cmd.printErrors();
return -1;
}
auto trainCost = double(t1 - t0) / cv::getTickFrequency();
auto matchCost = double(t2 - t1) / cv::getTickFrequency();
std::cout << "train(s):" << trainCost << " match(s):" << matchCost << std::endl;
if (cmd.has("help")) {
cmd.printMessage();
return 0;
}
cv::Mat color;
cv::cvtColor(dst, color, cv::COLOR_GRAY2RGB);
for (auto &pose : poses) {
cv::RotatedRect rect(cv::Point2f(pose.x, pose.y), src.size(), -pose.angle);
auto srcFile = std::string(IMG_DIR) + "/3.bmp";
auto dstFile = std::string(IMG_DIR) + "/h.bmp";
if (cmd.has("model"))
srcFile = cmd.get<std::string>("model");
if (cmd.has("scene"))
dstFile = cmd.get<std::string>("scene");
std::vector<cv::Point2f> pts;
rect.points(pts);
auto src = cv::imread(srcFile, cv::IMREAD_GRAYSCALE);
auto dst = cv::imread(dstFile, cv::IMREAD_GRAYSCALE);
if (src.empty() || dst.empty()) {
return -1;
}
cv::line(color, pts[ 0 ], pts[ 1 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
cv::line(color, pts[ 1 ], pts[ 2 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
cv::line(color, pts[ 2 ], pts[ 3 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
cv::line(color, pts[ 3 ], pts[ 0 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
const std::string modelName("model.bin");
{
auto t0 = cv::getTickCount();
auto model = trainModel(src.data, src.cols, src.rows, src.channels(),
static_cast<int>(src.step), 0, 0, src.cols, src.rows, -1);
auto t1 = cv::getTickCount();
// get size
int size;
serialize(model, nullptr, &size);
// serialize to buffer
std::vector<uchar> buffer(size);
serialize(model, buffer.data(), &size);
// write to file
std::ofstream ofs(modelName, std::ios::binary | std::ios::out);
if (!ofs.is_open()) {
return -1;
}
ofs.write(reinterpret_cast<const char *>(buffer.data()), size);
freeModel(&model);
auto trainCost = static_cast<double>(t1 - t0) / cv::getTickFrequency();
std::cout << "train(s):" << trainCost << std::endl;
}
int count = 70;
std::vector<Pose> poses(count);
Model_t model;
auto score = cmd.get<float>("threshold");
{
// open file
std::ifstream ifs(modelName, std::ios::binary | std::ios::in);
if (!ifs.is_open()) {
return -2;
}
// get size
ifs.seekg(0, std::ios::end);
auto size = ifs.tellg();
ifs.seekg(0, std::ios::beg);
// read to buffer
std::vector<uchar> buffer(size);
ifs.read(reinterpret_cast<char *>(buffer.data()), size);
// deserialize from buffer
model = deserialize(buffer.data(), static_cast<int>(buffer.size()));
auto t2 = cv::getTickCount();
matchModel(dst.data, dst.cols, dst.rows, dst.channels(), static_cast<int>(dst.step), 0, 0,
dst.cols, dst.rows, model, &count, poses.data(), -1, 0, 360, 0, score, 1);
auto t3 = cv::getTickCount();
auto matchCost = static_cast<double>(t3 - t2) / cv::getTickFrequency();
std::cout << "match(s):" << matchCost << std::endl;
}
for (int i = 0; i < count; i++) {
const auto &pose = poses[ i ];
std::cout << pose.x << "," << pose.y << "," << pose.angle << "," << pose.score << std::endl;
}
cv::imshow("img", color);
cv::waitKey();
if (cmd.has("bench")) {
constexpr int times = 100;
auto start = cv::getTickCount();
for (int i = 0; i < times; i++) {
matchModel(dst.data, dst.cols, dst.rows, dst.channels(), static_cast<int>(dst.step), 0,
0, dst.cols, dst.rows, model, &count, poses.data(), -1, 0, 360, 0, score, 1);
count = 70;
}
auto end = cv::getTickCount();
const auto cost = static_cast<double>(end - start) / cv::getTickFrequency() / times;
std::cout << "match bench avg(s):" << cost << std::endl;
}
if (cmd.has("view")) {
cv::Mat color;
cv::cvtColor(dst, color, cv::COLOR_GRAY2RGB);
for (int i = 0; i < count; i++) {
const auto &pose = poses[ i ];
cv::RotatedRect rect(cv::Point2f(pose.x, pose.y), src.size(), -pose.angle);
cv::Point2f pts[ 4 ];
rect.points(pts);
cv::line(color, pts[ 0 ], pts[ 1 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
cv::line(color, pts[ 1 ], pts[ 2 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
cv::line(color, pts[ 2 ], pts[ 3 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
cv::line(color, pts[ 3 ], pts[ 0 ], cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
}
cv::imshow("img", color);
cv::waitKey();
}
return 0;
}

41
privateType.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <opencv2/core.hpp>
struct Model {
std::vector<cv::Mat> pyramids;
std::vector<cv::Scalar> mean;
std::vector<double> normal;
std::vector<double> invArea;
std::vector<uchar> equal1;
uchar borderColor = 0;
void clear() {
pyramids.clear();
normal.clear();
invArea.clear();
mean.clear();
equal1.clear();
}
void resize(const std::size_t size) {
normal.resize(size);
invArea.resize(size);
mean.resize(size);
equal1.resize(size);
}
void reserve(const std::size_t size) {
pyramids.reserve(size);
normal.reserve(size);
invArea.reserve(size);
mean.reserve(size);
equal1.reserve(size);
}
};
#if CV_VERSION_MAJOR >= 4 && CV_VERSION_MINOR >= 8
#define simdSize(type) cv::VTraits<type>::nlanes
#else
#define simdSize(type) type::nlanes
#endif

228
serialize.cpp Normal file
View File

@ -0,0 +1,228 @@
#include "grayMatch.h"
#include "privateType.h"
#include <opencv2/core/hal/intrin.hpp>
class Buffer {
public:
Buffer(const int size_, unsigned char *data_)
: m_size(size_)
, m_data(data_) {}
virtual ~Buffer() = default;
virtual void operator&(uchar &val) = 0;
virtual void operator&(std::vector<cv::Mat> &val) = 0;
virtual void operator&(std::vector<cv::Scalar> &val) = 0;
virtual void operator&(std::vector<double> &val) = 0;
virtual void operator&(std::vector<uchar> &val) = 0;
void operator&(Model &val) {
this->operator&(val.pyramids);
this->operator&(val.mean);
this->operator&(val.normal);
this->operator&(val.invArea);
this->operator&(val.equal1);
this->operator&(val.borderColor);
}
[[nodiscard]] int count() const {
return m_size;
}
protected:
int m_size = 0;
unsigned char *m_data = nullptr;
};
void binWrite(void *const dst, const void *src, const int size) {
memcpy(dst, src, size);
}
void fakeWrite(void *const dst, const void *src, const int size) {
(void)dst;
(void)src;
(void)size;
}
using Write = void (*)(void *, const void *, int);
template <Write write> class OutBuffer final : public Buffer {
public:
explicit OutBuffer(unsigned char *const data_)
: Buffer(0, data_) {}
void operator&(uchar &val) override {
write(m_data + m_size, &val, sizeof(val));
m_size += static_cast<int>(sizeof(val));
}
void operator&(std::vector<cv::Mat> &val) override {
const int size = static_cast<int>(val.size());
write(m_data + m_size, &size, sizeof(size));
m_size += static_cast<int>(sizeof(size));
for (auto &element : val) {
writeElement(element);
}
}
void writeElement(cv::Mat &val) {
write(m_data + m_size, &val.cols, sizeof(int));
m_size += static_cast<int>(sizeof(int));
write(m_data + m_size, &val.rows, sizeof(int));
m_size += static_cast<int>(sizeof(int));
for (int i = 0; i < val.rows; i++) {
write(m_data + m_size, val.ptr<unsigned char>(i), val.cols);
m_size += val.cols;
}
}
void operator&(std::vector<cv::Scalar> &val) override {
const int size = static_cast<int>(val.size());
write(m_data + m_size, &size, sizeof(size));
m_size += static_cast<int>(sizeof(size));
for (auto &element : val) {
writeElement(element);
}
}
void writeElement(const cv::Scalar &val) {
write(m_data + m_size, val.val, sizeof(double) * 4);
m_size += static_cast<int>(sizeof(double)) * 4;
}
void operator&(std::vector<double> &val) override {
const int size = static_cast<int>(val.size());
write(m_data + m_size, &size, sizeof(size));
m_size += static_cast<int>(sizeof(size));
write(m_data + m_size, val.data(), static_cast<int>(sizeof(double)) * size);
m_size += static_cast<int>(sizeof(double)) * size;
}
void operator&(std::vector<uchar> &val) override {
const int size = static_cast<int>(val.size());
write(m_data + m_size, &size, sizeof(size));
m_size += static_cast<int>(sizeof(size));
write(m_data + m_size, val.data(), sizeof(uchar) * size);
m_size += static_cast<int>(sizeof(uchar)) * size;
}
};
using SizeCountBuffer = OutBuffer<fakeWrite>;
using WriteBuffer = OutBuffer<binWrite>;
class ReadBuffer final : public Buffer {
public:
explicit ReadBuffer(unsigned char *data_)
: Buffer(0, data_) {}
void operator&(uchar &val) override {
memcpy(&val, m_data + m_size, sizeof(uchar));
m_size += static_cast<int>(sizeof(uchar));
}
void operator&(std::vector<cv::Mat> &val) override {
int count = 0;
memcpy(&count, m_data + m_size, sizeof(int));
val.resize(count);
m_size += static_cast<int>(sizeof(count));
for (auto &element : val) {
read(element);
}
}
void read(cv::Mat &val) {
int width = 0;
memcpy(&width, m_data + m_size, sizeof(int));
m_size += static_cast<int>(sizeof(int));
int height = 0;
memcpy(&height, m_data + m_size, sizeof(int));
m_size += static_cast<int>(sizeof(int));
const int alignedWidth = static_cast<int>(cv::alignSize(width, simdSize(cv::v_uint8)));
const auto img = cv::Mat::zeros(height, alignedWidth, CV_8UC1);
val = img(cv::Rect(0, 0, width, height));
for (int y = 0; y < height; y++) {
auto *ptr = val.ptr<uchar>(y);
memcpy(ptr, m_data + m_size, width);
m_size += width;
}
}
void operator&(std::vector<cv::Scalar> &val) override {
int count = 0;
memcpy(&count, m_data + m_size, sizeof(int));
val.resize(count);
m_size += static_cast<int>(sizeof(count));
for (auto &element : val) {
read(element);
}
}
void read(cv::Scalar &val) {
memcpy(val.val, m_data + m_size, sizeof(double) * 4);
m_size += static_cast<int>(sizeof(double)) * 4;
}
void operator&(std::vector<double> &val) override {
int count = 0;
memcpy(&count, m_data + m_size, sizeof(int));
val.resize(count);
m_size += static_cast<int>(sizeof(count));
memcpy(val.data(), m_data + m_size, sizeof(double) * count);
m_size += static_cast<int>(sizeof(double)) * count;
}
void operator&(std::vector<uchar> &val) override {
int count = 0;
memcpy(&count, m_data + m_size, sizeof(int));
val.resize(count);
m_size += static_cast<int>(sizeof(count));
memcpy(val.data(), m_data + m_size, sizeof(bool) * count);
m_size += static_cast<int>(sizeof(uchar)) * count;
}
};
void operation(Buffer *buf, Model &model) {
*buf &model;
}
bool serialize(Model *const model, unsigned char *buffer, int *size) {
if (nullptr == size) {
return false;
}
if (nullptr == model) {
*size = 0;
return false;
}
SizeCountBuffer counter(buffer);
operation(&counter, *model);
if (nullptr == buffer) {
*size = counter.count();
return true;
}
if (counter.count() > *size) {
*size = 0;
return false;
}
WriteBuffer writer(buffer);
operation(&writer, *model);
return true;
}
Model_t deserialize(unsigned char *buffer, const int size) {
if (size < 1 || nullptr == buffer) {
return nullptr;
}
ReadBuffer reader(buffer);
auto *model = new Model;
operation(&reader, *model);
return model;
}