PeakGen
PeakGen

Reputation: 23035

Detecting difference between 2 images

I am working on the following code

#include <iostream>
#include <opencv2/core/core.hpp>
#include <string>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/video/background_segm.hpp>

using namespace std;
using namespace cv;

int main()
{
    Mat current,currentGrey,next,abs;
    VideoCapture cam1,cam2;

    std:: vector<vector<Point>>contours;
    vector<vector<Point>>contoursPoly(contours.size());

    cam1.open(0);
    cam2.open(0);

    namedWindow("Normal");
    namedWindow("Difference");

    if(!cam1.isOpened())
    {
        cout << "Cam not found" << endl;
        return -1;
    }



    while(true)
    {
        //Take the input
        cam1 >> current;
        currentGrey = current;
        cam2 >> next;

        //Convert to grey
        cvtColor(currentGrey,currentGrey,CV_RGB2GRAY);
        cvtColor(next,next,CV_RGB2GRAY);



        //Reduce Noise
        cv::GaussianBlur(currentGrey,currentGrey,Size(0,0),4);
        cv::GaussianBlur(next,next,Size(0,0),4);

        imshow("Normal",currentGrey);

        //Get the absolute difference
        absdiff(currentGrey,next,abs);
        imshow("Difference",abs);


       for(int i=0;i<abs.rows;i++)
        {
            for(int j=0;j<abs.cols;j++)
            {
                if(abs.at<int>(j,i)>0)
                {
                    cout << "Change Detected" << endl;

                    j = abs.cols+1;
                    i = abs.rows+1;
                }

            }
        }


        if(waitKey(30)>=0)
        {
            break;
        }
    }

}

In here, what I am trying to do is print a message whenever a difference between images are detected. Following part is the technique

for(int i=0;i<abs.rows;i++)
            {
                for(int j=0;j<abs.cols;j++)
                {
                    if(abs.at<int>(j,i)>0)
                    {
                        cout << "Change Detected" << endl;

                        j = abs.cols+1;
                        i = abs.rows+1;
                    }

                }
            }

Unfortunately, instead of printing messages when a difference is detected, it prints the message always. Why is this?

Upvotes: 3

Views: 3881

Answers (4)

vahid_rowghanian
vahid_rowghanian

Reputation: 23

The function getMSE() described above can be tweaked a little to better cover unsigned integer 8 data type. The difference on unsigned integer 8 datatype will produce 0 every time the result is negative. By converting matrices to double datatype at first and then computing the mean squared error, this problem would be avoided.

double getMSE(Mat& I1, Mat& I2)
{
    Mat s1;
    // save the I! and I2 type before converting to float
    int im1type = I1.type();
    int im2type = I2.type();
    // convert to float to avoid producing zero for negative numbers
    I1.convertTo(I1, CV_32F);
    I2.convertTo(I2, CV_32F);
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);         // sum elements per channel

    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double  mse =sse /(double)(I1.channels() * I1.total());
        return mse;
        // Instead of returning MSE, the tutorial code returned PSNR (below).
        //double psnr = 10.0*log10((255*255)/mse);
        //return psnr;
    }
     // return I1 and I2 to their initial types
    I1.convertTo(I1, im1type);
    I2.convertTo(I2, im2type);

}

The above code returns zero for small mse values (under 1e-10). Terms s.val1 and s.val[2] are zero for 1D images.

If you want to check for 1D image input as well (it is basically supporting 3 channel image), use the following code to test (with random unsigned numbers):

Mat I1(12, 12, CV_8UC1), I2(12, 12, CV_8UC1);
double low = 0;
double high = 255;

cv::randu(I1, Scalar(low), Scalar(high));
cv::randu(I2, Scalar(low), Scalar(high));
double mse = getMSE(I1, I2);
cout << mse << endl;

If you want to check for 3D image input, use the following code to test (with random unsigned numbers):

Mat I1(12, 12, CV_8UC3), I2(12, 12, CV_8UC3);
double low = 0;
double high = 255;

cv::randu(I1, Scalar(low), Scalar(high));
cv::randu(I2, Scalar(low), Scalar(high));
double mse = getMSE(I1, I2);
cout << mse << endl;

Upvotes: 1

fatihk
fatihk

Reputation: 7929

Image differencing has some tricks. Due to noise any 2 frames may not be same.

In order to alleviate the effect of the noise you can use method blur() or GaussianBlur() for every frame so that minute details may be removed with simple box or Gaussian filter.

Then, as a similarity criterion, you can take the difference of two frames and after taking the absolute value of the resulting difference matrix with abs, you can sum all the elements and calculate the ratio of this sum to the total pixel sum of the first frame. If this ratio is more than some threshold, lets say 0.05, then you can infer that image frames are sufficiently different.

Upvotes: 1

Bull
Bull

Reputation: 11951

You should calculate the mean square error between the two frames.

MSE = sum((frame1-frame2)^2 ) / no. of pixels

There is an example of calculating it in an OpenCV tutorial.

Based on that code you could have

double getMSE(const Mat& I1, const Mat& I2)
{
    Mat s1;
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);         // sum elements per channel

    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels

    if( sse <= 1e-10) // for small values return zero
        return 0;
    else
    {
        double  mse =sse /(double)(I1.channels() * I1.total());
        return mse;
        // Instead of returning MSE, the tutorial code returned PSNR (below).
        //double psnr = 10.0*log10((255*255)/mse);
        //return psnr;
    }
}

You can use it in your code like this:

   if(getMSE(currentGrey,next) > some_threshold)
        cout << "Change Detected" << endl;

It is up to you to decide the magnitude of MSE below which you consider the images to be the same. Also you should prefilter with GaussianBlur() to reduce noise, like you already do. The blur method suggested by @fatih_k is not a Gaussian filter; it is a box filter and although faster may introduce artifacts.

Upvotes: 2

Tom
Tom

Reputation: 986

Let's take a look what OpenCV documentation says about cv::waitKey returned value:

Returns the code of the pressed key or -1 if no key was pressed before the specified time had elapsed.

So... the loop is infinite and "Change Detected" is printed once for every two images compared until the program is terminated.

Upvotes: 0

Related Questions