#include #include #include #include #include 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 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> knn; cv::BFMatcher(cv::NORM_HAMMING).knnMatch(da, db, knn, 2); std::vector 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 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 corners{{0,0},{(float)a.cols,0},{(float)a.cols,(float)a.rows},{0,(float)a.rows}}; std::vector warped; cv::perspectiveTransform(corners, warped, h); std::vector frame{{0,0},{(float)b.cols,0},{(float)b.cols,(float)b.rows},{0,(float)b.rows}}; std::vector 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 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; }