#include "Identify.h" #include #include #include #include #include #include #include "Logger.hpp" #include "AsstAux.h" using namespace asst; using namespace cv; using namespace cv::xfeatures2d; bool Identify::add_image(const std::string& name, const std::string& path) { Mat mat = imread(path); if (mat.empty()) { return false; } m_mat_map.emplace(name, mat); return true; } bool asst::Identify::add_text_image(const std::string& text, const std::string& path) { Mat mat = imread(path); if (mat.empty()) { return false; } m_feature_map.emplace(text, surf_detect(mat)); return true; } void Identify::set_use_cache(bool b) noexcept { if (b) { m_use_cache = true; } else { m_cache_map.clear(); m_use_cache = false; } } Mat Identify::image_2_hist(const cv::Mat& src) { Mat src_hsv; cvtColor(src, src_hsv, COLOR_BGR2HSV); int histSize[] = { 50, 60 }; float h_ranges[] = { 0, 180 }; float s_ranges[] = { 0, 256 }; const float* ranges[] = { h_ranges, s_ranges }; int channels[] = { 0, 1 }; MatND src_hist; calcHist(&src_hsv, 1, channels, Mat(), src_hist, 2, histSize, ranges); normalize(src_hist, src_hist, 0, 1, NORM_MINMAX); return src_hist; } double Identify::image_hist_comp(const cv::Mat& src, const cv::MatND& hist) { // keep the interface return value unchanged return 1 - compareHist(image_2_hist(src), hist, CV_COMP_BHATTACHARYYA); } asst::Rect asst::Identify::cvrect_2_rect(const cv::Rect& cvRect) { return asst::Rect(cvRect.x, cvRect.y, cvRect.width, cvRect.height); } std::pair, cv::Mat> asst::Identify::surf_detect(const cv::Mat& mat) { // 灰度图转换 cv::Mat mat_gray; cv::cvtColor(mat, mat_gray, cv::COLOR_RGB2GRAY); constexpr int min_hessian = 400; // SURF特征点检测 static cv::Ptr detector = SURF::create(min_hessian); std::vector keypoints; cv::Mat mat_vector; // 找到特征点并计算特征描述子(向量) detector->detectAndCompute(mat_gray, Mat(), keypoints, mat_vector); return std::make_pair(std::move(keypoints), std::move(mat_vector)); } std::optional asst::Identify::feature_match( const std::vector& query_keypoints, const cv::Mat& query_mat_vector, const std::vector& train_keypoints, const cv::Mat& train_mat_vector #ifdef LOG_TRACE , const cv::Mat query_mat, const cv::Mat train_mat #endif ) { if (query_mat_vector.empty() || train_mat_vector.empty()) { DebugTraceError("feature_match | image empty"); return std::nullopt; } std::vector matches; static FlannBasedMatcher matcher; matcher.match(query_mat_vector, train_mat_vector, matches); #ifdef LOG_TRACE //std::cout << matches.size() << " / " << query_keypoints.size() << std::endl; cv::Mat allmatch_mat; cv::drawMatches(query_mat, query_keypoints, train_mat, train_keypoints, matches, allmatch_mat); #endif // 最大的距离 auto max_iter = std::max_element(matches.cbegin(), matches.cend(), [](const cv::DMatch& lhs, const cv::DMatch& rhs) ->bool { return lhs.distance < rhs.distance; }); // 描述符欧式距离(knn) if (max_iter == matches.cend()) { return std::nullopt;; } float maxdist = max_iter->distance; std::vector approach_matches; std::vector train_approach_keypoints; std::vector query_approach_keypoints; std::vector train_approach_points; std::vector query_approach_points; // 利用距离进行一次逼近 constexpr static const double MatchRatio = 0.4; int approach_index = 0; for (const cv::DMatch dmatch : matches) { if (dmatch.distance < maxdist * MatchRatio) { // 按理说不会越界,以防万一还是检查一下 if (dmatch.queryIdx >= 0 && dmatch.queryIdx < query_keypoints.size() && dmatch.trainIdx >= 0 && dmatch.trainIdx < train_keypoints.size()) { approach_matches.emplace_back(dmatch); train_approach_points.emplace_back(train_keypoints.at(dmatch.trainIdx).pt); query_approach_points.emplace_back(query_keypoints.at(dmatch.queryIdx).pt); train_approach_keypoints.emplace_back(train_keypoints.at(dmatch.trainIdx)); query_approach_keypoints.emplace_back(query_keypoints.at(dmatch.queryIdx)); } } } #ifdef LOG_TRACE cv::Mat approach_mat; cv::drawMatches(query_mat, query_keypoints, train_mat, train_keypoints, approach_matches, approach_mat); #endif if (query_approach_points.empty()) { return std::nullopt; } // 使用RANSAC剔除异常值 std::vector ransac_status; cv::Mat fundametal = cv::findFundamentalMat(query_approach_points, train_approach_points, ransac_status, cv::FM_RANSAC); std::vector ransac_matchs; std::vector train_ransac_keypoints; std::vector query_ransac_keypoints; int index = 0; for (size_t i = 0; i != ransac_status.size(); ++i) { if (ransac_status.at(i) != 0) { train_ransac_keypoints.emplace_back(train_approach_keypoints.at(i)); query_ransac_keypoints.emplace_back(query_approach_keypoints.at(i)); cv::DMatch dmatch = approach_matches.at(i); ransac_matchs.emplace_back(std::move(dmatch)); ++index; } } // 做一次算数均值滤波,过滤异常的点。这个算法有点蠢,TODO可以看下怎么改 size_t point_size = train_ransac_keypoints.size(); if (point_size == 0) { return std::nullopt; } cv::Point sum_point = std::accumulate( train_ransac_keypoints.cbegin(), train_ransac_keypoints.cend(), cv::Point(), [](cv::Point sum, const cv::KeyPoint& rhs) -> cv::Point { return cv::Point(sum.x + rhs.pt.x, sum.y + rhs.pt.y); }); cv::Point avg_point(sum_point.x / point_size, sum_point.y / point_size); std::vector good_matchs; std::vector good_points; // TODO,这个阈值需要根据分辨率进行缩放,而且最好写到配置文件里 constexpr static int DistanceThreshold = 100; for (size_t i = 0; i != train_ransac_keypoints.size(); ++i) { // 没必要算距离,x y各算一下就行了,省点CPU时间 //int distance = std::sqrt(std::pow(avg_point.x - cur_x, 2) + std::pow(avg_point.y - cur_y, 2)); cv::Point2f& pt = train_ransac_keypoints.at(i).pt; int x_distance = std::abs(avg_point.x - pt.x); int y_distance = std::abs(avg_point.y - pt.y); if (x_distance < DistanceThreshold && y_distance < DistanceThreshold) { good_matchs.emplace_back(ransac_matchs.at(i)); good_points.emplace_back(pt); } } #ifdef LOG_TRACE cv::Mat ransac_mat; cv::drawMatches(query_mat, query_keypoints, train_mat, train_keypoints, ransac_matchs, ransac_mat); cv::Mat good_mat; cv::drawMatches(query_mat, query_keypoints, train_mat, train_keypoints, good_matchs, good_mat); #endif constexpr static const double MatchSizeRatioThreshold = 0.1; if (good_points.size() >= query_keypoints.size() * MatchSizeRatioThreshold) { Rect dst; int left = 0, right = 0, top = 0, bottom = 0; for (const cv::Point& pt : good_points) { if (pt.x < left || left == 0) { left = pt.x; } if (pt.x > right || right == 0) { right = pt.x; } if (pt.y < top || top == 0) { top = pt.y; } if (pt.y > bottom || bottom == 0) { bottom = pt.y; } } dst = { left, top, right - left, bottom - top }; return dst; } return std::nullopt; } std::vector