Введение
Статья 566 разобрала контуры как способ описания формы объектов — детекция признаков (features) даёт альтернативный, дополняющий подход: поиск отдельных, характерных, легко повторно опознаваемых точек на изображении (углы, особые текстурные паттерны), полезных для задач сопоставления изображений, поиска объекта на другом изображении, и панорамной склейки.
Концепция
Ключевая точка (keypoint) — точка на изображении, выделяющаяся определённой характерной особенностью (чаще всего — угол, где границы объекта резко меняют направление, или область с уникальной, легко отличимой текстурой) — такие точки выбираются именно потому, что их относительно легко найти повторно на другом изображении той же сцены, снятом с иного ракурса или при иных условиях освещения, в отличие от, например, точки на однородной, лишённой текстуры поверхности, которую невозможно надёжно отличить от соседних точек той же поверхности. Дескриптор — числовое представление локальной окрестности вокруг ключевой точки, спроектированное так, чтобы оставаться устойчивым (или, по крайней мере, предсказуемо изменяющимся) при типичных преобразованиях изображения (поворот, масштабирование, изменение освещения) — сопоставление дескрипторов между двумя разными изображениями позволяет находить соответствующие друг другу точки, даже если сама сцена была сфотографирована с разных ракурсов или в разных условиях.
Пример кода: Детекция признаков (features)
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
int main()
{
cv::Mat image = cv::imread("scene.jpg", cv::IMREAD_GRAYSCALE);
cv::Ptr<cv::ORB> detector = cv::ORB::create(500); // ORB — быстрый, свободный от патентных ограничений детектор
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
detector->detectAndCompute(image, cv::noArray(), keypoints, descriptors);
std::cout << "Найдено ключевых точек: " << keypoints.size() << std::endl;
cv::Mat imageWithKeypoints;
cv::drawKeypoints(image, keypoints, imageWithKeypoints, cv::Scalar(0, 255, 0));
cv::imwrite("keypoints_result.png", imageWithKeypoints);
return 0;
}
// Сопоставление дескрипторов между двумя изображениями — поиск соответствующих точек
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
int main()
{
cv::Mat image1 = cv::imread("object.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat image2 = cv::imread("scene_with_object.jpg", cv::IMREAD_GRAYSCALE);
cv::Ptr<cv::ORB> detector = cv::ORB::create();
std::vector<cv::KeyPoint> keypoints1, keypoints2;
cv::Mat descriptors1, descriptors2;
detector->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1);
detector->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);
cv::BFMatcher matcher(cv::NORM_HAMMING); // сопоставитель дескрипторов — Hamming distance для ORB
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
// Сортировка по качеству соответствия — лучшие (наименьшее расстояние) совпадения первыми
std::sort(matches.begin(), matches.end(),
[](const cv::DMatch &a, const cv::DMatch &b) { return a.distance < b.distance; });
cv::Mat result;
std::vector<cv::DMatch> bestMatches(matches.begin(), matches.begin() + std::min(50, (int)matches.size()));
cv::drawMatches(image1, keypoints1, image2, keypoints2, bestMatches, result);
cv::imwrite("matches_result.png", result);
return 0;
}
Пояснения к коду
cv::ORB::create(500) показывает создание детектора признаков ORB (Oriented FAST and Rotated BRIEF) — выбор именно ORB, а не более старых альтернатив (SIFT, SURF), обоснован тем, что ORB полностью свободен от патентных ограничений (в отличие от исторически патентованных SIFT/SURF, статья 473 цикла Qt Multimedia о схожих лицензионных ограничениях для видеокодеков) при сохранении достаточно хорошего практического качества для большинства типичных задач. detectAndCompute() за один вызов выполняет и обнаружение ключевых точек, и вычисление их дескрипторов — это типичный, удобный паттерн API OpenCV для большинства детекторов признаков. Пример сопоставления показывает практический сценарий поиска объекта на сцене — дескрипторы обоих изображений вычисляются независимо, затем cv::BFMatcher (brute-force matcher) находит для каждого дескриптора первого изображения наиболее похожий дескриптор второго изображения согласно выбранной метрике расстояния (NORM_HAMMING, подходящей именно для бинарных дескрипторов ORB), и сортировка результатов по качеству соответствия (distance) позволяет отобрать наиболее надёжные, вероятно корректные соответствия, отбросив менее надёжные, потенциально ложные совпадения.
Подводные камни
- Использование детекторов SIFT/SURF в коммерческом проекте без проверки текущего статуса патентных ограничений — хотя патенты на SIFT уже истекли (на момент написания этого цикла), исторически эти алгоритмы были патентованными, и для проектов с долгой историей разработки или работающих в разных юрисдикциях стоит явно проверить актуальный патентный статус используемого алгоритма, аналогично подводным камням видеокодеков из статьи 473 Qt Multimedia цикла, либо предпочесть изначально свободные от таких рисков альтернативы (ORB).
- Сопоставление ВСЕХ найденных совпадений без фильтрации по качеству, включая заведомо ложные, случайные совпадения с большим расстоянием между дескрипторами — как показано в примере с отбором
bestMatches, прямое использование необработанного, полного спискаmatchesбез сортировки и фильтрации обычно включает существенную долю случайных, не отражающих реальное соответствие точек совпадений, которые могут серьёзно искажать дальнейший анализ (например, попытку вычислить геометрическое преобразование между изображениями на основе всех совпадений без отбора лучших из них). - Применение детекции признаков к изображениям с недостаточной текстурой или контрастом (однородные поверхности, размытые изображения) — детекторы признаков по своей природе требуют наличия достаточно выраженных, контрастных особенностей изображения для обнаружения надёжных ключевых точек, и попытка применить их к изображениям без такой текстуры (чистое небо, однородная стена) даст крайне мало или вовсе никаких надёжных ключевых точек, что ограничивает применимость этого подхода для определённых типов сцен.
- Игнорирование вычислительной стоимости детекции и сопоставления признаков для приложений, требующих обработки в реальном времени (статья 568 про захват видео) — детекция признаков и особенно brute-force сопоставление дескрипторов (с вычислительной сложностью, растущей пропорционально произведению количества признаков на обоих изображениях) могут быть существенно медленнее простых операций фильтрации (статья 564), и для приложений реального времени стоит явно профилировать эту стадию обработки и, при необходимости, рассмотреть более быстрые альтернативы сопоставления (FLANN-based matcher вместо brute-force для больших наборов признаков) или ограничение количества обрабатываемых ключевых точек.