Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenCV compare two images and get different pixels

Tags:

c++

opencv

For some reason the code bellow is not working. I have two 640*480 images which are very similar but not the same (at least few hundred/thousand pixels should be different).

This is how I am comparing them and counting different pixels:

unsigned char* row;
unsigned char* row2;
int count = 0;

// this happens in a loop
// fIplImageHeader is current image
// lastFIplImageHeader is image from previous iteration
if ( NULL != lastFIplImageHeader->imageData ) {
for( int y = 0; y < fIplImageHeader->height; y++ )
{
    row = &CV_IMAGE_ELEM( fIplImageHeader, unsigned char, y, 0 );
    row2 = &CV_IMAGE_ELEM( lastFIplImageHeader, unsigned char, y, 0 );
    for( int x = 0; x < fIplImageHeader->width*fIplImageHeader->nChannels; x += fIplImageHeader->nChannels )
    {
        if(row[x] != row2[x] || row[x+1] != row2[x+1] || row[x+2] != row2[x+2])
            count++;
        }
    }
}
}

Now at the end I get number 3626 which would seem alright.

But, I tried opening one of the images in MS Paint and drawing thick red lines all over it which should increase the number of different pixels substantially. I got the same number again: 3626.

Obviously I am doing something wrong here.

I am comparing these images in a loop.

This line is before the loop:

IplImage* lastFIplImageHeader = cvCreateImageHeader(cvSize(640, 480), 8, 3);

Then inside the loop I load images like this:

IplImage* fIplImageHeader = cvLoadImage( filePath.c_str() );

// here I compare the pixels (the first code snippet)

lastFIplImageHeader->imageData = fIplImageHeader->imageData;

So lastFIplImageHeader is storing the image from the previous iteration and fIplImageHeader is storing the current image.

like image 511
Richard Knop Avatar asked Dec 10 '22 11:12

Richard Knop


2 Answers

int count_diff_pixels(cv::Mat in1, cv::Mat in2) {
    cv::Mat diff;
    cv::compare(in1, in2, diff, cv::CMP_NE);
    return cv::countNonZero(diff);
}

Might need some tweaking, but that's about how you do it. Also, you shouldn't be messing with cv* if you really use c++. Use the new c++ interface, and don't worry about freeing images. reading images, remembering the previous one becomes as simple as

cv::Mat prev;
while (...) {
    cv::Mat current = cv::imread(fn); // or whereever your image comes from
    // ... do something ...
    prev = current;
} // automatic memory management!
like image 67
etarion Avatar answered Dec 22 '22 13:12

etarion


It seems to me you aren't counting different pixels, not even pixels at all.
You are counting how often a color channel of one pixel in the first image matches the channel of the corresponding pixel in the other image.

You probably meant to do something along the lines of:

...
//in the inner for loop
if(row[x] != row2[x] || row[x+1] != row2[x+1] || row[x+2] != row2[x+2])
  count++;
...

This will however not account for the alpha channel (if present) and will fail on grayscale images as you might read out of bounds of the data array.

EDIT: As long as you don't release the old image it should be fine. However, it might be better to do something like:

//make sure size and channels are correct.
//If unsure, load the image first and then create with the parameters taken from the loaded image.
cvCreateImage(cvSize(640, 480), 8, 3);

//use cvCopy to copy the contents and proceed as normal
cvCopy(fIplImageHeader , lastFIplImageHeader);

Alternatively, instead of copying, you could just hold a pointer to the old image and use that, as long as you don't release it.

lastFIplImageHeader = fIplImageHeader;
fIplImageHeader = cvLoadImage( filePath.c_str() );

EDIT2: If the difference is all you want, you could have a look at cvSub (with both images loaded as grayscale), followed by cvCountNonZero

like image 33
Darcara Avatar answered Dec 22 '22 14:12

Darcara