<h1 style="font-size: 32px; font-weight: bold; border-bottom: 2px solid rgb(204, 204, 204); padding: 0px 4px 0px 0px; text-align: left; margin: 0px 0px 10px;">双摄像头操作与图像相似度检测</h1><p style="white-space: normal;">本章节内容是博主网上收集的,主要内容包括:双摄像头的调用方法、图像窗体的固定、均方误差(MSE)、结构相似度指数(SSIM)、峰值信噪比</p><p style="white-space: normal;">参考博客:<a href="https://www.freesion.com/article/50591020225/" target="_blank">https://www.freesion.com/article/50591020225/</a> </p><p style="white-space: normal;"><br/></p><p style="margin-top: 0px; margin-bottom: 10px; white-space: normal; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; color: rgb(51, 51, 51); box-sizing: border-box;">当前系列所有demo下载地址:</p><p style="margin-top: 0px; margin-bottom: 10px; white-space: normal; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; color: rgb(51, 51, 51); box-sizing: border-box;"><a href="https://github.com/GaoRenBao/OpenCv4-Demo" target="_blank" style="margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(0, 102, 0); transition-duration: 0.2s; transition-property: opacity; outline: none; opacity: 0.8;">https://github.com/GaoRenBao/OpenCv4-Demo</a></p><p style="margin-top: 0px; margin-bottom: 10px; text-wrap: wrap; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; box-sizing: border-box; color: rgb(51, 51, 51);">不同编程语言对应的OpenCv版本以及<span style="">开发环境</span>信息如下:<span style="font-family: Calibri; font-size: 14px;"> </span></p><table border="1" style="text-wrap: wrap; border-right: none; border-bottom: none; border-image: initial; border-left: 1px solid rgb(102, 102, 102); border-top: 1px solid rgb(102, 102, 102);"><tbody><tr class="firstRow"><td width="81" valign="top" style="border-color: windowtext rgb(102, 102, 102) rgb(102, 102, 102) windowtext; border-bottom-width: 1px; border-bottom-style: solid; border-right-width: 1px; border-right-style: solid; padding: 5px;"><p style="text-align: center;"><strong><span style="font-family: 宋体; font-size: 14px;">语言</span></strong></p></td><td width="223" valign="top" style="border-color: windowtext rgb(102, 102, 102) rgb(102, 102, 102) windowtext; border-bottom-width: 1px; border-bottom-style: solid; border-right-width: 1px; border-right-style: solid; padding: 5px;"><p style="text-align: center;"><strong><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">OpenCv</span>版本</span></strong></p></td><td width="242" valign="top" style="border-color: windowtext rgb(102, 102, 102) rgb(102, 102, 102) windowtext; border-bottom-width: 1px; border-bottom-style: solid; border-right-width: 1px; border-right-style: solid; padding: 5px;"><p style="text-align: center;"><strong><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">IDE</span></span></strong></p></td></tr><tr><td width="81" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">C#</span></span></p></td><td width="223" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: Calibri; letter-spacing: 0px;">OpenCvSharp4.4.8.0.20230708</span></p></td><td width="242" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">Visual Studio 2022</span></span></p></td></tr><tr><td width="81" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">C++</span></span></p></td><td width="223" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; letter-spacing: 0px;"><span style="font-family: Calibri;">O</span></span><span style="font-family: Calibri; letter-spacing: 0px;">pen</span><span style="font-family: 宋体; letter-spacing: 0px;"><span style="font-family: Calibri;">C</span></span><span style="font-family: Calibri; letter-spacing: 0px;">v-4.5.5-vc14_vc15</span></p></td><td width="242" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">Visual Studio 2022</span></span></p></td></tr><tr><td width="81" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">Python</span></span></p></td><td width="223" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">OpenCv-Python (4.6.0.66)</span></span></p></td><td width="242" valign="top" style="border-top: none; border-left-color: windowtext; border-bottom: 1px solid rgb(102, 102, 102); border-right: 1px solid rgb(102, 102, 102); padding: 5px;"><p><span style="font-family: 宋体; font-size: 14px;"><span style="font-family: Calibri;">PyCharm Community Edition 2022.1.3</span></span></p></td></tr></tbody></table><p><span style="text-wrap: wrap;"></span></p><p style="margin-top: 0px; margin-bottom: 10px; white-space: normal; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; color: rgb(51, 51, 51); box-sizing: border-box;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><br/></strong></p><p style="margin-top: 0px; margin-bottom: 10px; white-space: normal; padding: 0px; list-style: none; border: 0px; overflow-wrap: break-word; word-break: break-all; line-height: 1.5em; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; color: rgb(51, 51, 51); box-sizing: border-box;"><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><span style="font-size: 16px; font-style: italic; font-weight: bold; color: rgb(51, 153, 204); line-height: 18px;">C#版本运行效果及代码如下:</span></strong><br/></p><p style="white-space: normal;"><strong style="white-space: normal; margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><img src="/upload/image/6379934880123842047229124.gif" title="6379670641173703788070263 (1).gif" alt="6379670641173703788070263 (1).gif"/></strong></p><pre class="brush:c#;toolbar:false">using OpenCvSharp;
using System;
using System.Windows.Forms;
namespace WindowsFormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
#region 双摄像头调用
/// <summary>
/// 双摄像头调用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
VideoCapture Cap0 = new VideoCapture();
VideoCapture Cap1 = new VideoCapture();
// 打开ID为0的摄像头
Cap0.Open(0);
// 判断摄像头是否成功打开
if (!Cap0.IsOpened())
{
MessageBox.Show("摄像头0打开失败.");
return;
}
// 打开ID为1的摄像头
Cap1.Open(1);
// 判断摄像头是否成功打开
if (!Cap1.IsOpened())
{
MessageBox.Show("摄像头1打开失败.");
return;
}
Cap0.Set(VideoCaptureProperties.FrameWidth, 320); // 设置采集的图像宽度:320
Cap0.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240
Cap1.Set(VideoCaptureProperties.FrameWidth, 320); // 设置采集的图像宽度:320
Cap1.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240
Mat frame0 = new Mat(), frame1 = new Mat();
while (Cap0.IsOpened() && Cap1.IsOpened())
{
if (Cap0.Read(frame0))
{
Cv2.ImShow("frame0", frame0);
Cv2.SetWindowTitle("frame0", "On Top");
}
if (Cap1.Read(frame1))
{
Cv2.ImShow("frame1", frame1);
Cv2.MoveWindow("frame1", frame0.Cols, 0);
}
if (Cv2.WaitKey(2) == 27)// ESC按钮
break;
}
// When everything done, release the capture
Cap0.Release();
Cap1.Release();
}
#endregion
#region 计算相似度
/// <summary>
/// 计算相似度
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
VideoCapture Cap = new VideoCapture();
Cap.Open(0);
// 打开ID为1的摄像头
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
MessageBox.Show("摄像头打开失败.");
return;
}
Mat frame = new Mat();
Mat temp = new Mat();
Mat gray = new Mat();
if (Cap.Read(frame))
{
Cv2.CvtColor(frame, temp, ColorConversionCodes.BGR2GRAY);
Cv2.ImShow("temp", temp);
}
while (Cap.IsOpened())
{
if (Cap.Read(frame))
{
Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);
Cv2.ImShow("gray", gray);
using (similarity si = new similarity())
{
label1.Text = $"mse:{si.mse(temp, gray)}\r\n";
}
using (similarity si = new similarity())
{
label1.Text += $"ssim:{si.ssim(temp, gray)}\r\n";
}
using (similarity si = new similarity())
{
label1.Text += $"psnr:{si.psnr(temp, gray)}";
}
}
Cv2.WaitKey(30);
}
Cap.Release();
}
#endregion
}
/// <summary>
/// 相似度
/// </summary>
public class similarity : IDisposable
{
void IDisposable.Dispose() { }
public void Dispose() { }
/// <summary>
/// 均方误差(MSE)
/// </summary>
/// <param name="imageA"></param>
/// <param name="imageB"></param>
/// <returns></returns>
public double mse(Mat imageA, Mat imageB)
{
// 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
// 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
Mat M1 = imageA.Clone();
Mat M2 = imageB.Clone();
Mat Diff = new Mat();
// 提前转换为32F精度
M1.ConvertTo(M1, MatType.CV_32F);
M2.ConvertTo(M2, MatType.CV_32F);
Diff.ConvertTo(Diff, MatType.CV_32F);
Cv2.Absdiff(M1, M2, Diff); // Diff = | M1 - M2 |
Diff = Diff.Mul(Diff); // | M1 - M2 |.^2
Scalar S = Cv2.Sum(Diff); //分别计算每个通道的元素之和
double sse; // square error
if (Diff.Channels() == 3)
sse = S.Val0 + S.Val1 + S.Val2; // sum of all channels
else
sse = S.Val0;
long nTotalElement = M2.Channels() * M2.Total();
double mse = (sse / (double)nTotalElement);
return mse;
}
// 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
// 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
// 参考: https://www.freesion.com/article/50591020225/
// C1,C2和C3为常数,是为了避免分母为0而维持稳定。
// L = 255( 是像素值的动态范围,一般都取为255)。
// K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
// 当两张图像一模一样时,SSIM的值等于1。
public double ssim(Mat i1, Mat i2)
{
// 跑久了,会报错。。。。
const double C1 = 6.5025, C2 = 58.5225;
int d = MatType.CV_32F;
Mat I1 = new Mat(), I2 = new Mat();
i1.ConvertTo(I1, d);
i2.ConvertTo(I2, d);
Mat I1_2 = I1.Mul(I1);
Mat I2_2 = I2.Mul(I2);
Mat I1_I2 = I1.Mul(I2);
Mat mu1 = new Mat(), mu2 = new Mat();
Cv2.GaussianBlur(I1, mu1, new Size(11, 11), 1.5);
Cv2.GaussianBlur(I2, mu2, new 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 = new Mat(), sigam2_2 = new Mat(), sigam12 = new Mat();
Cv2.GaussianBlur(I1_2, sigma1_2, new Size(11, 11), 1.5);
sigma1_2 -= mu1_2;
Cv2.GaussianBlur(I2_2, sigam2_2, new Size(11, 11), 1.5);
sigam2_2 -= mu2_2;
Cv2.GaussianBlur(I1_I2, sigam12, new Size(11, 11), 1.5);
sigam12 -= mu1_mu2;
Mat t1, t2, t3;
t1 = 2 * mu1_mu2 + C1;
t2 = 2 * sigam12 + C2;
t3 = t1.Mul(t2);
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigam2_2 + C2;
t1 = t1.Mul(t2);
Mat ssim_map = new Mat();
Cv2.Divide(t3, t1, ssim_map);
Scalar mssim = Cv2.Mean(ssim_map);
double ssim = (mssim.Val0 + mssim.Val1 + mssim.Val2) / 3;
return ssim;
}
// 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
// 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
// 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
// PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
public double psnr(Mat I1, Mat I2)
{
//注意,当两幅图像一样时这个函数计算出来的psnr为0
Mat s1 = new Mat();
Cv2.Absdiff(I1, I2, s1);
s1.ConvertTo(s1, MatType.CV_32F);//转换为32位的float类型,8位不能计算平方
s1 = s1.Mul(s1);
Scalar s = Cv2.Sum(s1); //计算每个通道的和
double sse = s.Val0 + s.Val1 + s.Val2;
if (sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse = sse / (double)(I1.Channels() * I1.Total()); // sse/(w*h*3)
double psnr = 10.0 * Math.Log10((255 * 255) / mse);
return psnr;
}
}
}
}</pre><p style="white-space: normal;"><strong style="white-space: normal; margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "></strong><br/></p><p style="white-space: normal;"><strong style="white-space: normal; margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><span style="font-size: 16px; font-style: italic; font-weight: bold; color: rgb(51, 153, 204); line-height: 18px;">C++<strong style="white-space: normal; margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; ">版本运行效果及代码如下:</strong></span><strong style="white-space: normal; margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "></strong></strong></strong></p><p><img src="/upload/image/6379934943279463058568486.gif" title="6379670665036039114262581 (1).gif" alt="6379670665036039114262581 (1).gif"/></p><pre class="brush:cpp;toolbar:false">#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
// 双摄像头调用
void demo1()
{
VideoCapture cap0;
VideoCapture cap1;
cap0.open(0);
cap1.open(1);
if (!cap0.isOpened())
{
cout << "Error0" << endl;
return;
}
if (!cap1.isOpened())
{
cout << "Error1" << endl;
return;
}
bool ret0 = cap0.set(3, 320);
bool ret1 = cap0.set(4, 240);
bool ret2 = cap1.set(3, 320);
bool ret3 = cap1.set(4, 240);
while (cap0.isOpened() && cap1.isOpened())
{
Mat frame0, frame1;
cap0 >> frame0;
cap1 >> frame1;
if (!frame0.empty())
{
imshow("frame0", frame0);
setWindowTitle("frame0", "On Top");
}
if (!frame1.empty())
{
imshow("frame1", frame1);
moveWindow("frame1", frame0.cols, 0);
//moveWindow("frame1", 320, 40);
}
if (waitKey(2) == 27)// ESC按钮
break;
}
// When everything done, release the capture
cap0.release();
cap1.release();
destroyAllWindows();
}
// 均方误差(MSE)
double mse(Mat imageA, Mat imageB)
{
// 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
// 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
cv::Mat M1 = imageA.clone();
cv::Mat M2 = imageB.clone();
cv::Mat Diff;
// 提前转换为32F精度
M1.convertTo(M1, CV_32F);
M2.convertTo(M2, CV_32F);
Diff.convertTo(Diff, CV_32F);
cv::absdiff(M1, M2, Diff); // Diff = | M1 - M2 |
Diff = Diff.mul(Diff); // | M1 - M2 |.^2
cv::Scalar S = cv::sum(Diff); //分别计算每个通道的元素之和
double sse; // square error
if (Diff.channels() == 3)
sse = S.val[0] + S.val[1] + S.val[2]; // sum of all channels
else
sse = S.val[0];
int nTotalElement = M2.channels() * M2.total();
double mse = (sse / (double)nTotalElement); //
return mse;
}
// 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
// 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
// 参考: https://www.freesion.com/article/50591020225/
// C1,C2和C3为常数,是为了避免分母为0而维持稳定。
// L = 255( 是像素值的动态范围,一般都取为255)。
// K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
// 当两张图像一模一样时,SSIM的值等于1。
double ssim(Mat& i1, Mat& i2) {
const double C1 = 6.5025, C2 = 58.5225;
int d = CV_32F;
Mat I1, I2;
i1.convertTo(I1, d);
i2.convertTo(I2, d);
Mat I1_2 = I1.mul(I1);
Mat I2_2 = I2.mul(I2);
Mat I1_I2 = I1.mul(I2);
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, sigam2_2, sigam12;
GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
sigma1_2 -= mu1_2;
GaussianBlur(I2_2, sigam2_2, Size(11, 11), 1.5);
sigam2_2 -= mu2_2;
GaussianBlur(I1_I2, sigam12, Size(11, 11), 1.5);
sigam12 -= mu1_mu2;
Mat t1, t2, t3;
t1 = 2 * mu1_mu2 + C1;
t2 = 2 * sigam12 + C2;
t3 = t1.mul(t2);
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigam2_2 + C2;
t1 = t1.mul(t2);
Mat ssim_map;
divide(t3, t1, ssim_map);
Scalar mssim = mean(ssim_map);
double ssim = (mssim.val[0] + mssim.val[1] + mssim.val[2]) / 3;
return ssim;
}
// 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
// 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
// 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
// PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
double psnr(Mat& I1, Mat& I2) { //注意,当两幅图像一样时这个函数计算出来的psnr为0
Mat s1;
absdiff(I1, I2, s1);
s1.convertTo(s1, CV_32F);//转换为32位的float类型,8位不能计算平方
s1 = s1.mul(s1);
Scalar s = sum(s1); //计算每个通道的和
double sse = s.val[0] + s.val[1] + s.val[2];
if (sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse = sse / (double)(I1.channels() * I1.total()); // sse/(w*h*3)
double psnr = 10.0 * log10((255 * 255) / mse);
return psnr;
}
}
// 图像相似度对比
void demo2()
{
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
{
cout << "Error0" << endl;
return;
}
Mat frame;
cap >> frame;
Mat temp;
cvtColor(frame, temp, COLOR_BGR2GRAY);
imshow("temp", temp);
while (cap.isOpened())
{
cap >> frame;
if (frame.empty())
{
continue;
}
Mat gray;
cvtColor(frame, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
cout << "mse:" << mse(temp, gray) << "\tssim:" << ssim(temp, gray) << "\tpsnr:" << psnr(temp, gray) << endl;
waitKey(200);
}
cap.release();
destroyAllWindows();
}
void main()
{
demo2();
}</pre><p><br/></p><p><strong style="white-space: normal; margin: 0px; padding: 0px; list-style: none; border: 0px; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "WenQuanYi Micro Hei", Arial, sans-serif; "><strong style="margin: 0px; padding: 0px; list-style: none; border: 0px;"><span style="font-size: 16px; font-style: italic; font-weight: bold; color: rgb(51, 153, 204); line-height: 18px;">Python版本代码如下:</span></strong></strong></p><p>python效果和C++差不多,这里就不进行图像演示了,感兴趣的童鞋,可以自己跑一下<br/></p><pre class="brush:python;toolbar:false"># 需要安装 pip install Scikit-Image
# 老版本
# from skimage.measure import compare_ssim as ssim
# from skimage.measure import compare_mse as mse
# 新版本
from skimage.metrics import structural_similarity as ssim
from skimage.metrics import mean_squared_error as mse
import matplotlib.pyplot as plt
import numpy as np
import cv2
# 双摄像头调用
def demo1():
cap0 = cv2.VideoCapture(0)
cap1 = cv2.VideoCapture(1)
ret = cap0.set(3, 320)
ret = cap0.set(4, 240)
ret = cap1.set(3, 320)
ret = cap1.set(4, 240)
while cap0.isOpened() and cap1.isOpened():
ret0, frame0 = cap0.read()
ret1, frame1 = cap1.read()
if ret0:
cv2.imshow('frame0', frame0)
cv2.setWindowTitle('frame0', 'On Top')
if ret1:
cv2.imshow('frame1', frame1)
# cv2.moveWindow('frame1', x=frame0.shape[1], y=0)
cv2.moveWindow('frame1', x=320, y=40)
key = cv2.waitKey(delay=2)
if key == ord("q"):
break
# When everything done, release the capture
cap0.release()
cap1.release()
cv2.destroyAllWindows()
# mse 算法
def my_mse(imageA, imageB):
# the 'Mean Squared Error' between the two images is the
# sum of the squared difference between the two images;
# NOTE: the two images must have the same dimension
err = np.sum((imageA.astype("float") - imageB.astype("float")) * 2)
err /= float(imageA.shape[0] * imageA.shape[1])
# return the MSE, the lower the error, the more "similar"
# the two images are
return err
# 图像相似度计算(感觉不咋滴)
def demo2():
cap = cv2.VideoCapture(0)
if cap.isOpened() == False:
print('Open Camera Error.')
return
ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)
ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
title = 'camera compare'
plt.ion()
ret, frame = cap.read()
if ret == True:
temp = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow("temp", temp)
while cap.isOpened():
ret, frame = cap.read()
if ret == True:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)
m = mse(temp, gray) # 均方误差(MSE)
s = ssim(temp, gray) # 结构相似度指数(SSIM)
print("MSE: %.2f, SSIM: %.2f" % (m, s))
temp = gray.copy()
cv2.waitKey(500)
# 运行demo2,如果想运行其他demo,改一下这个即可
if __name__ == '__main__':
# demo1()
demo2()</pre><p><br/></p>
双摄像头操作与图像相似度检测 本章节内容是博主网上收集的,主要内容包括:双摄像头的调用方法、图像窗体的固定、均方误差(MSE)、结构相似度指数(SSIM)、峰值信噪比
参考博客:https://www.freesion.com/article/50591020225/
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
不同编程语言对应的OpenCv版本以及开发环境 信息如下:
语言
OpenCv 版本
IDE
C#
OpenCvSharp4.4.8.0.20230708
Visual Studio 2022
C++
O pen C v-4.5.5-vc14_vc15
Visual Studio 2022
Python
OpenCv-Python (4.6.0.66)
PyCharm Community Edition 2022.1.3
C#版本运行效果及代码如下:
using OpenCvSharp;
using System;
using System.Windows.Forms;
namespace WindowsFormsApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
#region 双摄像头调用
/// <summary>
/// 双摄像头调用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
VideoCapture Cap0 = new VideoCapture();
VideoCapture Cap1 = new VideoCapture();
// 打开ID为0的摄像头
Cap0.Open(0);
// 判断摄像头是否成功打开
if (!Cap0.IsOpened())
{
MessageBox.Show("摄像头0打开失败.");
return;
}
// 打开ID为1的摄像头
Cap1.Open(1);
// 判断摄像头是否成功打开
if (!Cap1.IsOpened())
{
MessageBox.Show("摄像头1打开失败.");
return;
}
Cap0.Set(VideoCaptureProperties.FrameWidth, 320); // 设置采集的图像宽度:320
Cap0.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240
Cap1.Set(VideoCaptureProperties.FrameWidth, 320); // 设置采集的图像宽度:320
Cap1.Set(VideoCaptureProperties.FrameHeight, 240); // 设置采集的图像高度:240
Mat frame0 = new Mat(), frame1 = new Mat();
while (Cap0.IsOpened() && Cap1.IsOpened())
{
if (Cap0.Read(frame0))
{
Cv2.ImShow("frame0", frame0);
Cv2.SetWindowTitle("frame0", "On Top");
}
if (Cap1.Read(frame1))
{
Cv2.ImShow("frame1", frame1);
Cv2.MoveWindow("frame1", frame0.Cols, 0);
}
if (Cv2.WaitKey(2) == 27)// ESC按钮
break;
}
// When everything done, release the capture
Cap0.Release();
Cap1.Release();
}
#endregion
#region 计算相似度
/// <summary>
/// 计算相似度
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
VideoCapture Cap = new VideoCapture();
Cap.Open(0);
// 打开ID为1的摄像头
Cap.Open(0);
// 判断摄像头是否成功打开
if (!Cap.IsOpened())
{
MessageBox.Show("摄像头打开失败.");
return;
}
Mat frame = new Mat();
Mat temp = new Mat();
Mat gray = new Mat();
if (Cap.Read(frame))
{
Cv2.CvtColor(frame, temp, ColorConversionCodes.BGR2GRAY);
Cv2.ImShow("temp", temp);
}
while (Cap.IsOpened())
{
if (Cap.Read(frame))
{
Cv2.CvtColor(frame, gray, ColorConversionCodes.BGR2GRAY);
Cv2.ImShow("gray", gray);
using (similarity si = new similarity())
{
label1.Text = $"mse:{si.mse(temp, gray)}\r\n";
}
using (similarity si = new similarity())
{
label1.Text += $"ssim:{si.ssim(temp, gray)}\r\n";
}
using (similarity si = new similarity())
{
label1.Text += $"psnr:{si.psnr(temp, gray)}";
}
}
Cv2.WaitKey(30);
}
Cap.Release();
}
#endregion
}
/// <summary>
/// 相似度
/// </summary>
public class similarity : IDisposable
{
void IDisposable.Dispose() { }
public void Dispose() { }
/// <summary>
/// 均方误差(MSE)
/// </summary>
/// <param name="imageA"></param>
/// <param name="imageB"></param>
/// <returns></returns>
public double mse(Mat imageA, Mat imageB)
{
// 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
// 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
Mat M1 = imageA.Clone();
Mat M2 = imageB.Clone();
Mat Diff = new Mat();
// 提前转换为32F精度
M1.ConvertTo(M1, MatType.CV_32F);
M2.ConvertTo(M2, MatType.CV_32F);
Diff.ConvertTo(Diff, MatType.CV_32F);
Cv2.Absdiff(M1, M2, Diff); // Diff = | M1 - M2 |
Diff = Diff.Mul(Diff); // | M1 - M2 |.^2
Scalar S = Cv2.Sum(Diff); //分别计算每个通道的元素之和
double sse; // square error
if (Diff.Channels() == 3)
sse = S.Val0 + S.Val1 + S.Val2; // sum of all channels
else
sse = S.Val0;
long nTotalElement = M2.Channels() * M2.Total();
double mse = (sse / (double)nTotalElement);
return mse;
}
// 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
// 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
// 参考: https://www.freesion.com/article/50591020225/
// C1,C2和C3为常数,是为了避免分母为0而维持稳定。
// L = 255( 是像素值的动态范围,一般都取为255)。
// K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
// 当两张图像一模一样时,SSIM的值等于1。
public double ssim(Mat i1, Mat i2)
{
// 跑久了,会报错。。。。
const double C1 = 6.5025, C2 = 58.5225;
int d = MatType.CV_32F;
Mat I1 = new Mat(), I2 = new Mat();
i1.ConvertTo(I1, d);
i2.ConvertTo(I2, d);
Mat I1_2 = I1.Mul(I1);
Mat I2_2 = I2.Mul(I2);
Mat I1_I2 = I1.Mul(I2);
Mat mu1 = new Mat(), mu2 = new Mat();
Cv2.GaussianBlur(I1, mu1, new Size(11, 11), 1.5);
Cv2.GaussianBlur(I2, mu2, new 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 = new Mat(), sigam2_2 = new Mat(), sigam12 = new Mat();
Cv2.GaussianBlur(I1_2, sigma1_2, new Size(11, 11), 1.5);
sigma1_2 -= mu1_2;
Cv2.GaussianBlur(I2_2, sigam2_2, new Size(11, 11), 1.5);
sigam2_2 -= mu2_2;
Cv2.GaussianBlur(I1_I2, sigam12, new Size(11, 11), 1.5);
sigam12 -= mu1_mu2;
Mat t1, t2, t3;
t1 = 2 * mu1_mu2 + C1;
t2 = 2 * sigam12 + C2;
t3 = t1.Mul(t2);
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigam2_2 + C2;
t1 = t1.Mul(t2);
Mat ssim_map = new Mat();
Cv2.Divide(t3, t1, ssim_map);
Scalar mssim = Cv2.Mean(ssim_map);
double ssim = (mssim.Val0 + mssim.Val1 + mssim.Val2) / 3;
return ssim;
}
// 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
// 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
// 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
// PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
public double psnr(Mat I1, Mat I2)
{
//注意,当两幅图像一样时这个函数计算出来的psnr为0
Mat s1 = new Mat();
Cv2.Absdiff(I1, I2, s1);
s1.ConvertTo(s1, MatType.CV_32F);//转换为32位的float类型,8位不能计算平方
s1 = s1.Mul(s1);
Scalar s = Cv2.Sum(s1); //计算每个通道的和
double sse = s.Val0 + s.Val1 + s.Val2;
if (sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse = sse / (double)(I1.Channels() * I1.Total()); // sse/(w*h*3)
double psnr = 10.0 * Math.Log10((255 * 255) / mse);
return psnr;
}
}
}
}
C++版本运行效果及代码如下:
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
// 双摄像头调用
void demo1()
{
VideoCapture cap0;
VideoCapture cap1;
cap0.open(0);
cap1.open(1);
if (!cap0.isOpened())
{
cout << "Error0" << endl;
return;
}
if (!cap1.isOpened())
{
cout << "Error1" << endl;
return;
}
bool ret0 = cap0.set(3, 320);
bool ret1 = cap0.set(4, 240);
bool ret2 = cap1.set(3, 320);
bool ret3 = cap1.set(4, 240);
while (cap0.isOpened() && cap1.isOpened())
{
Mat frame0, frame1;
cap0 >> frame0;
cap1 >> frame1;
if (!frame0.empty())
{
imshow("frame0", frame0);
setWindowTitle("frame0", "On Top");
}
if (!frame1.empty())
{
imshow("frame1", frame1);
moveWindow("frame1", frame0.cols, 0);
//moveWindow("frame1", 320, 40);
}
if (waitKey(2) == 27)// ESC按钮
break;
}
// When everything done, release the capture
cap0.release();
cap1.release();
destroyAllWindows();
}
// 均方误差(MSE)
double mse(Mat imageA, Mat imageB)
{
// 在使用OpenCV时可以通过矩阵操作来避免for循环嵌套计算。
// 需要注意的是乘除操作一般要注意将图像本身的uint8转换成float后再做,否则精度误差可能会导致较大偏差。
cv::Mat M1 = imageA.clone();
cv::Mat M2 = imageB.clone();
cv::Mat Diff;
// 提前转换为32F精度
M1.convertTo(M1, CV_32F);
M2.convertTo(M2, CV_32F);
Diff.convertTo(Diff, CV_32F);
cv::absdiff(M1, M2, Diff); // Diff = | M1 - M2 |
Diff = Diff.mul(Diff); // | M1 - M2 |.^2
cv::Scalar S = cv::sum(Diff); //分别计算每个通道的元素之和
double sse; // square error
if (Diff.channels() == 3)
sse = S.val[0] + S.val[1] + S.val[2]; // sum of all channels
else
sse = S.val[0];
int nTotalElement = M2.channels() * M2.total();
double mse = (sse / (double)nTotalElement); //
return mse;
}
// 结构相似度指数(SSIM),是一种衡量两幅图像相似度的指标,
// 也是一种全参考的图像质量评价指标,它分别从亮度、对比度、结构三方面度量图像相似性
// 参考: https://www.freesion.com/article/50591020225/
// C1,C2和C3为常数,是为了避免分母为0而维持稳定。
// L = 255( 是像素值的动态范围,一般都取为255)。
// K1 = 0.01, K2 = 0.03。结构相似性的范围为 - 1 到 1 。
// 当两张图像一模一样时,SSIM的值等于1。
double ssim(Mat& i1, Mat& i2) {
const double C1 = 6.5025, C2 = 58.5225;
int d = CV_32F;
Mat I1, I2;
i1.convertTo(I1, d);
i2.convertTo(I2, d);
Mat I1_2 = I1.mul(I1);
Mat I2_2 = I2.mul(I2);
Mat I1_I2 = I1.mul(I2);
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, sigam2_2, sigam12;
GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
sigma1_2 -= mu1_2;
GaussianBlur(I2_2, sigam2_2, Size(11, 11), 1.5);
sigam2_2 -= mu2_2;
GaussianBlur(I1_I2, sigam12, Size(11, 11), 1.5);
sigam12 -= mu1_mu2;
Mat t1, t2, t3;
t1 = 2 * mu1_mu2 + C1;
t2 = 2 * sigam12 + C2;
t3 = t1.mul(t2);
t1 = mu1_2 + mu2_2 + C1;
t2 = sigma1_2 + sigam2_2 + C2;
t1 = t1.mul(t2);
Mat ssim_map;
divide(t3, t1, ssim_map);
Scalar mssim = mean(ssim_map);
double ssim = (mssim.val[0] + mssim.val[1] + mssim.val[2]) / 3;
return ssim;
}
// 峰值信噪比,一种评价图像的客观标准,用来评估图像的保真性。
// 峰值信噪比经常用作图像压缩等领域中信号重建质量的测量方法,
// 它常简单地通过均方差(MSE)进行定义,使用两个m×n单色图像I和K。PSNR的单位为分贝
// PSNR值越大,就代表失真越少,图像压缩中典型的峰值信噪比值在 30 到 40dB 之间,小于30dB时考虑图像无法忍受。
double psnr(Mat& I1, Mat& I2) { //注意,当两幅图像一样时这个函数计算出来的psnr为0
Mat s1;
absdiff(I1, I2, s1);
s1.convertTo(s1, CV_32F);//转换为32位的float类型,8位不能计算平方
s1 = s1.mul(s1);
Scalar s = sum(s1); //计算每个通道的和
double sse = s.val[0] + s.val[1] + s.val[2];
if (sse <= 1e-10) // for small values return zero
return 0;
else
{
double mse = sse / (double)(I1.channels() * I1.total()); // sse/(w*h*3)
double psnr = 10.0 * log10((255 * 255) / mse);
return psnr;
}
}
// 图像相似度对比
void demo2()
{
VideoCapture cap;
cap.open(0);
if (!cap.isOpened())
{
cout << "Error0" << endl;
return;
}
Mat frame;
cap >> frame;
Mat temp;
cvtColor(frame, temp, COLOR_BGR2GRAY);
imshow("temp", temp);
while (cap.isOpened())
{
cap >> frame;
if (frame.empty())
{
continue;
}
Mat gray;
cvtColor(frame, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
cout << "mse:" << mse(temp, gray) << "\tssim:" << ssim(temp, gray) << "\tpsnr:" << psnr(temp, gray) << endl;
waitKey(200);
}
cap.release();
destroyAllWindows();
}
void main()
{
demo2();
}
Python版本代码如下:
python效果和C++差不多,这里就不进行图像演示了,感兴趣的童鞋,可以自己跑一下
# 需要安装 pip install Scikit-Image
# 老版本
# from skimage.measure import compare_ssim as ssim
# from skimage.measure import compare_mse as mse
# 新版本
from skimage.metrics import structural_similarity as ssim
from skimage.metrics import mean_squared_error as mse
import matplotlib.pyplot as plt
import numpy as np
import cv2
# 双摄像头调用
def demo1():
cap0 = cv2.VideoCapture(0)
cap1 = cv2.VideoCapture(1)
ret = cap0.set(3, 320)
ret = cap0.set(4, 240)
ret = cap1.set(3, 320)
ret = cap1.set(4, 240)
while cap0.isOpened() and cap1.isOpened():
ret0, frame0 = cap0.read()
ret1, frame1 = cap1.read()
if ret0:
cv2.imshow('frame0', frame0)
cv2.setWindowTitle('frame0', 'On Top')
if ret1:
cv2.imshow('frame1', frame1)
# cv2.moveWindow('frame1', x=frame0.shape[1], y=0)
cv2.moveWindow('frame1', x=320, y=40)
key = cv2.waitKey(delay=2)
if key == ord("q"):
break
# When everything done, release the capture
cap0.release()
cap1.release()
cv2.destroyAllWindows()
# mse 算法
def my_mse(imageA, imageB):
# the 'Mean Squared Error' between the two images is the
# sum of the squared difference between the two images;
# NOTE: the two images must have the same dimension
err = np.sum((imageA.astype("float") - imageB.astype("float")) * 2)
err /= float(imageA.shape[0] * imageA.shape[1])
# return the MSE, the lower the error, the more "similar"
# the two images are
return err
# 图像相似度计算(感觉不咋滴)
def demo2():
cap = cv2.VideoCapture(0)
if cap.isOpened() == False:
print('Open Camera Error.')
return
ret = cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 640)
ret = cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
title = 'camera compare'
plt.ion()
ret, frame = cap.read()
if ret == True:
temp = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow("temp", temp)
while cap.isOpened():
ret, frame = cap.read()
if ret == True:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)
m = mse(temp, gray) # 均方误差(MSE)
s = ssim(temp, gray) # 结构相似度指数(SSIM)
print("MSE: %.2f, SSIM: %.2f" % (m, s))
temp = gray.copy()
cv2.waitKey(500)
# 运行demo2,如果想运行其他demo,改一下这个即可
if __name__ == '__main__':
# demo1()
demo2()