42

I have two instances of cv::Mat : m1 and m2. They are of the same numeric type and sizes. Is there any function in OpenCV that returns whether the matrices are identical (have all the same values)?

Andre Holzner
  • 18,333
  • 6
  • 54
  • 63
kirkor
  • 421
  • 1
  • 4
  • 3

10 Answers10

66

As mentioned by Acme, you can use cv::compare although it is not as clean as you might hope.
In the following example, cv::compare is called by using the != operator:

// Get a matrix with non-zero values at points where the 
// two matrices have different values
cv::Mat diff = a != b;
// Equal if no elements disagree
bool eq = cv::countNonZero(diff) == Scalar(0,0,0,0);

If you know the type you can use the STL equal function. The Mat iterators take care of cases where the data is non-contiguous, and you can use a vector type for multichannel matrices.

bool eq = std::equal(a.begin<uchar>(), a.end<uchar>(), b.begin<uchar>());

Edit: Updated to handle multichannel case.

Tim MB
  • 4,413
  • 4
  • 38
  • 48
  • 8
    `bool eq = cv::countNonZero(a!=b) == 0;`. I think this is reasonably clean. The most annoying thing is that you have to invert the comparison operator compared to what you actually want to know (`!=` instead of `==`) since there is no `countZero()` function. – Brent Bradburn Mar 03 '13 at 21:06
  • 3
    The cv::Mat may or may not continuous, I am not sure the bytes padding by each row has the same value. – StereoMatching Sep 03 '13 at 03:15
  • 3
    Yes but I believe the Mat iterators take care of this for you? http://docs.opencv.org/modules/core/doc/basic_structures.html#mat-begin – Tim MB Sep 03 '13 at 13:03
  • Why do you say that `cv::compare` might not be clean? – Antonio Sep 18 '15 at 08:52
  • Been a while, but I guess because the test reads as a double negative (equal if the are _no_ non-zeroes in a _not equals_ b), which I think is less clear than something like A == B. – Tim MB Sep 19 '15 at 14:20
  • 1
    Ok, now I understand what you meant! Please check if you agree with my edit. (Note: Please add an @Antonio if you want me to be notified about your comments) – Antonio Sep 24 '15 at 14:34
  • For some reason "cv::Mat diff = a != b;" is changing the actual contents of a for me! Beware... – Agargara Apr 14 '17 at 01:34
  • link is dead for `cv::compare` – Krupip Jan 29 '19 at 22:32
  • @opa Link fixed. – Tim MB Jan 31 '19 at 09:45
  • This solution is NOT VALID FOR MULTI-CHANNEL as cv::countNonZero is only for single channel. – Josep Bosch May 09 '23 at 07:17
  • @Josep Bosch Thanks for flagging. I've updated the answer to include multichannel cases. – Tim MB May 11 '23 at 14:06
  • Hmm the multichannel version did not work for me. Also https://stackoverflow.com/questions/31231565/countnonzero-function-gives-an-assertion-error-in-opencv?noredirect=1&lq=1 suggests `countNonZero()` works for one channel only. Is this a OpenCV version thing? Either way I went with the sum() version, which works for multi channel mats and is fine for positive pixel values. – martin.zaenker Aug 22 '23 at 14:45
20

The following will work also for multi-channel matrices:

bool isEqual = (sum(img1 != img2) == Scalar(0,0,0,0));

Since sum accepts matrices with 1 to 4 channels, and returns a Scalar, where the element at [0] is the result of the sum for first channel, and so on.

Miki
  • 40,887
  • 13
  • 123
  • 202
15

Another way using a single function would be to use:

bool areIdentical = !cv::norm(img1,img2,NORM_L1);

Since L1 norm is calculated as ∑I|img1(I)−img2(I)|

reference: OpenCV norm

fmigneault
  • 341
  • 3
  • 5
  • `norm` always outputs a `double` even if the images are of integral type – Daniel Jan 15 '21 at 01:55
  • @Daniel It doesn't matter in this case, we only want to know if the sum of the difference is equal to zero or not. When zero, the `not` operator will convert zero to `true` and anything else to `false` which is exactly what we want. – fmigneault Jan 17 '21 at 21:37
13

This is the code I use to compare generic (not depending on dimensions or type of elements) cv::Mat instances:

bool matIsEqual(const cv::Mat Mat1, const cv::Mat Mat2)
{
  if( Mat1.dims == Mat2.dims && 
    Mat1.size == Mat2.size && 
    Mat1.elemSize() == Mat2.elemSize())
  {
    if( Mat1.isContinuous() && Mat2.isContinuous())
    {
      return 0==memcmp( Mat1.ptr(), Mat2.ptr(), Mat1.total()*Mat1.elemSize());
    }
    else
    {
      const cv::Mat* arrays[] = {&Mat1, &Mat2, 0};
      uchar* ptrs[2];
      cv::NAryMatIterator it( arrays, ptrs, 2);
      for(unsigned int p = 0; p < it.nplanes; p++, ++it)
        if( 0!=memcmp( it.ptrs[0], it.ptrs[1], it.size*Mat1.elemSize()) )
          return false;

      return true;
    }
  }

  return false;
}

I don't understand, why cv::Mat does not have an operator == according to this implementation.

Angie Quijano
  • 4,167
  • 3
  • 25
  • 30
user5309470
  • 131
  • 1
  • 3
  • 1
    This answer should be on top. It is, to my knowledge, the fastest way to do the comparison. – fortuna Jun 03 '19 at 08:47
  • 1
    Profiled solutions proposed by solosuper, Tim MB (std:equal method), this solution, and Miki and this solution much much faster. – hansonap Jan 13 '22 at 21:01
7

Use cv::compare combined with cv::countNonZero.

An SO question that might help you further OpenCV compare two images and get different pixels

Community
  • 1
  • 1
Sadique
  • 22,572
  • 7
  • 65
  • 91
  • 1
    I was the one editing the answer, because it was incomplete, but I must say that `cv::countNonZero` does not work for multichannel images. – Antonio Sep 18 '15 at 09:22
5

As mentioned by Acme and Tim, you can use cv::compare. This is the code I use to compare my cv::Mat:

 bool matIsEqual(const cv::Mat mat1, const cv::Mat mat2){
    // treat two empty mat as identical as well
    if (mat1.empty() && mat2.empty()) {
        return true;
    }
    // if dimensionality of two mat is not identical, these two mat is not identical
    if (mat1.cols != mat2.cols || mat1.rows != mat2.rows || mat1.dims != mat2.dims) {
        return false;
    }
    cv::Mat diff;
    cv::compare(mat1, mat2, diff, cv::CMP_NE);
    int nz = cv::countNonZero(diff);
    return nz==0;
}

It is important to stand out that the function cv::countNonZero only works with cv::Mat of one channel, so if you need to compare two cv::Mat images, you need first to convert your cv::Mat in this way:

Mat gray1, gray2;
cvtColor(InputMat1, gray1, CV_BGR2GRAY);
cvtColor(InputMat2, gray2, CV_BGR2GRAY);

where InputMat1 and InputMat2 are the cv::Mat you want to compare. After that you can call the function:

bool equal = matsEqual(gray1, gray2);

I took this code of this site: OpenCV: compare whether two Mat is identical

I hope this help you.

Community
  • 1
  • 1
Angie Quijano
  • 4,167
  • 3
  • 25
  • 30
  • 3
    By converting the images into grayscale, you lose information, and potentially you make identical 2 color images originally different. – Antonio Sep 18 '15 at 08:59
  • This works pretty well, but uses a lot of cpu compared to Miki's answer. – cmperezg May 28 '19 at 15:17
  • @Antonio, not if you compare the color images and only convert the diff to grayscale before calling countNonZero() ? – Martin Jun 27 '19 at 12:36
3

I use this:

bool areEqual(const cv::Mat& a, const cv::Mat& b) {
    cv::Mat temp;
    cv::bitwise_xor(a,b,temp); //It vectorizes well with SSE/NEON
    return !(cv::countNonZero(temp) );
}

If you have to do this operation many times, you can make this into a class, have temp as member and prevent the image to be allocated every time. Detail: Make temp mutable so that areEqual can be a const method.

Note though that cv::countNonZero only works with cv::Mat of one channel. It's overkill, but in that case one could use cv::split to split each channel into separate images and do cv::countNonZero on them.

Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197
3

For multichannel images, you could use cv::Mat::reshape to create a single channel image without any extra overhead.

An update of Antonio's answer would be

bool areEqual(const cv::Mat& a, const cv::Mat& b)
{
    cv::Mat temp;
    cv::bitwise_xor(a,b,temp);
    return !(cv::countNonZero(temp.reshape(1)));
}
solosuper
  • 81
  • 4
0

The problem of using cv::countNonZero is that this function only works for one-channel images. If you want to work with multichannel images, you have to take care of each channel individually. The first step is to split an image into channels. As Antonio explained, you can use cv::split function for this purpose. After splitting, you can use cv::countNonZero for each channel and sum the results along all channels by using iteration. cv::Mat::channels gives you the number of channels.

This is the code that I use to check whether two matrices are identical.

bool isEqual(cv::Mat firstImage, cv::Mat secondImage){
    cv::Mat dst;
    std::vector<cv::Mat>channels;
    int count = 0;
    cv::bitwise_xor(firstImage, secondImage, dst);
    cv::split(dst, channels);
    for (int ch = 0; ch<dst.channels();ch++){
        count += cv::countNonZero(channels[ch]);
    }
    return count == 0 ? true : false;
}
black
  • 799
  • 1
  • 9
  • 23
-1
bool matIsEqual(const cv::Mat &m1, const cv::Mat &m2)
{
    if(m1.type() != m2.type())
        return false;
    if(m1.size() != m2.size())
        return false;
    return std::equal(m1.begin<uchar>(), m1.end<uchar>(), m2.begin<uchar>());
}
  • 2
    Please don't post code-only answers. The main audience, future readers, will be grateful to see explained *why* this answers the question instead of having to infer it from the code. Also, since this is an old, well answered question, please explain how it complements *all* other answers. – Gert Arnold Feb 12 '23 at 10:31