如何在 OpenCV 中检查两个图像是不是几乎相同?

Posted

技术标签:

【中文标题】如何在 OpenCV 中检查两个图像是不是几乎相同?【英文标题】:How to check if two images are almost the same in OpenCV?如何在 OpenCV 中检查两个图像是否几乎相同? 【发布时间】:2021-06-08 15:39:46 【问题描述】:

这听起来很容易,但我已经花了好几个小时了。有几篇标题相似的帖子,所以让我先描述一下我的问题。 我有H264 编码的视频文件,这些文件显示结肠镜检查/胃镜检查的记录。

在检查过程中,灭虫者可以制作某种截图。您可以在视频中看到这一点,因为大约一秒钟左右图像没有移动,因此有几帧显示“相同”。我想知道这些屏幕截图是什么时候制作的。

所以我首先提取了视频的图像:

ffmpeg -hwaccel cuvid -c:v h264_cuvid -i '/myVideo.mkv' -vf "hwdownload,format=nv12" -start_number 0 -vsync vfr -q:v 1 '/myFrames/%05d.jpg'

这很好用,结果是一个包含所有高质量图像的文件夹。现在的想法是比较图像x 和图像x+1(或+y),看看它们是否“相同”,如果是,则截取屏幕截图。 如果我看一下那些图像,图像看起来真的是一样的,我看不出有什么区别,但是电脑可以。

由于这些图像已被压缩/编码,因此它们有损失。我猜根据视频编码过程中的关键帧,那些“相同”图像之间的差异有时是 0,有时是“巨大”。到目前为止的问题,是时候写一点代码了:

//  init mPrev with last element
cv::Mat mPrev = cv::imread(imagePaths[imagePaths.size() - 1])(*rect).clone();
cvtColor(mPrev, mPrev, cv::COLOR_BGR2GRAY);
//  remove smaller noise 
cv::medianBlur(mPrev, mPrev, 5);
//  create binary image, shows only light reflection (landmarks) everything else is to dark
mPrev = mPrev.setTo(0, mPrev < max);
mPrev = mPrev.setTo(255, mPrev >= max);

cv::Mat diff;

std::vector<int> screenShotVec;
for (int k = start; k < end; k++) 
    cv::Mat mat = cv::imread(imagePaths[k])(*rect).clone();
    cvtColor(mat, mat, cv::COLOR_BGR2GRAY);
    //  remove smaller noise 
    cv::medianBlur(mat, mat, medSize);
    //  create binary image, shows only light reflection (landmarks) everything else is to dark
    mat = mat.setTo(0, mat < max);
    mat = mat.setTo(255, mat >= max);

    
    double d = cv::sum(mat)[0];

    //  if image is totally black, it is not a screenshot, since parts of interest always have light reflection
    if (d > 0) 
        //  get difference of binary images
        absdiff(mPrev, mat, diff);
        // differences should be very small and easy to remove with median blur
        cv::medianBlur(diff, diff, 9);
        d = cv::sum(diff)[0];

        // no difference, it is a screenshot
        if (d == 0) 
            screenShotVec.push_back(k);
        
    
    //clone the mat for the next round
    mPrev = mat.clone();

这是迄今为止效果最好的代码。但它不是很稳定,我有来自许多不同内窥镜处理器、相机和抓取器的视频。 所以我每次都要调整。例如,如果我“cv::substract”两个伪相同的帧,这就是结果:

虽然这是我“cv::substract”两帧相机移动非常小的结果:

大多数时候相机移动得非常快,因为我将帧 x 与帧 x+y (y >= 5) 进行比较,差异更加明显,问题是当相机移动不快时。 除了 cv::substract 我尝试了几种内核大小作为中值,我尝试只用 canny 检测边缘,然后比较它们或使用 cv::norm 函数。

有没有人有建议,我如何将那些伪相同的帧转换或测量为相同的东西,而显示真实变化的帧仍然可以区分?

附言很抱歉我不能发布真实的图像,因为这是医疗数据

【问题讨论】:

您对比较直方图有何看法?它可能很有用,但我不确定当相机不快速移动时的直方图结果。 嗨@badcode,直方图是一种快速的方法,但遗憾的是,当图像不移动时它看起来非常相似。 【参考方案1】:

经过几次测试,我终于找到了适合我的东西。讨论已经在 2013 年 here on ***,特征匹配。 opencv 中有几种匹配算法可用。我选择了this tutorial的代码作为基础。我做了一些更改,结果如下(OpenCv 4.5.2):

#include <string.h>
#include <opencv2/core/mat.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/xfeatures2d.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;

void Detector::run(vector<string> imagePaths) 

    Mat mPrev = cv::imread(imagePaths[imagePaths.size() - 1]);
    cvtColor(mPrev, mPrev, cv::COLOR_BGR2GRAY);
    cv::medianBlur(mPrev, mPrev, 5);

    for (int k = start; k < end; k++) 
        Mat mat = cv::imread(imagePaths[k]);
        cvtColor(mat, mat, cv::COLOR_BGR2GRAY);
        medianBlur(mat, mat, 5);

        if (areImageFeaturesTheSame(mPrev, mat)) 
            //yes it is the same image
        

    mPrev = mat.clone();


bool Detector::areImageFeaturesTheSame(cv::Mat img1, cv::Mat img2) 

    //threshold to check if the left img 
    //feature coordinates are almost the same like the right one
    const float xyThreshold = 0.1;

    //-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
    int minHessian = 800; //smaller value finds more and bigger value less features
    Ptr<SURF> detector = SURF::create(minHessian);
    std::vector<KeyPoint> keypoints1, keypoints2;
    Mat descriptors1, descriptors2;

    detector->detectAndCompute(img1, noArray(), keypoints1, descriptors1);
    detector->detectAndCompute(img2, noArray(), keypoints2, descriptors2);

    //-- Step 2: Matching descriptor vectors with a FLANN based matcher
    // Since SURF is a floating-point descriptor NORM_L2 is used
    Ptr<cv::DescriptorMatcher> matcher = cv::DescriptorMatcher::create(cv::DescriptorMatcher::FLANNBASED);
    std::vector<DMatch> knn_matches;
    
    //no features images is not a screenshot
    if(descriptors1.empty() || descriptors2.empty())
        return false; 
    

    matcher->match(descriptors1, descriptors2, knn_matches, 2);
    //-- using a threshold for the distance of a match
    const float distanceThresh = 0.5;

    int matchCount = 0;

    for (size_t i = 0; i < knn_matches.size(); i++) 
        //check if distance of match is small. check if x and y in left are right image almost the same, keep in mind that a feature can be slightly different even in an image which looks the same for an human
        if (knn_matches[i].distance < distanceThresh &&
            abs(keypoints1[knn_matches[i].queryIdx].pt.x - keypoints2[knn_matches[i].trainIdx].pt.x) < xyThreshold &&
            abs(keypoints1[knn_matches[i].queryIdx].pt.y - keypoints2[knn_matches[i].trainIdx].pt.y) < xyThreshold) 
            matchCount++;
        
    

    //have at least 18 of those features, this works fine for me
    //but depending on the image you may need another value here
    return matchCount > 18;

记住大部分代码来自here。这就是想法,我尝试查看特征是否相同并且在相同的位置,如果这是真的,我认为它是相同的图像。由于我提取了视频的帧,因此它们彼此非常相似。所以我只比较每 x(第 10)张图片。这使得差异更加明显。这是两个相同图像的示例,我将 minHessian 提高到 20000,所以没有太多特征:

您可以看到所有匹配的特征左右在同一位置,线条是直的。 在这里你可以看到如果有 20 帧差异,特征匹配的样子: 您可以看到大部分匹配的要素线都略微上升/下降。如果我将图像放在一条垂直线上,您可能会在左/右的 y 轴上看到相同的图像。一些匹配的特征位于完全不同的位置,只是因为新右图像上的另一个特征看起来非常相似,在我的代码中,带有 xyThresholdif 很小心对于所有这一切,由于该值非常小0.1,因此匹配的特征必须在同一位置左右。

一个缺点是,如果您遍历整个视频,整个过程会相对较慢。我考虑为此训练一个人工智能。我已经尝试过匹配器的 opencv cuda 版本,对于我的具体情况,它并没有真正更快。

【讨论】:

以上是关于如何在 OpenCV 中检查两个图像是不是几乎相同?的主要内容,如果未能解决你的问题,请参考以下文章

在opencv中规范化图像

检查灰度图像中的像素是不是为黑色(OpenCV)

如何实现快速的 OpenCV 均匀性检测图像处理算法?

OpenCV将相同的图像放入容器中

在 Python 中使用 Opencv 从图像中减去背景

OpenCV错误:bitwise_and抛出掩码和图像大小不同的错误