Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

I will try to elaborate a little more my comment about matching method. Could be useful for someone else reading this. Hope I won't say too much wrong things.

First of all, a descriptor can be seen basically as an array of numbers that encode the local information around the corresponding keypoint.

For floating point descriptors (like SIFT, or SURF), we can compute the distance between them by:

Euclidean distance

This is the classical Euclidean distance between two vectors.

Binary descriptors use a binary string to encode the local information, and the suitable distance to use is the Hamming distance (the number of different bits):

Hamming distance

For example, descriptor 1 is 10010 and descriptor 2 is 11010. If we treat the binary string as an array of 5 numbers, the Euclidean distance would be 1, the Hamming distance would be 1.

If we have the same result (before the square root operation) in theory, it could be different on a computer, based upon how numbers are represented. In OpenCV, this binary string seems to be represented by a vector of uchar (8 bits) (CV_8U): 10010 becomes 18 and 11010 becomes 26. The Euclidean distance would be 8 and the Hamming distance would still be 1.

For example, we have a descriptor1=00010010 (18 dec) and we want to match it to the closest descriptors, descriptor2=00011010 (26 dec) or descriptor3=00010111 (23 dec).

normL2(descriptor1, descriptor2) //== 8
normL2(descriptor1, descriptor3) //== 5
normHamming(descriptor1, descriptor2) //==1
normHamming(descriptor1, descriptor3) //==2

This would lead to match descriptor1 to a different descriptor according to the matching method.

Some test code:

#include <iostream>
#include <opencv2/opencv.hpp>    

int main() {
  uchar desc1 = 0x12; //18 dec ; 10010
  uchar desc2 = 0x1A; //26 dec ; 11010
  uchar desc3 = 0x17; //23 dec ; 10111

  cv::Mat M1 = (cv::Mat_<float>(1,1) << desc1);
  cv::Mat M2 = (cv::Mat_<float>(1,1) << desc2);
  cv::Mat M3 = (cv::Mat_<float>(1,1) << desc3);
  std::cout << "NORM_L2=" << cv::norm(M1, M2, cv::NORM_L2) << std::endl; //8
  std::cout << "NORM_L2=" << cv::norm(M1, M3, cv::NORM_L2) << std::endl; //5


  cv::Mat M4 = (cv::Mat_<uchar>(1,1) << desc1);
  cv::Mat M5 = (cv::Mat_<uchar>(1,1) << desc2);
  cv::Mat M6 = (cv::Mat_<uchar>(1,1) << desc3);
  std::cout << "NORM_HAMMING=" << cv::norm(M4, M5, cv::NORM_HAMMING) << std::endl; //1
  std::cout << "NORM_HAMMING=" << cv::norm(M4, M6, cv::NORM_HAMMING) << std::endl; //2

  cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce");
  std::vector<cv::DMatch> matches;
  matcher->match(M4, M5, matches);
  std::cout << "Distance=" << matches[0].distance << std::endl; //8

  matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
  matcher->match(M4, M5, matches);
  std::cout << "Distance=" << matches[0].distance << std::endl; //1

  return 0;
}

PS1: It could be easy to add DMatch and others OpenCV structure (like KeyPoint, ...) in the persistence.hpp (just add the corresponding read/write function like for Point) ?

PS2: A link to a blog I found very interesting and helpful about binary descriptors.

I will try to elaborate a little more my comment about matching method. Could be useful for someone else reading this. Hope I won't say too much wrong things.

First of all, a descriptor can be seen basically as an array of numbers that encode the local information around the corresponding keypoint.

For floating point descriptors (like SIFT, or SURF), we can compute the distance between them by:

Euclidean distance

This is the classical Euclidean distance between two vectors.

Binary descriptors use a binary string to encode the local information, and the suitable distance to use is the Hamming distance (the number of different bits):

Hamming distance

One of the advantages of binary descriptors over previous method is the CPU cost involved in the distance computation and thus on the matching process. The XOR operation on two arrays followed by a bit count shoud be less expensive than computing the L2 norm on our CPU architecture.

For example, descriptor 1 is 10010 and descriptor 2 is 11010. If we treat the binary string as an array of 5 numbers, the Euclidean distance would be 1, the Hamming distance would be 1.

If we have the same result (before the square root operation) in theory, it could be different on a computer, based upon how numbers are represented. In OpenCV, this binary string seems to be represented by a vector of uchar (8 bits) (CV_8U): 10010 becomes 18 and 11010 becomes 26. The Euclidean distance would be 8 and the Hamming distance would still be 1.

For example, we have a descriptor1=00010010 (18 dec) and we want to match it to the closest descriptors, descriptor2=00011010 (26 dec) or descriptor3=00010111 (23 dec).

normL2(descriptor1, descriptor2) //== 8
normL2(descriptor1, descriptor3) //== 5
normHamming(descriptor1, descriptor2) //==1
normHamming(descriptor1, descriptor3) //==2

This would lead to match descriptor1 to a different descriptor according to the matching method.

Some test code:

#include <iostream>
#include <opencv2/opencv.hpp>    

int main() {
  uchar desc1 = 0x12; //18 dec ; 10010
  uchar desc2 = 0x1A; //26 dec ; 11010
  uchar desc3 = 0x17; //23 dec ; 10111

  cv::Mat M1 = (cv::Mat_<float>(1,1) << desc1);
  cv::Mat M2 = (cv::Mat_<float>(1,1) << desc2);
  cv::Mat M3 = (cv::Mat_<float>(1,1) << desc3);
  std::cout << "NORM_L2=" << cv::norm(M1, M2, cv::NORM_L2) << std::endl; //8
  std::cout << "NORM_L2=" << cv::norm(M1, M3, cv::NORM_L2) << std::endl; //5


  cv::Mat M4 = (cv::Mat_<uchar>(1,1) << desc1);
  cv::Mat M5 = (cv::Mat_<uchar>(1,1) << desc2);
  cv::Mat M6 = (cv::Mat_<uchar>(1,1) << desc3);
  std::cout << "NORM_HAMMING=" << cv::norm(M4, M5, cv::NORM_HAMMING) << std::endl; //1
  std::cout << "NORM_HAMMING=" << cv::norm(M4, M6, cv::NORM_HAMMING) << std::endl; //2

  cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce");
  std::vector<cv::DMatch> matches;
  matcher->match(M4, M5, matches);
  std::cout << "Distance=" << matches[0].distance << std::endl; //8

  matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
  matcher->match(M4, M5, matches);
  std::cout << "Distance=" << matches[0].distance << std::endl; //1

  return 0;
}

PS1: It could be easy to add DMatch and others OpenCV structure (like KeyPoint, ...) in the persistence.hpp (just add the corresponding read/write function like for Point) ?

PS2: A link to a blog I found very interesting and helpful about binary descriptors.

I will try to elaborate a little more my comment about matching method. Could be useful for someone else reading this. Hope I won't say too much wrong things.

First of all, a descriptor can be seen basically as an array of numbers that encode the local information around the corresponding keypoint.

For floating point descriptors (like SIFT, or SURF), we can compute the distance between them by:

Euclidean distance

This is the classical Euclidean distance between two vectors.

Binary descriptors use a binary string to encode the local information, and the suitable distance to use is the Hamming distance (the number of different bits):

Hamming distance

One of the advantages of binary descriptors over previous method is the CPU cost involved in the distance computation and thus on the matching process. The XOR operation on two arrays followed by a bit count shoud be less expensive than computing the L2 norm on our CPU architecture.

For example, descriptor 1 is 10010 and descriptor 2 is 11010. If we treat the binary string as an array of 5 numbers, the Euclidean distance would be 1, the Hamming distance would be 1.

If we have the same result (before the square root operation) in theory, it could be different on a computer, based upon how numbers are represented. In OpenCV, this binary string seems to be represented by a vector of uchar (8 bits) (CV_8U): 10010 becomes 18 and 11010 becomes 26. The Euclidean distance would be 8 and the Hamming distance would still be 1.

For example, we have a descriptor1=00010010 (18 dec) and we want to match it to the closest descriptors, descriptor2=00011010 (26 dec) or descriptor3=00010111 (23 dec).

normL2(descriptor1, descriptor2) //== 8
normL2(descriptor1, descriptor3) //== 5
normHamming(descriptor1, descriptor2) //==1
normHamming(descriptor1, descriptor3) //==2

This would lead to match descriptor1 to a different descriptor according to the matching method.

Some test code:

#include <iostream>
#include <opencv2/opencv.hpp>    

int main() {
  uchar desc1 = 0x12; //18 dec ; 10010
  uchar desc2 = 0x1A; //26 dec ; 11010
  uchar desc3 = 0x17; //23 dec ; 10111

  cv::Mat M1 = (cv::Mat_<float>(1,1) << desc1);
  cv::Mat M2 = (cv::Mat_<float>(1,1) << desc2);
  cv::Mat M3 = (cv::Mat_<float>(1,1) << desc3);
  std::cout << "NORM_L2=" << cv::norm(M1, M2, cv::NORM_L2) << std::endl; //8
  std::cout << "NORM_L2=" << cv::norm(M1, M3, cv::NORM_L2) << std::endl; //5


  cv::Mat M4 = (cv::Mat_<uchar>(1,1) << desc1);
  cv::Mat M5 = (cv::Mat_<uchar>(1,1) << desc2);
  cv::Mat M6 = (cv::Mat_<uchar>(1,1) << desc3);
  std::cout << "NORM_HAMMING=" << cv::norm(M4, M5, cv::NORM_HAMMING) << std::endl; //1
  std::cout << "NORM_HAMMING=" << cv::norm(M4, M6, cv::NORM_HAMMING) << std::endl; //2

  cv::Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create("BruteForce");
  std::vector<cv::DMatch> matches;
  matcher->match(M4, M5, matches);
  std::cout << "Distance=" << matches[0].distance << std::endl; //8

  matcher = cv::DescriptorMatcher::create("BruteForce-Hamming");
  matches.clear();
  matcher->match(M4, M5, matches);
  std::cout << "Distance=" << matches[0].distance << std::endl; //1

  return 0;
}

PS1: It could be easy to add DMatch and others OpenCV structure (like KeyPoint, ...) in the persistence.hpp (just add the corresponding read/write function like for Point) ?

PS2: A link to a blog I found very interesting and helpful about binary descriptors.