86 lines
4.2 KiB
C++
86 lines
4.2 KiB
C++
#include <opencv2/opencv.hpp>
|
|
#include <algorithm>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc != 3) { std::cerr << "usage: measure_overlap IMAGE_A IMAGE_B\n"; return 2; }
|
|
cv::Mat a = cv::imread(argv[1], cv::IMREAD_GRAYSCALE);
|
|
cv::Mat b = cv::imread(argv[2], cv::IMREAD_GRAYSCALE);
|
|
if (a.empty() || b.empty()) { std::cerr << "cannot read input image\n"; return 3; }
|
|
if (a.size() != b.size()) cv::resize(b, b, a.size());
|
|
|
|
const double scale = std::min(1.0, 960.0 / a.cols);
|
|
if (scale < 1.0) {
|
|
cv::resize(a, a, {}, scale, scale, cv::INTER_AREA);
|
|
cv::resize(b, b, {}, scale, scale, cv::INTER_AREA);
|
|
}
|
|
cv::Mat af, bf, window;
|
|
a.convertTo(af, CV_32F);
|
|
b.convertTo(bf, CV_32F);
|
|
cv::createHanningWindow(window, a.size(), CV_32F);
|
|
double phase_response = 0.0;
|
|
const cv::Point2d phase = cv::phaseCorrelate(af, bf, window, &phase_response);
|
|
const double phase_dx = phase.x / a.cols;
|
|
const double phase_dy = phase.y / a.rows;
|
|
|
|
auto orb = cv::ORB::create(2500, 1.2f, 8, 31, 0, 2, cv::ORB::HARRIS_SCORE, 31, 8);
|
|
std::vector<cv::KeyPoint> ka, kb; cv::Mat da, db;
|
|
orb->detectAndCompute(a, {}, ka, da);
|
|
orb->detectAndCompute(b, {}, kb, db);
|
|
if (da.empty() || db.empty()) {
|
|
std::cout << "{\"valid\":false,\"reason\":\"not_enough_features\",\"overlap\":0"
|
|
<< ",\"phase_dx\":" << phase_dx << ",\"phase_dy\":" << phase_dy
|
|
<< ",\"phase_response\":" << phase_response << "}\n";
|
|
return 0;
|
|
}
|
|
std::vector<std::vector<cv::DMatch>> knn;
|
|
cv::BFMatcher(cv::NORM_HAMMING).knnMatch(da, db, knn, 2);
|
|
std::vector<cv::DMatch> good;
|
|
for (const auto& pair : knn)
|
|
if (pair.size() == 2 && pair[0].distance < 0.75f * pair[1].distance) good.push_back(pair[0]);
|
|
if (good.size() < 8) {
|
|
std::cout << "{\"valid\":false,\"reason\":\"not_enough_matches\",\"good_matches\":"
|
|
<< good.size() << ",\"overlap\":0"
|
|
<< ",\"phase_dx\":" << phase_dx << ",\"phase_dy\":" << phase_dy
|
|
<< ",\"phase_response\":" << phase_response << "}\n";
|
|
return 0;
|
|
}
|
|
std::vector<cv::Point2f> pa, pb;
|
|
for (const auto& m : good) { pa.push_back(ka[m.queryIdx].pt); pb.push_back(kb[m.trainIdx].pt); }
|
|
cv::Mat mask;
|
|
cv::Mat h = cv::findHomography(pa, pb, cv::RANSAC, 4.0, mask, 3000, 0.995);
|
|
const int inliers = mask.empty() ? 0 : cv::countNonZero(mask);
|
|
const double inlier_ratio = good.empty() ? 0.0 : inliers / (double)good.size();
|
|
if (h.empty() || inliers < 50 || inlier_ratio < 0.45) {
|
|
std::cout << "{\"valid\":false,\"reason\":\"homography_failed\",\"good_matches\":"
|
|
<< good.size() << ",\"inliers\":" << inliers
|
|
<< ",\"inlier_ratio\":" << inlier_ratio << ",\"overlap\":0"
|
|
<< ",\"phase_dx\":" << phase_dx << ",\"phase_dy\":" << phase_dy
|
|
<< ",\"phase_response\":" << phase_response << "}\n";
|
|
return 0;
|
|
}
|
|
|
|
std::vector<cv::Point2f> corners{{0,0},{(float)a.cols,0},{(float)a.cols,(float)a.rows},{0,(float)a.rows}};
|
|
std::vector<cv::Point2f> warped;
|
|
cv::perspectiveTransform(corners, warped, h);
|
|
std::vector<cv::Point2f> frame{{0,0},{(float)b.cols,0},{(float)b.cols,(float)b.rows},{0,(float)b.rows}};
|
|
std::vector<cv::Point2f> intersection;
|
|
const float area = cv::intersectConvexConvex(warped, frame, intersection, true);
|
|
const double overlap = std::clamp(area / (double)(b.cols * b.rows), 0.0, 1.0);
|
|
std::vector<cv::Point2f> center{{a.cols * .5f, a.rows * .5f}}, moved_center;
|
|
cv::perspectiveTransform(center, moved_center, h);
|
|
const double dx = (moved_center[0].x - center[0].x) / a.cols;
|
|
const double dy = (moved_center[0].y - center[0].y) / a.rows;
|
|
std::cout << std::fixed << std::setprecision(4)
|
|
<< "{\"valid\":true,\"overlap\":" << overlap
|
|
<< ",\"good_matches\":" << good.size()
|
|
<< ",\"inliers\":" << inliers
|
|
<< ",\"inlier_ratio\":" << inlier_ratio
|
|
<< ",\"center_dx\":" << dx << ",\"center_dy\":" << dy
|
|
<< ",\"phase_dx\":" << phase_dx << ",\"phase_dy\":" << phase_dy
|
|
<< ",\"phase_response\":" << phase_response << "}\n";
|
|
return 0;
|
|
}
|