应用程序实用工具——使用 OpenCV 和相似性测量进行视频输入 OpenCV v4.8.0

上一个教程使用 GDAL 读取地理空间光栅文件

下一个教程使用 OpenCV 创建视频

原作者Bernát Gábor
兼容性OpenCV >= 3.0

目标

如今,拥有数字视频录制系统已成为一种普遍现象。因此,您最终需要处理的不再是一批图像,而是视频流。这些视频流可能有两种:实时图像馈送(如网络摄像头)或预先录制并存储在硬盘驱动器中的文件。幸运的是,OpenCV 使用相同的 C++ 类以相同的方式处理这两种流。下面就是本教程的内容:

  • 如何打开和读取视频流
  • 检查图像相似性的两种方法: PSNR 和 SSIM

源代码

作为使用 OpenCV 展示这些方法的测试案例,我创建了一个小程序,读入两个视频文件并对它们进行相似性检查。你可以用它来检查新的视频压缩算法是否有效。假设有一个参考(原始)视频,如 Megamind 的这个小片段,以及它的压缩版本。您也可以在 OpenCV 源代码库的 samples/data 文件夹中找到源代码和这些视频文件。
C++

#include <iostream> // 用于标准 I/O
#include <string> // 用于字符串
#include <iomanip> // 用于控制浮点打印精度
#include <sstream> // 字符串到数字的转换
#include <opencv2/core.hpp> // OpenCV 基本结构 (cv::Mat, Scalar)
#include <opencv2/imgproc.hpp> // 高斯模糊
#include <opencv2/videoio.hpp> // 高斯模糊
#include <opencv2/highgui.hpp> // OpenCV window I/O
using namespace std;
using namespace cv;
double getPSNR ( const Mat& I1, const Mat& I2);
Scalar getMSSIM( const Mat& I1, const Mat& I2);
static void help()
{
 cout
 << "------------------------------------------------------------------------------" << endl
 << "This program shows how to read a video file with OpenCV. In addition, it "
 << "tests the similarity of two input videos first with PSNR, and for the frames "
 << "below a PSNR trigger value, also with MSSIM." << endl
 << "Usage:" << endl
 << "./video-input-psnr-ssim <referenceVideo> <useCaseTestVideo> <PSNR_Trigger_Value> <Wait_Between_Frames> " << endl
 << "--------------------------------------------------------------------------" << endl
 << endl;
}
int main(int argc, char *argv[])
{
 help();
 if (argc != 5)
 {
 cout << "Not enough parameters" << endl;
 return -1;
 }
 stringstream conv;
 const string sourceReference = argv[1], sourceCompareWith = argv[2];
 int psnrTriggerValue, delay;
 conv << argv[3] << endl << argv[4]; // 输入字符串
 conv >> psnrTriggerValue >> delay; // 删除数字
 int frameNum = -1; // 帧计数器
 VideoCapture captRefrnc(sourceReference), captUndTst(sourceCompareWith);
 if (!captRefrnc.isOpened())
 {
 cout << "Could not open reference " << sourceReference << endl;
 return -1;
 }
 if (!captUndTst.isOpened())
 {
 cout << "Could not open case test " << sourceCompareWith << endl;
 return -1;
 }
 Size refS = Size((int) captRefrnc.get(CAP_PROP_FRAME_WIDTH),
 (int) captRefrnc.get(CAP_PROP_FRAME_HEIGHT)),
 uTSi = Size((int) captUndTst.get(CAP_PROP_FRAME_WIDTH),
 (int) captUndTst.get(CAP_PROP_FRAME_HEIGHT));
 if (refS != uTSi)
 {
 cout << "Inputs have different size!!! Closing." << endl;
 return -1;
 }
 const char* WIN_UT = "Under Test";
 const char* WIN_RF = "Reference";
 // 窗口
 namedWindow(WIN_RF, WINDOW_AUTOSIZE);
 namedWindow(WIN_UT, WINDOW_AUTOSIZE);
 moveWindow(WIN_RF, 400 , 0); //750, 2 (bernat =0)
 moveWindow(WIN_UT, refS.width, 0); //1500, 2
 cout << "Reference frame resolution: Width=" << refS.width << " Height=" << refS.height
 << " of nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;
 cout << "PSNR trigger value " << setiosflags(ios::fixed) << setprecision(3)
 << psnrTriggerValue << endl;
 Mat frameReference, frameUnderTest;
 double psnrV;
 Scalar mssimV;
 for(;;) //在窗口中显示捕获的图像并重复
 {
 captRefrnc >> frameReference;
 captUndTst >> frameUnderTest;
 if (frameReference.empty() || frameUnderTest.empty())
 {
 cout << " < < < Game over! > > > ";
 break;
 }
 ++frameNum;
 cout << "Frame: " << frameNum << "# ";
 psnrV = getPSNR(frameReference,frameUnderTest);
 cout << setiosflags(ios::fixed) << setprecision(3) << psnrV << "dB";
 if (psnrV < psnrTriggerValue && psnrV)
 {
 mssimV = getMSSIM(frameReference, frameUnderTest);
 cout << " MSSIM: "
 << " R " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[2] * 100 << "%"
 << " G " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[1] * 100 << "%"
 << " B " << setiosflags(ios::fixed) << setprecision(2) << mssimV.val[0] * 100 << "%";
 }
 cout << endl;
 imshow(WIN_RF, frameReference);
 imshow(WIN_UT, frameUnderTest);
 char c = (char)waitKey(delay);
 if (c == 27) break;
 }
 return 0;
}
// ![get-psnr]
double getPSNR(const Mat& I1, const Mat& I2)
{
 Mat s1;
 absdiff(I1, I2, s1); // |I1 - I2|
 s1.convertTo(s1, CV_32F); // 无法在 8 位上进行平方运算
 s1 = s1.mul(s1); // |I1 - I2|^2
 Scalar s = sum(s1); // 每个通道的元素总和
 double sse = s.val[0] + s.val[1] + s.val[2]; // 通道总和
 if( sse <= 1e-10) // 对于小数值,返回 0
 return 0;
 else
 {
 double mse = sse / (double)(I1.channels() * I1.total());
 double psnr = 10.0 * log10((255 * 255) / mse);
 return psnr;
 }
}
// ![get-psnr]
// ![get-mssim]
Scalar getMSSIM( const Mat& i1, const Mat& i2)
{
 const double C1 = 6.5025, C2 = 58.5225;
 /***************************** INITS **********************************/
 int d = CV_32F;
 Mat I1, I2;
 i1.convertTo(I1, d); // 无法计算一个字节的大数值
 i2.convertTo(I2, d);
 Mat I2_2 = I2.mul(I2); // I2^2
 Mat I1_2 = I1.mul(I1); // I1^2
 Mat I1_I2 = I1.mul(I2); // I1 * I2
 /*************************** END INITS **********************************/
 Mat mu1, mu2; // 初步计算
 GaussianBlur(I1, mu1, Size(11, 11), 1.5);
 GaussianBlur(I2, mu2, Size(11, 11), 1.5);
 Mat mu1_2 = mu1.mul(mu1);
 Mat mu2_2 = mu2.mul(mu2);
 Mat mu1_mu2 = mu1.mul(mu2);
 Mat sigma1_2, sigma2_2, sigma12;
 GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
 sigma1_2 -= mu1_2;
 GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
 sigma2_2 -= mu2_2;
 GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
 sigma12 -= mu1_mu2;
 Mat t1, t2, t3;
 t1 = 2 * mu1_mu2 + C1;
 t2 = 2 * sigma12 + C2;
 t3 = t1.mul(t2); // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
 t1 = mu1_2 + mu2_2 + C1;
 t2 = sigma1_2 + sigma2_2 + C2;
 t1 = t1.mul(t2); // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
 Mat ssim_map;
 divide(t3, t1, ssim_map); // ssim_map = t3./t1;
 Scalar mssim = mean(ssim_map); // mssim = ssim map 的平均值
 return mssim;
}
// ![get-mssim]

Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 2/3 compatibility
from __future__ import print_function
import numpy as np
import cv2 as cv
import argparse
import sys
# [get-psnr]
def getPSNR(I1, I2):
 s1 = cv.absdiff(I1, I2) #|I1 - I2|
 s1 = np.float32(s1) # 无法在 8 位上进行平方运算
 s1 = s1 * s1 # |I1 - I2|^2
 sse = s1.sum() # 每个通道的元素总和
 if sse <= 1e-10# 对通道求和
 return 0 # 对于小数值,返回 0
 else:
 shape = I1.shape
 mse = 1.0 * sse / (shape[0] * shape[1] * shape[2])
 psnr = 10.0 * np.log10((255 * 255) / mse)
 return psnr
# [get-psnr]
# [get-mssim]
def getMSSISM(i1, i2):
 C1 = 6.5025
 C2 = 58.5225
 # INITS
 I1 = np.float32(i1) # 无法计算一个字节的大数值
 I2 = np.float32(i2)
 i2_2 = i2 * i2 # i2^2
 i1_2 = i1 * i1 # i1^2
 i1_i2 = i1 * i2 # i1 * i2
 # END INITS
 # 初步计算
 mu1 = cv.GaussianBlur(I1, (11, 11), 1.5)
 mu2 = cv.GaussianBlur(I2, (11, 11), 1.5)
 mu1_2 = mu1 * mu1
 mu2_2 = mu2 * mu2
 mu1_mu2 = mu1 * mu2
 sigma1_2 = cv.GaussianBlur(I1_2, (11, 11), 1.5)
 sigma1_2 -= mu1_2
 sigma2_2 = cv.GaussianBlur(I2_2, (11, 11), 1.5)
 sigma2_2 -= mu2_2
 sigma12 = cv.GaussianBlur(I1_I2, (11, 11), 1.5)
 sigma12 -= mu1_mu2
 t1 = 2 * mu1_mu2 + C1
 t2 = 2 * sigma12 + C2
 t3 = t1 * t2 # t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
 t1 = mu1_2 + mu2_2 + C1
 t2 = sigma1_2 + sigma2_2 + C2
 t1 = t1 * t2 # t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
 ssim_map = cv.divide(t3, t1) # ssim_map = t3./t1;
 mssim = cv.mean(ssim_map) # mssim = ssim 地图的平均值
 return mssim
# [get-mssim]
def main():
 parser = argparse.ArgumentParser()
 parser.add_argument("-d", "--delay", type=int, default=30, help=" Time delay")
 parser.add_argument("-v", "--psnrtriggervalue", type=int, default=30, help="PSNR Trigger Value")
 parser.add_argument("-r", "--ref", type=str, default="Megamind.avi", help="Path to reference video")
 parser.add_argument("-t", "--undertest", type=str, default="Megamind_bugy.avi",
 help="Path to the video to be tested")
 args = parser.parse_args()
 sourceReference = args.ref
 sourceCompareWith = args.undertest
 delay = args.delay
 psnrTriggerValue = args.psnrtriggervalue
 framenum = -1 # 帧计数器
 captRefrnc = cv.VideoCapture(cv.samples.findFileOrKeep(sourceReference))
 captUndTst = cv.VideoCapture(cv.samples.findFileOrKeep(sourceCompareWith))
 if not captRefrnc.isOpened():
 print("Could not open the reference " + sourceReference)
 sys.exit(-1)
 if not captUndTst.isOpened():
 print("Could not open case test " + sourceCompareWith)
 sys.exit(-1)
 refS = (int(captRefrnc.get(cv.CAP_PROP_FRAME_WIDTH)), int(captRefrnc.get(cv.CAP_PROP_FRAME_HEIGHT)))
 uTSi = (int(captUndTst.get(cv.CAP_PROP_FRAME_WIDTH)), int(captUndTst.get(cv.CAP_PROP_FRAME_HEIGHT)))
 if refS != uTSi:
 print("Inputs have different size!!! Closing.")
 sys.exit(-1)
 WIN_UT = "Under Test"
 WIN_RF = "Reference"
 cv.namedWindow(WIN_RF, cv.WINDOW_AUTOSIZE)
 cv.namedWindow(WIN_UT, cv.WINDOW_AUTOSIZE)
 cv.moveWindow(WIN_RF, 400, 0) #750, 2 (bernat =0)
 cv.moveWindow(WIN_UT, refS[0], 0) #1500, 2
 print("Reference frame resolution: Width={} Height={} of nr#: {}".format(refS[0], refS[1],
 captRefrnc.get(cv.CAP_PROP_FRAME_COUNT)))
 print("PSNR trigger value {}".format(psnrTriggerValue))
 while True# 在窗口中显示捕捉到的图像并重复
 _, frameReference = captRefrnc.read()
 _, frameUnderTest = captUndTst.read()
 if frameReference is None or frameUnderTest is None:
 print(" < < < Game over! > > > ")
 break
 framenum += 1
 psnrv = getPSNR(frameReference, frameUnderTest)
 print("Frame: {}# {}dB".format(framenum, round(psnrv, 3)), end=" ")
 if (psnrv < psnrTriggerValue and psnrv):
 mssimv = getMSSISM(frameReference, frameUnderTest)
 print("MSSISM: R {}% G {}% B {}%".format(round(mssimv[2] * 100, 2), round(mssimv[1] * 100, 2),
 round(mssimv[0] * 100, 2)), end=" ")
 print()
 cv.imshow(WIN_RF, frameReference)
 cv.imshow(WIN_UT, frameUnderTest)
 k = cv.waitKey(delay)
 if k == 27:
 break
 sys.exit(0)
if __name__ == "__main__":
 main()

如何读取视频流(在线摄像机或离线文件)?

基本上,视频处理所需的所有功能都集成在 cv::VideoCapture C++ 类中。该类本身基于 FFmpeg 开放源代码库。这是 OpenCV 的基本依赖项,因此您无需担心。视频由连续的图像组成,我们在文献中将这些图像称为帧。视频文件有一个帧频,规定两帧之间的间隔时间。对于摄像机来说,每秒能数字化的帧数通常是有限制的,但这一特性并不那么重要,因为摄像机随时都能看到当前世界的快照。

您需要做的第一件事就是为 cv::VideoCapture 类分配源。您可以通过 cv::VideoCapture::VideoCapture 或其 cv::VideoCapture::open 函数来完成这项工作。如果该参数是一个整数,那么您将把该类绑定到一个摄像机、一个设备上。这里传递的数字是操作系统分配的设备 ID。如果您的系统只连接了一台摄像机,那么它的 ID 很可能是 0,然后依次递增。如果传入的参数是字符串,则指的是视频文件,字符串指向文件的位置和名称。例如,上层源代码的有效命令行是

video/Megamind.avi video/Megamind_bug.avi 35 10

我们进行相似性检查。这需要一个参考文件和一个测试用例视频文件。前两个参数指的就是这个文件。这里我们使用的是相对地址。这意味着应用程序将查看当前工作目录并打开视频文件夹,尝试在其中找到 Megamind.avi 和 Megamind_bug.avi。

const string sourceReference = argv[1],sourceCompareWith = argv[2];
VideoCapture captRefrnc(sourceReference)// 或
VideoCapture captUndTst;
captUndTst.open(sourceCompareWith)

要检查类与视频源的绑定是否成功,请使用 cv::VideoCapture::isOpened 函数:

if ( !captRefrnc.isOpened())
 {
 cout << "Could not open reference " << sourceReference << endl;
 return -1}

调用对象的析构函数时,视频会自动关闭。不过,如果想在此之前关闭视频,则需要调用 cv::VideoCapture::release 函数。视频的帧只是简单的图像。因此,我们只需将它们从 cv::VideoCapture 对象中提取出来,然后放入 Mat 对象中即可。视频流是连续的。您可以使用 cv::VideoCapture::read 或重载的 >> 操作符一个接一个地获取帧:

Mat frameReference, frameUnderTest;
captRefrnc >> frameReference;
captUndTst.read(frameUnderTest)

如果无法获取帧(视频流已关闭或视频文件已结束),则上层读取操作将使 Mat 对象为空。我们可以用一个简单的 if 来检查:

if( frameReference.empty() || frameUnderTest.empty())
{
 // 退出程序
}

读取方法由帧抓取和解码组成。您可以使用 cv::VideoCapture::grabcv::VideoCapture::retrieve 函数明确调用这两个函数。

除了帧的内容外,视频还附加了许多信息。这些信息通常是数字,但在某些情况下可能是短字符序列(4 字节或更少)。因此,为了获取这些信息,有一个名为 cv::VideoCapture::get 的通用函数可以返回包含这些属性的双值。在有效值仅为整数的情况下,使用比特运算从 double 类型和转换中解码字符。它的单个参数是所查询属性的 ID。例如,在这里我们可以获得参考文件和测试用例视频文件中帧的大小,以及参考文件中的帧数。

Size refS = Size((int) captRefrnc.get(CAP_PROP_FRAME_WIDTH)(int) captRefrnc.get(CAP_PROP_FRAME_HEIGHT))、
cout << "参考帧分辨率: 宽度=" << refS.width << " 高度=" << refS.height
 << " 的 nr#: " << captRefrnc.get(CAP_PROP_FRAME_COUNT) << endl;

在处理视频时,您可能经常希望自己控制这些值。为此,我们提供了一个 cv::VideoCapture::set 函数。它的第一个参数仍然是要更改的属性名称,第二个参数为 double 类型,包含要设置的值。如果设置成功,则返回 true,否则返回 false。在视频文件中寻找给定的时间或帧就是很好的例子:

captRefrnc.set(CAP_PROP_POS_MSEC, 1.2); // 转到视频中 1.2 秒的位置
captRefrnc.set(CAP_PROP_POS_FRAMES, 10); // 转到视频的第 10 帧
// 现在,读取操作将读取所设置位置的帧。

关于可以读取和更改的属性,请参阅 cv::VideoCapture::getcv::VideoCapture::set 函数的文档。

图像相似性 - PSNR 和 SSIM

我们要检查视频转换操作的不易察觉程度,因此需要一个系统来逐帧检查相似性或差异。最常用的算法是 PSNR(又称峰值信噪比)。最简单的定义是从均方误差开始的。假设有两幅图像: I1 和 I2;尺寸分别为 i 和 j,由 c 个通道组成。

M S E = 1 c ∗ i ∗ j ∑ ( I 1 − I 2 ) 2 MSE = \frac{1}{c*i*j} \sum{(I_1-I_2)^2} MSE=cij1(I1I2)2
那么 PSNR 表示为

P S N R = 10 ⋅ log ⁡ 10 ( M A X I 2 M S E ) PSNR = 10 \cdot \log_{10} \left( \frac{MAX_I^2}{MSE} \right) PSNR=10log10(MSEMAXI2)
这里的 M A X I MAX_I MAXI 是像素的最大有效值。对于简单的单字节图像,每个像素每个通道的最大有效值为 255。当两幅图像相同时,MSE 将为零,从而导致 PSNR 公式中除以零的运算无效。在这种情况下,PSNR 是未定义的,我们需要单独处理这种情况。过渡到对数刻度是因为像素值的动态范围非常大。所有这些转换到 OpenCV 的函数看起来如下
C++

double getPSNR(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());
 double psnr = 10.0 * log10((255 * 255) / mse);
 return psnr;
 }
}

Python

def getPSNR(I1, I2):
 s1 = cv.absdiff(I1, I2) #|I1 - I2|
 s1 = np.float32(s1) # cannot make a square on 8 bits
 s1 = s1 * s1 # |I1 - I2|^2
 sse = s1.sum() # sum elements per channel
 if sse <= 1e-10: # sum channels
 return 0 # for small values return zero
 else:
 shape = I1.shape
 mse = 1.0 * sse / (shape[0] * shape[1] * shape[2])
 psnr = 10.0 * np.log10((255 * 255) / mse)
 return psnr

通常情况下,视频压缩的结果值在 30 到 50 之间,越高越好。如果图像差异很大,则结果值会低很多,如 15 等。这种相似性检查计算简单快捷,但在实际应用中可能会与人眼感知不一致。结构相似性算法旨在纠正这一点。

描述这些方法远远超出了本教程的目的。为此,我邀请您阅读介绍该算法的文章。不过,您可以通过观察下面的 OpenCV 实现来了解该算法。

注释
SSIM 在 "Z.Wang, A. C. "一文中有更深入的描述: "Z. Wang、A. C. Bovik、H. R.
Sheikh 和 E. P. Simoncelli,"图像质量评估: IEEE Transactions on Image
Processing, vol. 13, no. 4, pp.

C++


Scalar getMSSIM( const Mat& i1, const Mat& i2)
{
 const double C1 = 6.5025, C2 = 58.5225;
 /***************************** INITS **********************************/
 int d = CV_32F;
 Mat I1, I2;
 i1.convertTo(I1, d); // cannot calculate on one byte large values
 i2.convertTo(I2, d);
 Mat I2_2 = I2.mul(I2); // I2^2
 Mat I1_2 = I1.mul(I1); // I1^2
 Mat I1_I2 = I1.mul(I2); // I1 * I2
 /*************************** END INITS **********************************/
 Mat mu1, mu2; // PRELIMINARY COMPUTING
 GaussianBlur(I1, mu1, Size(11, 11), 1.5);
 GaussianBlur(I2, mu2, Size(11, 11), 1.5);
 Mat mu1_2 = mu1.mul(mu1);
 Mat mu2_2 = mu2.mul(mu2);
 Mat mu1_mu2 = mu1.mul(mu2);
 Mat sigma1_2, sigma2_2, sigma12;
 GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
 sigma1_2 -= mu1_2;
 GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
 sigma2_2 -= mu2_2;
 GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);
 sigma12 -= mu1_mu2;
 Mat t1, t2, t3;
 t1 = 2 * mu1_mu2 + C1;
 t2 = 2 * sigma12 + C2;
 t3 = t1.mul(t2); // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
 t1 = mu1_2 + mu2_2 + C1;
 t2 = sigma1_2 + sigma2_2 + C2;
 t1 = t1.mul(t2); // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
 Mat ssim_map;
 divide(t3, t1, ssim_map); // ssim_map = t3./t1;
 Scalar mssim = mean(ssim_map); // mssim = average of ssim map
 return mssim;
}

Python

def getMSSISM(i1, i2):
 C1 = 6.5025
 C2 = 58.5225
 # INITS
 I1 = np.float32(i1) # cannot calculate on one byte large values
 I2 = np.float32(i2)
 I2_2 = I2 * I2 # I2^2
 I1_2 = I1 * I1 # I1^2
 I1_I2 = I1 * I2 # I1 * I2
 # END INITS
 # PRELIMINARY COMPUTING
 mu1 = cv.GaussianBlur(I1, (11, 11), 1.5)
 mu2 = cv.GaussianBlur(I2, (11, 11), 1.5)
 mu1_2 = mu1 * mu1
 mu2_2 = mu2 * mu2
 mu1_mu2 = mu1 * mu2
 sigma1_2 = cv.GaussianBlur(I1_2, (11, 11), 1.5)
 sigma1_2 -= mu1_2
 sigma2_2 = cv.GaussianBlur(I2_2, (11, 11), 1.5)
 sigma2_2 -= mu2_2
 sigma12 = cv.GaussianBlur(I1_I2, (11, 11), 1.5)
 sigma12 -= mu1_mu2
 t1 = 2 * mu1_mu2 + C1
 t2 = 2 * sigma12 + C2
 t3 = t1 * t2 # t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
 t1 = mu1_2 + mu2_2 + C1
 t2 = sigma1_2 + sigma2_2 + C2
 t1 = t1 * t2 # t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
 ssim_map = cv.divide(t3, t1) # ssim_map = t3./t1;
 mssim = cv.mean(ssim_map) # mssim = average of ssim map
 return mssim

这将返回图像每个通道的相似性指数。该值介于 0 和 1 之间,其中 1 代表完全拟合。不幸的是,许多高斯模糊的成本相当高,因此虽然 PSNR 可以在类似实时环境(每秒 24 帧)下工作,但要获得类似的性能结果,所需的时间将大大超过 PSNR。

因此,本教程开头介绍的源代码将对每一帧图像进行 PSNR 测量,并仅对 PSNR 低于输入值的帧图像进行 SSIM 测量。为了实现可视化,我们会在 OpenCV 窗口中显示两幅图像,并将 PSNR 和 MSSIM 值打印到控制台。预计会看到类似的内容:

在这里插入图片描述

您可以在 YouTube 上看到运行时的实例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值