如何在 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 轴上看到相同的图像。一些匹配的特征位于完全不同的位置,只是因为新右图像上的另一个特征看起来非常相似,在我的代码中,带有 xyThreshold 的 if 很小心对于所有这一切,由于该值非常小0.1,因此匹配的特征必须在同一位置左右。
一个缺点是,如果您遍历整个视频,整个过程会相对较慢。我考虑为此训练一个人工智能。我已经尝试过匹配器的 opencv cuda 版本,对于我的具体情况,它并没有真正更快。
【讨论】:
以上是关于如何在 OpenCV 中检查两个图像是不是几乎相同?的主要内容,如果未能解决你的问题,请参考以下文章