Files
scan-calibrator/src/measure_overlap.cpp
T
2026-07-29 16:25:16 +08:00

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;
}