检测两幅图像之间的差异

Detecting difference between 2 images

本文关键字:之间 图像 两幅 检测      更新时间:2023-10-16

我正在编写以下代码

#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;
        }
    }
}

在这里,我要做的是在检测到图像之间的差异时打印一条消息。下面是技术

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;
                    }
                }
            }

不幸的是,它不是在检测到差异时打印消息,而是始终打印消息。为什么会这样?

你应该计算两帧之间的均方误差。

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

在OpenCV教程中有一个计算它的例子。

根据这段代码,你可以得到

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;
    }
}

你可以在你的代码中这样使用它:

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

这是由你来决定MSE的大小,你认为图像是相同的。另外,你应该用GaussianBlur()预滤波来降低噪声,就像你已经做的那样。@fatih_k建议的blur方法是而不是高斯滤波器;它是一个框式过滤器,虽然速度更快,但可能会引入工件。

图像区分有一些技巧。由于噪声,任意两帧可能不相同。

为了减轻噪声的影响,您可以对每一帧使用blur()GaussianBlur()方法,以便用简单的框或高斯滤波器去除微小的细节。

然后,作为相似度准则,取两帧的差值,用abs求差值矩阵的绝对值后,对所有元素求和,并计算该和与第一帧总像素和的比值。如果这个比率超过某个阈值,比如0.05,那么你可以推断图像帧是足够不同的。

让我们看一下OpenCV文档中关于cv::waitKey返回值的说明:

返回按下的键的代码,如果在指定时间之前没有按下键,则返回-1。

所以…循环是无限的,并且每比较两个图像打印一次"Change Detected",直到程序终止。

上面描述的函数getMSE()可以稍微调整一下,以更好地覆盖无符号整数8数据类型。无符号整数8数据类型的差值每次结果为负时都会产生0。通过首先将矩阵转换为双数据类型,然后计算均方误差,可以避免这个问题。

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);
}

上面的代码对于较小的mse值(小于1e-10)返回零。s.val1和s.val[2]项对于1D图像为零。

如果您也想检查1D图像输入(它基本上支持3通道图像),请使用以下代码进行测试(随机无符号数):

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;

如果要检查3D图像输入,请使用以下代码进行测试(使用随机无符号数):

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;